/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.crypto.cipher;

import java.nio.ByteBuffer;
import java.security.Key;
import java.util.Arrays;
import java.util.Properties;
import java.util.Random;

import javax.crypto.AEADBadTagException;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.crypto.utils.Utils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class GcmCipherTest {

    Properties props = null;
    String cipherClass = null;
    String transformation = "AES/GCM/NoPadding";

    private String[] kHex;
    private String[] pHex;
    private String[] ivHex;
    private String[] aadHex;
    private String[] cHex;
    private String[] tHex;

    @Before
    public void setup() {
        //init
        cipherClass = OpenSslCipher.class.getName();

        props = new Properties();
        props.setProperty(CryptoCipherFactory.CLASSES_KEY,
                cipherClass);
        initTestData();
    }

    /**
     * NIST AES Test Vectors
     * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
     */
    @Test
    public void testGcmNistCase2() throws Exception {
        // key length:          16 bytes
        // plain text length:   16 bytes
        // iv length:           12 bytes
        // aad length:          0 bytes

        final String kHex = "00000000000000000000000000000000";
        final String pHex = "00000000000000000000000000000000";
        final String ivHex = "000000000000000000000000";
        final String aadHex = "";

        final String cHex = "0388dace60b6a392f328c2b971b2fe78";
        final String tHex = "ab6e47d42cec13bdf53a67b21257bddf";

        testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex);
    }

    @Test
    public void testGcmNistCase4() throws Exception {
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           12 bytes
        // aad length:          20 bytes

        final String kHex = "feffe9928665731c6d6a8f9467308308";
        final String pHex = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";
        final String ivHex = "cafebabefacedbaddecaf888";
        final String aadHex = "feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";

        final String cHex = "42831ec2217774244b7221b784d0d49c"
                + "e3aa212f2c02a4e035c17e2329aca12e"
                + "21d514b25466931c7d8f6a5aac84aa05"
                + "1ba30b396a0aac973d58e091";
        final String tHex = "5bc94fbc3221a5db94fae95ae7121a47";

        testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex);
    }

    @Test
    public void testGcmNistCase5() throws Exception {
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           8 bytes
        // aad length:          20 bytes

        final String kHex = "feffe9928665731c6d6a8f9467308308";

        final String pHex = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";

        final String ivHex ="cafebabefacedbad"; // 64bits < 96bits

        final String aadHex="feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";

        final String cHex = "61353b4c2806934a777ff51fa22a4755"
                + "699b2a714fcdc6f83766e5f97b6c7423"
                + "73806900e49f24b22b097544d4896b42"
                + "4989b5e1ebac0f07c23f4598";

        final String tHex = "3612d2e79e3b0785561be14aaca2fccb";

        testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex);
    }

    @Test
    public void testGcmNistCase6() throws Exception {
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           60 bytes
        // aad length:          20 bytes

        final String kHex = "feffe9928665731c6d6a8f9467308308";

        final String pHex = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";

        final String ivHex ="9313225df88406e555909c5aff5269aa"
                + "6a7a9538534f7da1e4c303d2a318a728"
                + "c3c0c95156809539fcf0e2429a6b5254"
                + "16aedbf5a0de6a57a637b39b"; // > 96bits

        final String aadHex="feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";

        final String cHex = "8ce24998625615b603a033aca13fb894"
                + "be9112a5c3a211a8ba262a3cca7e2ca7"
                + "01e4a9a4fba43c90ccdcb281d48c7c6f"
                + "d62875d2aca417034c34aee5";

        final String tHex = "619cc5aefffe0bfa462af43c1699d050";

        testGcmEncryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmDecryption(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmByteBuffer(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmReturnDataAfterTagVerified(kHex, pHex, ivHex, aadHex, cHex, tHex);
        testGcmArbitraryLengthUpdate(kHex, pHex, ivHex, aadHex, cHex, tHex);
    }

    @Test
    public void testGcmNistCases() throws Exception {
        for(int i = 0; i < kHex.length; i++) {
            testGcmEncryption(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]);
            testGcmDecryption(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]);
            testGcmByteBuffer(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]);
            testGcmReturnDataAfterTagVerified(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]);
            testGcmArbitraryLengthUpdate(kHex[i], pHex[i], ivHex[i], aadHex[i], cHex[i], tHex[i]);
        }
    }

    @Test(expected = AEADBadTagException.class)
    public void testGcmTamperedData() throws Exception {

        final Random r = new Random();
        final int textLength = r.nextInt(1024*1024);
        final int ivLength = r.nextInt(60);
        final int keyLength = 16;
        final int tagLength = 128;  // bits
        final int aadLength = r.nextInt(128);

        final byte[] keyBytes = new byte[keyLength];
        final byte[] plainBytes = new byte[textLength];
        final byte[] ivBytes = new byte[ivLength];
        final byte[] aadBytes = new byte[aadLength];

        r.nextBytes(keyBytes);
        r.nextBytes(plainBytes);
        r.nextBytes(ivBytes);
        r.nextBytes(aadBytes);

        final byte[] encOutput = new byte[plainBytes.length + (tagLength >> 3)];
        final byte[] decOutput = new byte[plainBytes.length];

        {
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");

            final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes);
            c.init(Cipher.ENCRYPT_MODE, key, iv);
            c.updateAAD(aadBytes);
            c.doFinal(plainBytes, 0, plainBytes.length, encOutput, 0);
            c.close();
        }

        // Tamper the encrypted data.
        encOutput[0] = (byte)(encOutput[0] + 1);

        try {
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");

            final GCMParameterSpec iv = new GCMParameterSpec(tagLength, ivBytes);
            c.init(Cipher.DECRYPT_MODE, key, iv);
            c.updateAAD(aadBytes);
            c.doFinal(encOutput, 0, encOutput.length, decOutput, 0);
            c.close();
        }
        catch (final AEADBadTagException ex) {
            Assert.assertTrue("Tag mismatch!".equals(ex.getMessage()));
            throw ex;
        }
    }

    @Test
    public void testGMac() throws Exception {
        // for GMAC,  aad is the input data,
        // tag is the digest message

        final Random r = new Random();
        final byte[] keyBytes = new byte[32];
        final byte[] input = new byte[0];  // no input for GMAC
        final byte[] ivBytes = new byte[16];

        final byte[] tag_orig = new byte[16]; // JDK's tag
        final byte[] tag = new byte[16];

        // aad is the data to be hashed
        final byte[] aad = new byte[r.nextInt() % 1000 + 1000 ];

        r.nextBytes(keyBytes);
        r.nextBytes(input);
        r.nextBytes(ivBytes);
        r.nextBytes(aad);

        {
            final Cipher c = Cipher.getInstance(transformation);
            final Key key = new SecretKeySpec(keyBytes, "AES");
            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
            c.init(Cipher.ENCRYPT_MODE, key, iv);
            c.updateAAD(aad);
            c.doFinal(input, 0, input.length, tag_orig, 0);
        }

        {
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");
            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
            c.init(Cipher.ENCRYPT_MODE, key, iv);
            c.updateAAD(aad);
            c.doFinal(input, 0, input.length, tag, 0);
            c.close();
        }

        // tag should be the same as JDK's cipher
        Assert.assertArrayEquals(tag_orig, tag);

        // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode
        // let's verify the add & tag now
        {
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");
            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
            c.init(Cipher.DECRYPT_MODE, key, iv);
            c.updateAAD(aad);
            c.doFinal(tag, 0, tag.length, input, 0);
            c.close();
        }
    }

    @Test(expected = AEADBadTagException.class)
    public void testGMacTamperedData() throws Exception {
        final Random r = new Random();
        final byte[] keyBytes = new byte[32];
        final byte[] input = new byte[0];
        final byte[] ivBytes = new byte[16];

        final byte[] tag = new byte[16];

        final byte[] aad = new byte[r.nextInt() % 1000 + 1000 ];

        r.nextBytes(keyBytes);
        r.nextBytes(input);
        r.nextBytes(ivBytes);
        r.nextBytes(aad);

        {
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");
            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
            c.init(Cipher.ENCRYPT_MODE, key, iv);
            c.updateAAD(aad);
            c.doFinal(input, 0, input.length, tag, 0);
            c.close();
        }

        try {
            // like JDK's decrypt mode. The plaintext+tag is the input for decrypt mode
            final CryptoCipher c = Utils.getCipherInstance(transformation, props);
            final Key key = new SecretKeySpec(keyBytes, "AES");
            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
            c.init(Cipher.DECRYPT_MODE, key, iv);

            // if the origin data is tampered
            aad[0] = (byte) (aad[0] + 1);
            c.updateAAD(aad);

            c.doFinal(tag, 0, tag.length, input, 0);
            c.close();

        }
        catch (final AEADBadTagException ex) {
            Assert.assertTrue("Tag mismatch!".equals(ex.getMessage()));
            throw ex;
        }
    }

    private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
                                   final String cHex, final String tHex) throws Exception {

        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);

        final byte[] output = new byte[expectedOutput.length];

        final CryptoCipher c = Utils.getCipherInstance(transformation, props);

        final Key key = new SecretKeySpec(keyBytes, "AES");

        final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
        c.init(Cipher.ENCRYPT_MODE, key, iv);
        c.updateAAD(aad);

        c.doFinal(input, 0, input.length, output, 0);

        Assert.assertArrayEquals(expectedOutput, output);
        c.close();
    }

    private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex,
                                              final String cHex, final String tHex) throws Exception {

        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);

        final byte[] encOutput = new byte[expectedOutput.length];
        final byte[] decOutput = new byte[input.length];

        final Random r = new Random();

        final CryptoCipher enc = Utils.getCipherInstance(transformation, props);
        final Key key = new SecretKeySpec(keyBytes, "AES");
        final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
        enc.init(Cipher.ENCRYPT_MODE, key, iv);
        if (aad.length > 0) {
            final int len1 = r.nextInt(aad.length) ;
            final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
            final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
            enc.updateAAD(aad1);
            enc.updateAAD(aad2);
        }

        int partLen = r.nextInt(input.length);
        int len = enc.update(input, 0, partLen, encOutput, 0);
        Assert.assertTrue(len == partLen);
        len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen);
        Assert.assertTrue(len == (input.length + (iv.getTLen() >> 3) - partLen));

        Assert.assertArrayEquals(expectedOutput, encOutput);
        enc.close();

        // Decryption
        final CryptoCipher dec = Utils.getCipherInstance(transformation, props);
        dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"),
                new GCMParameterSpec(128, ivBytes));
        if (aad.length > 0) {
            final int len1 = r.nextInt(aad.length) ;
            final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
            final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
            dec.updateAAD(aad1);
            dec.updateAAD(aad2);
        }
        final byte[] decInput = encOutput;
        partLen = r.nextInt(input.length);
        len = dec.update(decInput, 0, partLen, decOutput, 0);
        Assert.assertTrue(len == 0);
        len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0);
        Assert.assertTrue(len == input.length);

        Assert.assertArrayEquals(input, decOutput);
        dec.close();
    }

    private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
                                   final String cHex, final String tHex) throws Exception {

        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);

        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);

        final byte[] input = cipherBytes;
        final byte[] output = new byte[plainBytes.length];

        final CryptoCipher c = Utils.getCipherInstance(transformation, props);

        final Key key = new SecretKeySpec(keyBytes, "AES");

        final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
        c.init(Cipher.DECRYPT_MODE, key, iv);
        c.updateAAD(aad);
        c.doFinal(input, 0, input.length, output, 0);

        Assert.assertArrayEquals(plainBytes, output);
        c.close();
    }

    private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex,
                                                   final String cHex, final String tHex) throws Exception {

        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);

        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);

        final byte[] input = cipherBytes;
        final byte[] output = new byte[plainBytes.length];

        final CryptoCipher c = Utils.getCipherInstance(transformation, props);

        final Key key = new SecretKeySpec(keyBytes, "AES");

        final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
        c.init(Cipher.DECRYPT_MODE, key, iv);
        c.updateAAD(aad);

        //only return recovered data after tag is successfully verified
        int len = c.update(input, 0, input.length, output, 0);
        Assert.assertTrue(len == 0);
        len += c.doFinal(input, input.length, 0, output, 0);
        Assert.assertTrue(len == plainBytes.length);

        Assert.assertArrayEquals(plainBytes, output);
        c.close();
    }

    private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex,
                                   final String cHex, final String tHex) throws Exception {

        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
        final byte[] plainText = DatatypeConverter.parseHexBinary(pHex);
        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
        final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex);

        final byte[] encOutput = new byte[cipherText.length];
        final byte[] decOutput = new byte[plainText.length];

        final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length);
        bfAAD.put(aad);

        ByteBuffer bfPlainText;
        ByteBuffer bfCipherText;
        bfPlainText = ByteBuffer.allocateDirect(plainText.length);
        bfCipherText = ByteBuffer.allocateDirect(encOutput.length);

        // Encryption -------------------
        final CryptoCipher c = Utils.getCipherInstance(transformation, props);
        final Key key = new SecretKeySpec(keyBytes, "AES");
        final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
        c.init(Cipher.ENCRYPT_MODE, key, iv);

        bfAAD.flip();
        c.updateAAD(bfAAD);

        bfPlainText.put(plainText);
        bfPlainText.flip();
        bfCipherText.position(0);

        c.doFinal(bfPlainText, bfCipherText);

        bfCipherText.flip();
        bfCipherText.get(encOutput);
        Assert.assertArrayEquals(cipherText, encOutput);
        c.close();

        // Decryption -------------------
        final CryptoCipher dec = Utils.getCipherInstance(transformation, props);
        dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"),
                new GCMParameterSpec(128, ivBytes));
        bfAAD.flip();
        dec.updateAAD(bfAAD);
        bfCipherText.clear();
        bfPlainText.clear();
        bfCipherText.put(cipherText);
        bfCipherText.flip();
        dec.doFinal(bfCipherText, bfPlainText);
        bfPlainText.flip();
        bfPlainText.get(decOutput);
        Assert.assertArrayEquals(plainText, decOutput);
        dec.close();
    }

    private void initTestData() {

        final int casesNumber = 4;

        kHex = new String[casesNumber];
        pHex = new String[casesNumber];
        ivHex = new String[casesNumber];
        aadHex = new String[casesNumber];
        cHex = new String[casesNumber];
        tHex = new String[casesNumber];

        // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
        // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
        // NIST Case2  -----------------------------
        // key length:          16 bytes
        // plain text length:   16 bytes
        // iv length:           12 bytes
        // aad length:          0 bytes
        kHex[0] = "00000000000000000000000000000000";
        pHex[0] = "00000000000000000000000000000000";
        ivHex[0]  = "000000000000000000000000";
        aadHex[0]  = "";
        cHex[0]  = "0388dace60b6a392f328c2b971b2fe78";
        tHex[0]  = "ab6e47d42cec13bdf53a67b21257bddf";

        // NIST Case4 ---------------------------------
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           12 bytes
        // aad length:          20 bytes
        kHex[1] = "feffe9928665731c6d6a8f9467308308";
        pHex[1] = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";
        ivHex[1] = "cafebabefacedbaddecaf888";
        aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";
        cHex[1] = "42831ec2217774244b7221b784d0d49c"
                + "e3aa212f2c02a4e035c17e2329aca12e"
                + "21d514b25466931c7d8f6a5aac84aa05"
                + "1ba30b396a0aac973d58e091";
        tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47";

        // NIST Case5 ---------------------------------
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           8 bytes
        // aad length:          20 bytes
        kHex[2] = "feffe9928665731c6d6a8f9467308308";
        pHex[2] = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";
        ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits
        aadHex[2]="feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";
        cHex[2] = "61353b4c2806934a777ff51fa22a4755"
                + "699b2a714fcdc6f83766e5f97b6c7423"
                + "73806900e49f24b22b097544d4896b42"
                + "4989b5e1ebac0f07c23f4598";
        tHex[2] = "3612d2e79e3b0785561be14aaca2fccb";

        // NIST Case6 ---------------------------------
        // key length:          16 bytes
        // plain text length:   60 bytes
        // iv length:           60 bytes
        // aad length:          20 bytes
        kHex[3] = "feffe9928665731c6d6a8f9467308308";
        pHex[3] = "d9313225f88406e5a55909c5aff5269a"
                + "86a7a9531534f7da2e4c303d8a318a72"
                + "1c3c0c95956809532fcf0e2449a6b525"
                + "b16aedf5aa0de657ba637b39";
        ivHex[3] = "9313225df88406e555909c5aff5269aa"
                + "6a7a9538534f7da1e4c303d2a318a728"
                + "c3c0c95156809539fcf0e2429a6b5254"
                + "16aedbf5a0de6a57a637b39b"; // > 96bits
        aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef"
                + "abaddad2";
        cHex[3] = "8ce24998625615b603a033aca13fb894"
                + "be9112a5c3a211a8ba262a3cca7e2ca7"
                + "01e4a9a4fba43c90ccdcb281d48c7c6f"
                + "d62875d2aca417034c34aee5";
        tHex[3] = "619cc5aefffe0bfa462af43c1699d050";
    }
}