SqlAeadAes256CbcHmac256Algorithm.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. //------------------------------------------------------------------------------
  2. // <copyright file="SqlAeadAes256CbcHmac256Algorithm.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">balnee</owner>
  6. // <owner current="true" primary="false">krishnib</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.SqlClient
  9. {
  10. using System;
  11. using System.Collections.Concurrent;
  12. using System.Collections.Generic;
  13. using System.Data.SqlClient;
  14. using System.Diagnostics;
  15. using System.IO;
  16. using System.Runtime.CompilerServices;
  17. using System.Security.Cryptography;
  18. /// <summary>
  19. /// This class implements authenticated encryption algorithm with associated data as described in
  20. /// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05. More specifically this implements
  21. /// AEAD_AES_256_CBC_HMAC_SHA256 algorithm.
  22. /// </summary>
  23. internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm
  24. {
  25. /// <summary>
  26. /// Algorithm Name
  27. /// </summary>
  28. internal const string AlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256";
  29. /// <summary>
  30. /// Key size in bytes
  31. /// </summary>
  32. private const int _KeySizeInBytes = SqlAeadAes256CbcHmac256EncryptionKey.KeySize / 8;
  33. /// <summary>
  34. /// Block size in bytes. AES uses 16 byte blocks.
  35. /// </summary>
  36. private const int _BlockSizeInBytes = 16;
  37. /// <summary>
  38. /// Minimum Length of cipherText without authentication tag. This value is 1 (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
  39. /// </summary>
  40. private const int _MinimumCipherTextLengthInBytesNoAuthenticationTag = sizeof(byte) + _BlockSizeInBytes + _BlockSizeInBytes;
  41. /// <summary>
  42. /// Minimum Length of cipherText. This value is 1 (version byte) + 32 (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
  43. /// </summary>
  44. private const int _MinimumCipherTextLengthInBytesWithAuthenticationTag = _MinimumCipherTextLengthInBytesNoAuthenticationTag + _KeySizeInBytes;
  45. /// <summary>
  46. /// Cipher Mode. For this algorithm, we only use CBC mode.
  47. /// </summary>
  48. private const CipherMode _cipherMode = CipherMode.CBC;
  49. /// <summary>
  50. /// Padding mode. This algorithm uses PKCS7.
  51. /// </summary>
  52. private const PaddingMode _paddingMode = PaddingMode.PKCS7;
  53. /// <summary>
  54. /// Variable indicating whether this algorithm should work in Deterministic mode or Randomized mode.
  55. /// For deterministic encryption, we derive an IV from the plaintext data.
  56. /// For randomized encryption, we generate a cryptographically random IV.
  57. /// </summary>
  58. private readonly bool _isDeterministic;
  59. /// <summary>
  60. /// Algorithm Version.
  61. /// </summary>
  62. private readonly byte _algorithmVersion;
  63. /// <summary>
  64. /// Column Encryption Key. This has a root key and three derived keys.
  65. /// </summary>
  66. private readonly SqlAeadAes256CbcHmac256EncryptionKey _columnEncryptionKey;
  67. /// <summary>
  68. /// The pool of crypto providers to use for encrypt/decrypt operations.
  69. /// </summary>
  70. private readonly ConcurrentQueue<AesCryptoServiceProvider> _cryptoProviderPool;
  71. /// <summary>
  72. /// Byte array with algorithm version used for authentication tag computation.
  73. /// </summary>
  74. private static readonly byte[] _version = new byte[] {0x01};
  75. /// <summary>
  76. /// Byte array with algorithm version size used for authentication tag computation.
  77. /// </summary>
  78. private static readonly byte[] _versionSize = new byte[] {sizeof(byte)};
  79. /// <summary>
  80. /// Initializes a new instance of SqlAeadAes256CbcHmac256Algorithm algorithm with a given key and encryption type
  81. /// </summary>
  82. /// <param name="encryptionKey">
  83. /// Root encryption key from which three other keys will be derived
  84. /// </param>
  85. /// <param name="encryptionType">Encryption Type, accepted values are Deterministic and Randomized.
  86. /// For Deterministic encryption, a synthetic IV will be genenrated during encryption
  87. /// For Randomized encryption, a random IV will be generated during encryption.
  88. /// </param>
  89. /// <param name="algorithmVersion">
  90. /// Algorithm version
  91. /// </param>
  92. internal SqlAeadAes256CbcHmac256Algorithm(SqlAeadAes256CbcHmac256EncryptionKey encryptionKey, SqlClientEncryptionType encryptionType, byte algorithmVersion) {
  93. _columnEncryptionKey = encryptionKey;
  94. _algorithmVersion = algorithmVersion;
  95. _version[0] = algorithmVersion;
  96. Debug.Assert (null != encryptionKey, "Null encryption key detected in AeadAes256CbcHmac256 algorithm");
  97. Debug.Assert (0x01 == algorithmVersion, "Unknown algorithm version passed to AeadAes256CbcHmac256");
  98. // Validate encryption type for this algorithm
  99. // This algorithm can only provide randomized or deterministic encryption types.
  100. if (encryptionType == SqlClientEncryptionType.Deterministic) {
  101. _isDeterministic = true;
  102. }
  103. else {
  104. Debug.Assert (SqlClientEncryptionType.Randomized == encryptionType, "Invalid Encryption Type detected in SqlAeadAes256CbcHmac256Algorithm, this should've been caught in factory class");
  105. }
  106. _cryptoProviderPool = new ConcurrentQueue<AesCryptoServiceProvider>();
  107. }
  108. /// <summary>
  109. /// Encryption Algorithm
  110. /// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
  111. /// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
  112. /// cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
  113. /// cell_blob = versionbyte + cell_tag + cell_iv + cell_ciphertext
  114. /// </summary>
  115. /// <param name="plainText">Plaintext data to be encrypted</param>
  116. /// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
  117. internal override byte[] EncryptData(byte[] plainText) {
  118. return EncryptData(plainText, hasAuthenticationTag: true);
  119. }
  120. /// <summary>
  121. /// Encryption Algorithm
  122. /// cell_iv = HMAC_SHA-2-256(iv_key, cell_data) truncated to 128 bits
  123. /// cell_ciphertext = AES-CBC-256(enc_key, cell_iv, cell_data) with PKCS7 padding.
  124. /// (optional) cell_tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
  125. /// cell_blob = versionbyte + [cell_tag] + cell_iv + cell_ciphertext
  126. /// </summary>
  127. /// <param name="plainText">Plaintext data to be encrypted</param>
  128. /// <param name="hasAuthenticationTag">Does the algorithm require authentication tag.</param>
  129. /// <returns>Returns the ciphertext corresponding to the plaintext.</returns>
  130. protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag) {
  131. // Empty values get encrypted and decrypted properly for both Deterministic and Randomized encryptions.
  132. Debug.Assert(plainText != null);
  133. byte[] iv = new byte[_BlockSizeInBytes];
  134. // Prepare IV
  135. // Should be 1 single block (16 bytes)
  136. if (_isDeterministic) {
  137. SqlSecurityUtility.GetHMACWithSHA256(plainText, _columnEncryptionKey.IVKey, iv);
  138. }
  139. else {
  140. SqlSecurityUtility.GenerateRandomBytes(iv);
  141. }
  142. int numBlocks = plainText.Length / _BlockSizeInBytes + 1;
  143. // Final blob we return = version + HMAC + iv + cipherText
  144. const int hmacStartIndex = 1;
  145. int authenticationTagLen = hasAuthenticationTag ? _KeySizeInBytes : 0;
  146. int ivStartIndex = hmacStartIndex + authenticationTagLen;
  147. int cipherStartIndex = ivStartIndex + _BlockSizeInBytes; // this is where hmac starts.
  148. // Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
  149. int outputBufSize = sizeof(byte) + authenticationTagLen + iv.Length + (numBlocks*_BlockSizeInBytes);
  150. byte[] outBuffer = new byte[outputBufSize];
  151. // Store the version and IV rightaway
  152. outBuffer[0] = _algorithmVersion;
  153. Buffer.BlockCopy(iv, 0, outBuffer, ivStartIndex, iv.Length);
  154. AesCryptoServiceProvider aesAlg;
  155. // Try to get a provider from the pool.
  156. // If no provider is available, create a new one.
  157. if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
  158. aesAlg = new AesCryptoServiceProvider();
  159. try {
  160. // Set various algorithm properties
  161. aesAlg.Key = _columnEncryptionKey.EncryptionKey;
  162. aesAlg.Mode = _cipherMode;
  163. aesAlg.Padding = _paddingMode;
  164. }
  165. catch (Exception) {
  166. if (aesAlg != null) {
  167. aesAlg.Dispose();
  168. }
  169. throw;
  170. }
  171. }
  172. try {
  173. // Always set the IV since it changes from cell to cell.
  174. aesAlg.IV = iv;
  175. // Compute CipherText and authentication tag in a single pass
  176. using (ICryptoTransform encryptor = aesAlg.CreateEncryptor()) {
  177. Debug.Assert(encryptor.CanTransformMultipleBlocks, "AES Encryptor can transform multiple blocks");
  178. int count = 0;
  179. int cipherIndex = cipherStartIndex; // this is where cipherText starts
  180. if (numBlocks > 1) {
  181. count = (numBlocks - 1) * _BlockSizeInBytes;
  182. cipherIndex += encryptor.TransformBlock(plainText, 0, count, outBuffer, cipherIndex);
  183. }
  184. byte[] buffTmp = encryptor.TransformFinalBlock(plainText, count, plainText.Length - count); // done encrypting
  185. Buffer.BlockCopy(buffTmp, 0, outBuffer, cipherIndex, buffTmp.Length);
  186. cipherIndex += buffTmp.Length;
  187. }
  188. if (hasAuthenticationTag) {
  189. using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
  190. Debug.Assert(hmac.CanTransformMultipleBlocks, "HMAC can't transform multiple blocks");
  191. hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
  192. hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
  193. // Compute HMAC on final block
  194. hmac.TransformBlock(outBuffer, cipherStartIndex, numBlocks * _BlockSizeInBytes, outBuffer, cipherStartIndex);
  195. hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
  196. byte[] hash = hmac.Hash;
  197. Debug.Assert(hash.Length >= authenticationTagLen, "Unexpected hash size");
  198. Buffer.BlockCopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);
  199. }
  200. }
  201. }
  202. finally {
  203. // Return the provider to the pool.
  204. _cryptoProviderPool.Enqueue(aesAlg);
  205. }
  206. return outBuffer;
  207. }
  208. /// <summary>
  209. /// Decryption steps
  210. /// 1. Validate version byte
  211. /// 2. Validate Authentication tag
  212. /// 3. Decrypt the message
  213. /// </summary>
  214. /// <param name="cipherText"></param>
  215. /// <returns></returns>
  216. internal override byte[] DecryptData(byte[] cipherText) {
  217. return DecryptData(cipherText, hasAuthenticationTag: true);
  218. }
  219. /// <summary>
  220. /// Decryption steps
  221. /// 1. Validate version byte
  222. /// 2. (optional) Validate Authentication tag
  223. /// 3. Decrypt the message
  224. /// </summary>
  225. /// <param name="cipherText"></param>
  226. /// <param name="hasAuthenticationTag"></param>
  227. /// <returns></returns>
  228. protected byte[] DecryptData(byte[] cipherText, bool hasAuthenticationTag) {
  229. Debug.Assert(cipherText != null);
  230. byte[] iv = new byte[_BlockSizeInBytes];
  231. int minimumCipherTextLength = hasAuthenticationTag ? _MinimumCipherTextLengthInBytesWithAuthenticationTag : _MinimumCipherTextLengthInBytesNoAuthenticationTag;
  232. if (cipherText.Length < minimumCipherTextLength) {
  233. throw SQL.InvalidCipherTextSize(cipherText.Length, minimumCipherTextLength);
  234. }
  235. // Validate the version byte
  236. int startIndex = 0;
  237. if (cipherText[startIndex] != _algorithmVersion) {
  238. // Cipher text was computed with a different algorithm version than this.
  239. throw SQL.InvalidAlgorithmVersion(cipherText[startIndex], _algorithmVersion);
  240. }
  241. startIndex += 1;
  242. int authenticationTagOffset = 0;
  243. // Read authentication tag
  244. if (hasAuthenticationTag) {
  245. authenticationTagOffset = startIndex;
  246. startIndex += _KeySizeInBytes; // authentication tag size is _KeySizeInBytes
  247. }
  248. // Read cell IV
  249. Buffer.BlockCopy(cipherText, startIndex, iv, 0, iv.Length);
  250. startIndex += iv.Length;
  251. // Read encrypted text
  252. int cipherTextOffset = startIndex;
  253. int cipherTextCount = cipherText.Length - startIndex;
  254. if (hasAuthenticationTag) {
  255. // Compute authentication tag
  256. byte[] authenticationTag = PrepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount);
  257. if (!SqlSecurityUtility.CompareBytes(authenticationTag, cipherText, authenticationTagOffset, authenticationTag.Length)) {
  258. // Potentially tampered data, throw an exception
  259. throw SQL.InvalidAuthenticationTag();
  260. }
  261. }
  262. // Decrypt the text and return
  263. return DecryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
  264. }
  265. /// <summary>
  266. /// Decrypts plain text data using AES in CBC mode
  267. /// </summary>
  268. /// <param name="plainText"> cipher text data to be decrypted</param>
  269. /// <param name="iv">IV to be used for decryption</param>
  270. /// <returns>Returns decrypted plain text data</returns>
  271. private byte[] DecryptData(byte[] iv, byte[] cipherText, int offset, int count) {
  272. Debug.Assert((iv != null) && (cipherText != null));
  273. Debug.Assert (offset > -1 && count > -1);
  274. Debug.Assert ((count+offset) <= cipherText.Length);
  275. byte[] plainText;
  276. AesCryptoServiceProvider aesAlg;
  277. // Try to get a provider from the pool.
  278. // If no provider is available, create a new one.
  279. if (!_cryptoProviderPool.TryDequeue(out aesAlg)) {
  280. aesAlg = new AesCryptoServiceProvider();
  281. try {
  282. // Set various algorithm properties
  283. aesAlg.Key = _columnEncryptionKey.EncryptionKey;
  284. aesAlg.Mode = _cipherMode;
  285. aesAlg.Padding = _paddingMode;
  286. }
  287. catch (Exception) {
  288. if (aesAlg != null) {
  289. aesAlg.Dispose();
  290. }
  291. throw;
  292. }
  293. }
  294. try {
  295. // Always set the IV since it changes from cell to cell.
  296. aesAlg.IV = iv;
  297. // Create the streams used for decryption.
  298. using (MemoryStream msDecrypt = new MemoryStream()) {
  299. // Create an encryptor to perform the stream transform.
  300. using (ICryptoTransform decryptor = aesAlg.CreateDecryptor()) {
  301. using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write)) {
  302. // Decrypt the secret message and get the plain text data
  303. csDecrypt.Write(cipherText, offset, count);
  304. csDecrypt.FlushFinalBlock();
  305. plainText = msDecrypt.ToArray();
  306. }
  307. }
  308. }
  309. }
  310. finally {
  311. // Return the provider to the pool.
  312. _cryptoProviderPool.Enqueue(aesAlg);
  313. }
  314. return plainText;
  315. }
  316. /// <summary>
  317. /// Prepares an authentication tag.
  318. /// Authentication Tag = HMAC_SHA-2-256(mac_key, versionbyte + cell_iv + cell_ciphertext + versionbyte_length)
  319. /// </summary>
  320. /// <param name="cipherText"></param>
  321. /// <returns></returns>
  322. private byte[] PrepareAuthenticationTag(byte[] iv, byte[] cipherText, int offset, int length) {
  323. Debug.Assert(cipherText != null);
  324. byte[] computedHash;
  325. byte[] authenticationTag = new byte[_KeySizeInBytes];
  326. // Raw Tag Length:
  327. // 1 for the version byte
  328. // 1 block for IV (16 bytes)
  329. // cipherText.Length
  330. // 1 byte for version byte length
  331. using (HMACSHA256 hmac = new HMACSHA256(_columnEncryptionKey.MACKey)) {
  332. int retVal = 0;
  333. retVal = hmac.TransformBlock(_version, 0, _version.Length, _version, 0);
  334. Debug.Assert(retVal == _version.Length);
  335. retVal = hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
  336. Debug.Assert(retVal == iv.Length);
  337. retVal = hmac.TransformBlock(cipherText, offset, length, cipherText, offset);
  338. Debug.Assert(retVal == length);
  339. hmac.TransformFinalBlock(_versionSize, 0, _versionSize.Length);
  340. computedHash = hmac.Hash;
  341. }
  342. Debug.Assert (computedHash.Length >= authenticationTag.Length);
  343. Buffer.BlockCopy (computedHash, 0, authenticationTag, 0, authenticationTag.Length);
  344. return authenticationTag;
  345. }
  346. }
  347. }