| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- //------------------------------------------------------------------------------
- // <copyright file="SqlAeadAes256CbcHmac256Algorithm.cs" company="Microsoft">
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
- // <owner current="true" primary="true">balnee</owner>
- // <owner current="true" primary="false">krishnib</owner>
- //------------------------------------------------------------------------------
- namespace System.Data.SqlClient
- {
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Data.SqlClient;
- using System.Diagnostics;
- using System.IO;
- using System.Runtime.CompilerServices;
- using System.Security.Cryptography;
- /// <summary>
- /// This class implements authenticated encryption algorithm with associated data as described in
- /// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05. More specifically this implements
- /// AEAD_AES_256_CBC_HMAC_SHA256 algorithm.
- /// </summary>
- internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
- {
- /// <summary>
- /// Algorithm Name
- /// </summary>
- internal const string AlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256";
- /// <summary>
- /// Key size in bytes
- /// </summary>
- private const int _KeySizeInBytes = SqlAeadAes256CbcHmac256EncryptionKey.KeySize / 8;
- /// <summary>
- /// Block size in bytes. AES uses 16 byte blocks.
- /// </summary>
- private const int _BlockSizeInBytes = 16;
- /// <summary>
- /// Minimum Length of cipherText without authentication tag. This value is 1 (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
- /// </summary>
- private const int _MinimumCipherTextLengthInBytesNoAuthenticationTag = sizeof(byte) + _BlockSizeInBytes + _BlockSizeInBytes;
- /// <summary>
- /// Minimum Length of cipherText. This value is 1 (version byte) + 32 (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
- /// </summary>
- private const int _MinimumCipherTextLengthInBytesWithAuthenticationTag = _MinimumCipherTextLengthInBytesNoAuthenticationTag + _KeySizeInBytes;
- /// <summary>
- /// Cipher Mode. For this algorithm, we only use CBC mode.
- /// </summary>
- private const CipherMode _cipherMode = CipherMode.CBC;
- /// <summary>
- /// Padding mode. This algorithm uses PKCS7.
- /// </summary>
- private const PaddingMode _paddingMode = PaddingMode.PKCS7;
- /// <summary>
- /// Variable indicating whether this algorithm should work in Deterministic mode or Randomized mode.
- /// For deterministic encryption, we derive an IV from the plaintext data.
- /// For randomized encryption, we generate a cryptographically random IV.
- /// </summary>
- private readonly bool _isDeterministic;
- /// <summary>
- /// Algorithm Version.
- /// </summary>
- private readonly byte _algorithmVersion;
- /// <summary>
- /// Column Encryption Key. This has a root key and three derived keys.
- /// </summary>
- private readonly SqlAeadAes256CbcHmac256EncryptionKey _columnEncryptionKey;
- /// <summary>
- /// The pool of crypto providers to use for encrypt/decrypt operations.
- /// </summary>
- private readonly ConcurrentQueue<AesCryptoServiceProvider> _cryptoProviderPool;
- /// <summary>
- /// Byte array with algorithm version used for authentication tag computation.
- /// </summary>
- private static readonly byte[] _version = new byte[] {0x01};
- /// <summary>
- /// Byte array with algorithm version size used for authentication tag computation.
- /// </summary>
- private static readonly byte[] _versionSize = new byte[] {sizeof(byte)};
- /// <summary>
- /// Initializes a new instance of SqlAeadAes256CbcHmac256Algorithm algorithm with a given key and encryption type
- /// </summary>
- /// <param name="encryptionKey">
- /// Root encryption key from which three other keys will be derived
- /// </param>
- /// <param name="encryptionType">Encryption Type, accepted values are Deterministic and Randomized.
- /// For Deterministic encryption, a synthetic IV will be genenrated during encryption
- /// For Randomized encryption, a random IV will be generated during encryption.
- /// </param>
- /// <param name="algorithmVersion">
- /// Algorithm version
- /// </param>
- internal SqlAeadAes256CbcHmac256Algorithm(SqlAeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion) {
- _columnEncryptionKey = encryptionKey;
- _algorithmVersion = algorithmVersion;
- _version[0] = algorithmVersion;
- Debug.Assert (null != encryptionKey, "Null encryption key detected in AeadAes256CbcHmac256 algorithm");
- Debug.Assert (0x01 == algorithmVersion, "Unknown algorithm version passed to AeadAes256CbcHmac256");
- // Validate encryption type for this algorithm
- // This algorithm can only provide randomized or deterministic encryption types.
- if (encryptionType == SqlClientEncryptionType.Deterministic) {
- _isDeterministic = true;
- }
- else {
- Debug.Assert (SqlClientEncryptionType.Randomized == encryptionType, "Invalid Encryption Type detected in SqlAeadAes256CbcHmac256Algorithm, this should've been caught in factory class");
- }
- _cryptoProviderPool = new ConcurrentQueue<AesCryptoServiceProvider>();
- }
- /// <summary>
- /// Encryption Algorithm
- /// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
- /// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
- /// cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
- /// cell_blob = versionbyte + cell_tag + cell_iv + cell_ciphertext
- /// </summary>
- /// <param name="plainText">Plaintext data to be encrypted</param>
- /// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
- internal override byte[] EncryptData(byte[] plainText) {
- return EncryptData(plainText, hasAuthenticationTag: true);
- }
- /// <summary>
- /// Encryption Algorithm
- /// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
- /// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
- /// (optional) cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
- /// cell_blob = versionbyte + [cell_tag] + cell_iv + cell_ciphertext
- /// </summary>
- /// <param name="plainText">Plaintext data to be encrypted</param>
- /// <param name="hasAuthenticationTag">Does the algorithm require authentication tag.</param>
- /// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
- protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag) {
- // Empty values get encrypted and decrypted properly for both Deterministic and Randomized encryptions.
- Debug.Assert(plainText != null);
- byte[] iv = new byte[_BlockSizeInBytes];
- // Prepare IV
- // Should be 1 single block (16 bytes)
- if (_isDeterministic) {
- SqlSecurityUtility.GetHMACWithSHA256(plainText, _columnEncryptionKey.IVKey, iv);
- }
- else {
- SqlSecurityUtility.GenerateRandomBytes(iv);
- }
- int numBlocks = plainText.Length / _BlockSizeInBytes + 1;
- // Final blob we return = version + HMAC + iv + cipherText
- const int hmacStartIndex = 1;
- int authenticationTagLen = hasAuthenticationTag ? _KeySizeInBytes : 0;
- int ivStartIndex = hmacStartIndex + authenticationTagLen;
- int cipherStartIndex = ivStartIndex + _BlockSizeInBytes; // this is where hmac starts.
- // Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
- int outputBufSize = sizeof(byte) + authenticationTagLen + iv.Length + (numBlocks*_BlockSizeInBytes);
- byte[] outBuffer = new byte[outputBufSize];
- // Store the version and IV rightaway
- outBuffer[0] = _algorithmVersion;
- Buffer.BlockCopy(iv, 0, outBuffer, ivStartIndex, iv.Length);
- AesCryptoServiceProvider aesAlg;
- // Try to get a provider from the pool.
- // If no provider is available, create a new one.
- if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
- aesAlg = new AesCryptoServiceProvider();
- try {
- // Set various algorithm properties
- aesAlg.Key = _columnEncryptionKey.EncryptionKey;
- aesAlg.Mode = _cipherMode;
- aesAlg.Padding = _paddingMode;
- }
- catch (Exception) {
- if (aesAlg != null) {
- aesAlg.Dispose();
- }
- throw;
- }
- }
- try {
- // Always set the IV since it changes from cell to cell.
- aesAlg.IV = iv;
- // Compute CipherText and authentication tag in a single pass
- using (ICryptoTransform encryptor = aesAlg.CreateEncryptor()) {
- Debug.Assert(encryptor.CanTransformMultipleBlocks, "AES Encryptor can transform multiple blocks");
- int count = 0;
- int cipherIndex = cipherStartIndex; // this is where cipherText starts
- if (numBlocks > 1) {
- count = (numBlocks - 1) * _BlockSizeInBytes;
- cipherIndex += encryptor.TransformBlock(plainText, 0, count, outBuffer, cipherIndex);
- }
- byte[] buffTmp = encryptor.TransformFinalBlock(plainText, count, plainText.Length - count); // done encrypting
- Buffer.BlockCopy(buffTmp, 0, outBuffer, cipherIndex, buffTmp.Length);
- cipherIndex += buffTmp.Length;
- }
- if (hasAuthenticationTag) {
- using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
- Debug.Assert(hmac.CanTransformMultipleBlocks, "HMAC can't transform multiple blocks");
- hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
- hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
- // Compute HMAC on final block
- hmac.TransformBlock(outBuffer, cipherStartIndex, numBlocks * _BlockSizeInBytes, outBuffer, cipherStartIndex);
- hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
- byte[] hash = hmac.Hash;
- Debug.Assert(hash.Length >= authenticationTagLen, "Unexpected hash size");
- Buffer.BlockCopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);
- }
- }
- }
- finally {
- // Return the provider to the pool.
- _cryptoProviderPool.Enqueue(aesAlg);
- }
- return outBuffer;
- }
- /// <summary>
- /// Decryption steps
- /// 1. Validate version byte
- /// 2. Validate Authentication tag
- /// 3. Decrypt the message
- /// </summary>
- /// <param name="cipherText"></param>
- /// <returns></returns>
- internal override byte[] DecryptData(byte[] cipherText) {
- return DecryptData(cipherText, hasAuthenticationTag: true);
- }
- /// <summary>
- /// Decryption steps
- /// 1. Validate version byte
- /// 2. (optional) Validate Authentication tag
- /// 3. Decrypt the message
- /// </summary>
- /// <param name="cipherText"></param>
- /// <param name="hasAuthenticationTag"></param>
- /// <returns></returns>
- protected byte[] DecryptData(byte[] cipherText, bool hasAuthenticationTag) {
- Debug.Assert(cipherText != null);
- byte[] iv = new byte[_BlockSizeInBytes];
- int minimumCipherTextLength = hasAuthenticationTag ? _MinimumCipherTextLengthInBytesWithAuthenticationTag : _MinimumCipherTextLengthInBytesNoAuthenticationTag;
- if (cipherText.Length < minimumCipherTextLength) {
- throw SQL.InvalidCipherTextSize(cipherText.Length, minimumCipherTextLength);
- }
- // Validate the version byte
- int startIndex = 0;
- if (cipherText[startIndex] != _algorithmVersion) {
- // Cipher text was computed with a different algorithm version than this.
- throw SQL.InvalidAlgorithmVersion(cipherText[startIndex], _algorithmVersion);
- }
- startIndex += 1;
- int authenticationTagOffset = 0;
- // Read authentication tag
- if (hasAuthenticationTag) {
- authenticationTagOffset = startIndex;
- startIndex += _KeySizeInBytes; // authentication tag size is _KeySizeInBytes
- }
- // Read cell IV
- Buffer.BlockCopy(cipherText, startIndex, iv, 0, iv.Length);
- startIndex += iv.Length;
- // Read encrypted text
- int cipherTextOffset = startIndex;
- int cipherTextCount = cipherText.Length - startIndex;
- if (hasAuthenticationTag) {
- // Compute authentication tag
- byte[] authenticationTag = PrepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount);
- if (!SqlSecurityUtility.CompareBytes(authenticationTag, cipherText, authenticationTagOffset, authenticationTag.Length)) {
- // Potentially tampered data, throw an exception
- throw SQL.InvalidAuthenticationTag();
- }
- }
- // Decrypt the text and return
- return DecryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
- }
- /// <summary>
- /// Decrypts plain text data using AES in CBC mode
- /// </summary>
- /// <param name="plainText"> cipher text data to be decrypted</param>
- /// <param name="iv">IV to be used for decryption</param>
- /// <returns>Returns decrypted plain text data</returns>
- private byte[] DecryptData(byte[] iv, byte[] cipherText, int offset, int count) {
- Debug.Assert((iv != null) && (cipherText != null));
- Debug.Assert (offset > -1 && count > -1);
- Debug.Assert ((count+offset) <= cipherText.Length);
- byte[] plainText;
- AesCryptoServiceProvider aesAlg;
- // Try to get a provider from the pool.
- // If no provider is available, create a new one.
- if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
- aesAlg = new AesCryptoServiceProvider();
- try {
- // Set various algorithm properties
- aesAlg.Key = _columnEncryptionKey.EncryptionKey;
- aesAlg.Mode = _cipherMode;
- aesAlg.Padding = _paddingMode;
- }
- catch (Exception) {
- if (aesAlg != null) {
- aesAlg.Dispose();
- }
- throw;
- }
- }
- try {
- // Always set the IV since it changes from cell to cell.
- aesAlg.IV = iv;
- // Create the streams used for decryption.
- using (MemoryStream msDecrypt = new MemoryStream()) {
- // Create an encryptor to perform the stream transform.
- using (ICryptoTransform decryptor = aesAlg.CreateDecryptor()) {
- using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write)) {
- // Decrypt the secret message and get the plain text data
- csDecrypt.Write(cipherText, offset, count);
- csDecrypt.FlushFinalBlock();
- plainText = msDecrypt.ToArray();
- }
- }
- }
- }
- finally {
- // Return the provider to the pool.
- _cryptoProviderPool.Enqueue(aesAlg);
- }
- return plainText;
- }
- /// <summary>
- /// Prepares an authentication tag.
- /// Authentication Tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
- /// </summary>
- /// <param name="cipherText"></param>
- /// <returns></returns>
- private byte[] PrepareAuthenticationTag(byte[] iv, byte[] cipherText, int offset, int length) {
- Debug.Assert(cipherText != null);
- byte[] computedHash;
- byte[] authenticationTag = new byte[_KeySizeInBytes];
- // Raw Tag Length:
- // 1 for the version byte
- // 1 block for IV (16 bytes)
- // cipherText.Length
- // 1 byte for version byte length
- using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
- int retVal = 0;
- retVal = hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
- Debug.Assert(retVal == _version.Length);
- retVal = hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
- Debug.Assert(retVal == iv.Length);
- retVal = hmac.TransformBlock(cipherText, offset, length, cipherText, offset);
- Debug.Assert(retVal == length);
- hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
- computedHash = hmac.Hash;
- }
- Debug.Assert (computedHash.Length >= authenticationTag.Length);
- Buffer.BlockCopy (computedHash, 0, authenticationTag, 0, authenticationTag.Length);
- return authenticationTag;
- }
- }
- }
|