/// <summary>
        /// Decrypt in aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
        /// </summary>
        /// <param name="key">key data</param>
        /// <param name="cipher">cipher data to be decrypted</param>
        /// <param name="usage">key usage number</param>
        /// <param name="aesKeyType">aes key type (128bit/256bit)</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>the decrypted data</returns>
        public static byte[] Decrypt(
            byte[] key,
            byte[] cipher,
            int usage,
            AesKeyType aesKeyType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == cipher)
            {
                throw new ArgumentNullException("cipher");
            }

            // the cipher has two parts: encrypted(confounder + plain) + hmac
            byte[] encryptedData = ArrayUtility.SubArray <byte>(
                cipher, 0, cipher.Length - ConstValue.HMAC_HASH_OUTPUT_SIZE);
            byte[] hmacData = ArrayUtility.SubArray <byte>(
                cipher, cipher.Length - ConstValue.HMAC_HASH_OUTPUT_SIZE);

            // use ke key (the encryption key) to decrypt
            byte[] ke            = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ke, aesKeyType);
            byte[] initialVector = new byte[ConstValue.AES_BLOCK_SIZE];
            CipherTextStealingMode aesCtsCrypto = CryptoUtility.CreateAesCtsCrypto(ke, initialVector);

            byte[] decryptedData = aesCtsCrypto.DecryptFinal(encryptedData, 0, encryptedData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(decryptedData);
            }
            else
            {
                toBeSignedData = decryptedData;
            }

            // use ki key (the integrity key) to verify hmac data
            byte[] ki = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ki, aesKeyType);
            byte[] expectedHmacData = CryptoUtility.ComputeHmacSha1(ki, toBeSignedData);
            expectedHmacData = ArrayUtility.SubArray <byte>(expectedHmacData, 0, ConstValue.HMAC_HASH_OUTPUT_SIZE);
            if (!ArrayUtility.CompareArrays <byte>(hmacData, expectedHmacData))
            {
                throw new FormatException(
                          "Decryption: failed integrity check in hmac checksum.");
            }

            // remove confounder data
            decryptedData = ArrayUtility.SubArray <byte>(decryptedData, ConstValue.AES_BLOCK_SIZE,
                                                         decryptedData.Length - ConstValue.AES_BLOCK_SIZE);
            return(decryptedData);
        }
        /// <summary>
        /// RC4-HMAC / RC4-HMAC-EXP decryption
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="cipher">the encrypted cipher data</param>
        /// <param name="usage">key usage number</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>the decrypted data</returns>
        public static byte[] Decrypt(
            byte[] key,
            byte[] cipher,
            int usage,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == cipher)
            {
                throw new ArgumentNullException("cipher");
            }

            // get checksum and encrypted data
            byte[] checksum      = ArrayUtility.SubArray(cipher, 0, ConstValue.MD5_CHECKSUM_SIZE);
            byte[] encryptedData = ArrayUtility.SubArray(cipher, ConstValue.MD5_CHECKSUM_SIZE);

            // get salt and hash
            byte[] salt    = GetSalt(usage, encryptionType);
            byte[] hash    = CryptoUtility.ComputeMd5Hmac(key, salt);
            byte[] hashExp = GetExpHash(hash, encryptionType);

            // get rc4 decryption key
            byte[] rc4Key = CryptoUtility.ComputeMd5Hmac(hashExp, checksum);

            // decrypt
            ICryptoTransform decryptor = CryptoUtility.CreateRc4Decryptor(rc4Key);

            byte[] plain = decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plain);
            }
            else
            {
                toBeSignedData = plain;
            }

            // verify checksum
            byte[] expectedChecksum = CryptoUtility.ComputeMd5Hmac(hash, toBeSignedData);
            if (!ArrayUtility.CompareArrays(checksum, expectedChecksum))
            {
                throw new FormatException("Decryption: invalid checksum.");
            }

            // remove confounder
            return(ArrayUtility.SubArray(plain, ConstValue.CONFOUNDER_SIZE));
        }
        /// <summary>
        /// Decrypt in aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
        /// </summary>
        /// <param name="key">key data</param>
        /// <param name="cipher">cipher data to be decrypted</param>
        /// <param name="usage">key usage number</param>
        /// <param name="aesKeyType">aes key type (128bit/256bit)</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data. 
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>the decrypted data</returns>
        public static byte[] Decrypt(
            byte[] key,
            byte[] cipher,
            int usage,
            AesKeyType aesKeyType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == cipher)
            {
                throw new ArgumentNullException("cipher");
            }

            // the cipher has two parts: encrypted(confounder + plain) + hmac
            byte[] encryptedData = ArrayUtility.SubArray<byte>(
                cipher, 0, cipher.Length - ConstValue.HMAC_HASH_OUTPUT_SIZE);
            byte[] hmacData = ArrayUtility.SubArray<byte>(
                cipher, cipher.Length - ConstValue.HMAC_HASH_OUTPUT_SIZE);

            // use ke key (the encryption key) to decrypt
            byte[] ke = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ke, aesKeyType);
            byte[] initialVector = new byte[ConstValue.AES_BLOCK_SIZE];
            CipherTextStealingMode aesCtsCrypto = CryptoUtility.CreateAesCtsCrypto(ke, initialVector);
            byte[] decryptedData = aesCtsCrypto.DecryptFinal(encryptedData, 0, encryptedData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(decryptedData);
            }
            else
            {
                toBeSignedData = decryptedData;
            }

            // use ki key (the integrity key) to verify hmac data
            byte[] ki = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ki, aesKeyType);
            byte[] expectedHmacData = CryptoUtility.ComputeHmacSha1(ki, toBeSignedData);
            expectedHmacData = ArrayUtility.SubArray<byte>(expectedHmacData, 0, ConstValue.HMAC_HASH_OUTPUT_SIZE);
            if (!ArrayUtility.CompareArrays<byte>(hmacData, expectedHmacData))
            {
                throw new FormatException(
                    "Decryption: failed integrity check in hmac checksum.");
            }

            // remove confounder data
            decryptedData = ArrayUtility.SubArray<byte>(decryptedData, ConstValue.AES_BLOCK_SIZE,
                decryptedData.Length - ConstValue.AES_BLOCK_SIZE);
            return decryptedData;
        }
        /// <summary>
        /// RC4-HMAC / RC4-HMAC-EXP decryption
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="cipher">the encrypted cipher data</param>
        /// <param name="usage">key usage number</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data. 
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>the decrypted data</returns>
        public static byte[] Decrypt(
            byte[] key,
            byte[] cipher,
            int usage,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == cipher)
            {
                throw new ArgumentNullException("cipher");
            }

            // get checksum and encrypted data
            byte[] checksum = ArrayUtility.SubArray(cipher, 0, ConstValue.MD5_CHECKSUM_SIZE);
            byte[] encryptedData = ArrayUtility.SubArray(cipher, ConstValue.MD5_CHECKSUM_SIZE);

            // get salt and hash
            byte[] salt = GetSalt(usage, encryptionType);
            byte[] hash = CryptoUtility.ComputeMd5Hmac(key, salt);
            byte[] hashExp = GetExpHash(hash, encryptionType);

            // get rc4 decryption key
            byte[] rc4Key = CryptoUtility.ComputeMd5Hmac(hashExp, checksum);

            // decrypt
            ICryptoTransform decryptor = CryptoUtility.CreateRc4Decryptor(rc4Key);
            byte[] plain = decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plain);
            }
            else
            {
                toBeSignedData = plain;
            }

            // verify checksum
            byte[] expectedChecksum = CryptoUtility.ComputeMd5Hmac(hash, toBeSignedData);
            if (!ArrayUtility.CompareArrays(checksum, expectedChecksum))
            {
                throw new FormatException("Decryption: invalid checksum.");
            }

            // remove confounder
            return ArrayUtility.SubArray(plain, ConstValue.CONFOUNDER_SIZE);
        }
        /// <summary>
        /// RC4-HMAC / RC4-HMAC-EXP encryption
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="plain">the to be encrypted plain data</param>
        /// <param name="usage">key usage number</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use plain-text data directly if this parameter is null.
        /// </param>
        /// <returns>the encrypted data</returns>
        public static byte[] Encrypt(
            byte[] key,
            byte[] plain,
            int usage,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == plain)
            {
                throw new ArgumentNullException("plain");
            }

            // add confounder
            byte[] confounderData = CryptoUtility.CreateConfounder(ConstValue.CONFOUNDER_SIZE);
            byte[] plainData      = ArrayUtility.ConcatenateArrays(confounderData, plain);

            // add padding
            plainData = CryptoUtility.AddPadding(plainData, ConstValue.RC4_BLOCK_SIZE);

            // get salt and hash
            byte[] salt    = GetSalt(usage, encryptionType);
            byte[] hash    = CryptoUtility.ComputeMd5Hmac(key, salt);
            byte[] hashExp = GetExpHash(hash, encryptionType);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plainData);
            }
            else
            {
                toBeSignedData = plainData;
            }

            // get checksum
            byte[] checksum = CryptoUtility.ComputeMd5Hmac(hash, toBeSignedData);

            // get rc4 encryption key
            byte[] rc4Key = CryptoUtility.ComputeMd5Hmac(hashExp, checksum);

            // encrypt
            ICryptoTransform encryptor = CryptoUtility.CreateRc4Encryptor(rc4Key);

            byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);

            // result = checksum + encryptedData
            return(ArrayUtility.ConcatenateArrays(checksum, encryptedData));
        }
        /// <summary>
        /// DES-CBC-MD5 / DES-CBC-CRC32 decryption
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="cipher">the encrypted cipher data</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>the decrypted data</returns>
        public static byte[] Decrypt(
            byte[] key,
            byte[] cipher,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == cipher)
            {
                throw new ArgumentNullException("cipher");
            }

            // des-cbc decryption
            byte[]           initialVector = GetInitialVector(key, encryptionType);
            ICryptoTransform decryptor     = CryptoUtility.CreateDesCbcDecryptor(key, initialVector, PaddingMode.None);

            byte[] decryptedData = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);

            // get checksum, set checksum field to zeros
            int checksumSize = GetChecksumSize(encryptionType);

            byte[] checksum = ArrayUtility.SubArray <byte>(decryptedData,
                                                           ConstValue.DES_BLOCK_SIZE, checksumSize);
            Array.Clear(decryptedData, ConstValue.DES_BLOCK_SIZE, checksumSize);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(decryptedData);
            }
            else
            {
                toBeSignedData = decryptedData;
            }

            // verify checksum
            byte[] expectedChecksum = CalculateChecksum(toBeSignedData, encryptionType);
            if (!ArrayUtility.CompareArrays <byte>(checksum, expectedChecksum))
            {
                throw new FormatException(
                          "Decryption: inconsistent checksum.");
            }

            // remove confounder and checksum
            decryptedData = ArrayUtility.SubArray <byte>(decryptedData, ConstValue.DES_BLOCK_SIZE + checksumSize);

            return(decryptedData);
        }
        /// <summary>
        /// DES-CBC-MD5 / DES-CBC-CRC32 encryption
        /// [RFC 3961, Section 6.2, DES-Based Encryption and Checksum Types]
        ///  "One generates a random confounder of one block, placing it in 'confounder';
        ///  zeros out the 'checksum' field (of length appropriate to exactly hold the checksum
        ///  to be computed); adds the necessary padding; calculates the appropriate checksum
        ///  over the whole sequence, placing the result in 'checksum'; and then encrypts
        ///  using the specified encryption type and the appropriate key."
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="plain">the to be encrypted plain data</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use plain-text data directly if this parameter is null.
        /// </param>
        /// <returns>the encrypted data</returns>
        public static byte[] Encrypt(
            byte[] key,
            byte[] plain,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == plain)
            {
                throw new ArgumentNullException("plain");
            }

            // toBePaddedData = confounderData + emptyChecksumData + plainData
            int checksumSize = GetChecksumSize(encryptionType);

            byte[] toBeEncryptedData = ArrayUtility.ConcatenateArrays(
                CryptoUtility.CreateConfounder(ConstValue.DES_BLOCK_SIZE),
                new byte[checksumSize],
                plain);

            // add padding
            byte[] paddedData = CryptoUtility.AddPadding(toBeEncryptedData, ConstValue.DES_BLOCK_SIZE);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(paddedData);
            }
            else
            {
                toBeSignedData = paddedData;
            }

            // replace the empty checksum with the caculated checksum
            byte[] checksum = CalculateChecksum(toBeSignedData, encryptionType);
            Array.Copy(checksum, 0, paddedData, ConstValue.DES_BLOCK_SIZE, checksum.Length);

            // des-cbc encryption
            byte[]           initialVector = GetInitialVector(key, encryptionType);
            ICryptoTransform encryptor     = CryptoUtility.CreateDesCbcEncryptor(key, initialVector, PaddingMode.None);

            return(encryptor.TransformFinalBlock(paddedData, 0, paddedData.Length));
        }
        /// <summary>
        /// Encrypt in aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
        /// </summary>
        /// <param name="key">key data</param>
        /// <param name="plain">plain data to be encrypted</param>
        /// <param name="usage">key usage number</param>
        /// <param name="aesKeyType">aes key type (128bit/256bit)</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use plain-text data directly if this parameter is null.
        /// </param>
        /// <returns>the encrypted data</returns>
        public static byte[] Encrypt(
            byte[] key,
            byte[] plain,
            int usage,
            AesKeyType aesKeyType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == plain)
            {
                throw new ArgumentNullException("plain");
            }

            // add confounder data
            byte[] plainData = ArrayUtility.ConcatenateArrays(
                CryptoUtility.CreateConfounder(ConstValue.AES_BLOCK_SIZE),
                plain);

            // use ke key (the encryption key) to encrypt the plain data
            byte[] ke            = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ke, aesKeyType);
            byte[] initialVector = new byte[ConstValue.AES_BLOCK_SIZE];
            CipherTextStealingMode aesCtsCrypto = CryptoUtility.CreateAesCtsCrypto(ke, initialVector);

            byte[] encryptedData = aesCtsCrypto.EncryptFinal(plainData, 0, plainData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plainData);
            }
            else
            {
                toBeSignedData = plainData;
            }

            // use ki key (the integrity key) to generate checksum
            byte[] ki       = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ki, aesKeyType);
            byte[] hmacData = CryptoUtility.ComputeHmacSha1(ki, toBeSignedData);
            hmacData = ArrayUtility.SubArray <byte>(hmacData, 0, ConstValue.HMAC_HASH_OUTPUT_SIZE);

            // result: encryptedData + hmacData
            return(ArrayUtility.ConcatenateArrays(encryptedData, hmacData));
        }
        /// <summary>
        /// RC4-HMAC / RC4-HMAC-EXP encryption
        /// </summary>
        /// <param name="key">the secret key</param>
        /// <param name="plain">the to be encrypted plain data</param>
        /// <param name="usage">key usage number</param>
        /// <param name="encryptionType">encryption type</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data. 
        /// The method will use plain-text data directly if this parameter is null.
        /// </param>
        /// <returns>the encrypted data</returns>
        public static byte[] Encrypt(
            byte[] key,
            byte[] plain,
            int usage,
            EncryptionType encryptionType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == plain)
            {
                throw new ArgumentNullException("plain");
            }

            // add confounder
            byte[] confounderData = CryptoUtility.CreateConfounder(ConstValue.CONFOUNDER_SIZE);
            byte[] plainData = ArrayUtility.ConcatenateArrays(confounderData, plain);

            // add padding
            plainData = CryptoUtility.AddPadding(plainData, ConstValue.RC4_BLOCK_SIZE);

            // get salt and hash
            byte[] salt = GetSalt(usage, encryptionType);
            byte[] hash = CryptoUtility.ComputeMd5Hmac(key, salt);
            byte[] hashExp = GetExpHash(hash, encryptionType);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plainData);
            }
            else
            {
                toBeSignedData = plainData;
            }

            // get checksum
            byte[] checksum = CryptoUtility.ComputeMd5Hmac(hash, toBeSignedData);

            // get rc4 encryption key
            byte[] rc4Key = CryptoUtility.ComputeMd5Hmac(hashExp, checksum);

            // encrypt
            ICryptoTransform encryptor = CryptoUtility.CreateRc4Encryptor(rc4Key);
            byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);

            // result = checksum + encryptedData
            return ArrayUtility.ConcatenateArrays(checksum, encryptedData);
        }
        /// <summary>
        /// Decrypt the cipher text with the session key. The usage indicated
        /// in section 7 of RFC4210 and section 3 of RFC4757 is used to derive key
        /// from the session key.
        /// </summary>
        /// <param name="type">The decryption type selected.</param>
        /// <param name="sessionKey">An session key used to decrypt and it can be obtained
        /// from the KDC's response. This key size should be equal to the symmetric algorithm
        /// key size. This argument can be null. If it is null, null will be returned.</param>
        /// <param name="cipherData">The text to be decrypted. This argument can be null.
        /// If it is null, null will be returned.</param>
        /// <param name="usage">A 32 bits integer used to derive the key.</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data.
        /// The method will use decrypted data directly if this parameter is null.
        /// </param>
        /// <returns>The plain text.</returns>
        internal static byte[] Decrypt(EncryptionType type, byte[] sessionKey, byte[] cipherData, int usage, GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            switch (type)
            {
            case EncryptionType.AES128_CTS_HMAC_SHA1_96:
                return(AesCtsHmacSha1Crypto.Decrypt(sessionKey, cipherData, usage, AesKeyType.Aes128BitsKey, getToBeSignedDateCallback));

            case EncryptionType.AES256_CTS_HMAC_SHA1_96:
                return(AesCtsHmacSha1Crypto.Decrypt(sessionKey, cipherData, usage, AesKeyType.Aes256BitsKey, getToBeSignedDateCallback));

            case EncryptionType.DES_CBC_CRC:
                return(DesCbcCrypto.Decrypt(sessionKey, cipherData, EncryptionType.DES_CBC_CRC, getToBeSignedDateCallback));

            case EncryptionType.DES_CBC_MD5:
                return(DesCbcCrypto.Decrypt(sessionKey, cipherData, EncryptionType.DES_CBC_MD5, getToBeSignedDateCallback));

            case EncryptionType.RC4_HMAC:
                return(Rc4HmacCrypto.Decrypt(sessionKey, cipherData, usage, EncryptionType.RC4_HMAC, getToBeSignedDateCallback));

            case EncryptionType.RC4_HMAC_EXP:
                return(Rc4HmacCrypto.Decrypt(sessionKey, cipherData, usage, EncryptionType.RC4_HMAC_EXP, getToBeSignedDateCallback));

            default:
                throw new ArgumentException("Unsupported encryption type.");
            }
        }
        /// <summary>
        /// Encrypt in aes128-cts-hmac-sha1-96 or aes256-cts-hmac-sha1-96
        /// </summary>
        /// <param name="key">key data</param>
        /// <param name="plain">plain data to be encrypted</param>
        /// <param name="usage">key usage number</param>
        /// <param name="aesKeyType">aes key type (128bit/256bit)</param>
        /// <param name="getToBeSignedDateCallback">
        /// A callback to get to-be-signed data. 
        /// The method will use plain-text data directly if this parameter is null.
        /// </param>
        /// <returns>the encrypted data</returns>
        public static byte[] Encrypt(
            byte[] key,
            byte[] plain,
            int usage,
            AesKeyType aesKeyType,
            GetToBeSignedDataFunc getToBeSignedDateCallback)
        {
            // check inputs
            if (null == key)
            {
                throw new ArgumentNullException("key");
            }
            if (null == plain)
            {
                throw new ArgumentNullException("plain");
            }

            // add confounder data
            byte[] plainData = ArrayUtility.ConcatenateArrays(
                CryptoUtility.CreateConfounder(ConstValue.AES_BLOCK_SIZE),
                plain);

            // use ke key (the encryption key) to encrypt the plain data
            byte[] ke = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ke, aesKeyType);
            byte[] initialVector = new byte[ConstValue.AES_BLOCK_SIZE];
            CipherTextStealingMode aesCtsCrypto = CryptoUtility.CreateAesCtsCrypto(ke, initialVector);
            byte[] encryptedData = aesCtsCrypto.EncryptFinal(plainData, 0, plainData.Length);

            byte[] toBeSignedData;
            if (getToBeSignedDateCallback != null)
            {
                toBeSignedData = getToBeSignedDateCallback(plainData);
            }
            else
            {
                toBeSignedData = plainData;
            }

            // use ki key (the integrity key) to generate checksum
            byte[] ki = AesKey.MakeDerivedKey(key, usage, DerivedKeyType.Ki, aesKeyType);
            byte[] hmacData = CryptoUtility.ComputeHmacSha1(ki, toBeSignedData);
            hmacData = ArrayUtility.SubArray<byte>(hmacData, 0, ConstValue.HMAC_HASH_OUTPUT_SIZE);

            // result: encryptedData + hmacData
            return ArrayUtility.ConcatenateArrays(encryptedData, hmacData);
        }