/// <summary> /// Generates raw data encryption key bytes suitable for use with the provided encryption algorithm. /// </summary> /// <param name="encryptionAlgorithm">Encryption algorithm the returned key is intended to be used with.</param> /// <returns>New instance of data encryption key.</returns> public static byte[] Generate(string encryptionAlgorithm) { if (encryptionAlgorithm != CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized) { throw new ArgumentException($"Encryption algorithm not supported: {encryptionAlgorithm}", nameof(encryptionAlgorithm)); } byte[] rawKey = new byte[32]; SecurityUtility.GenerateRandomBytes(rawKey); return(rawKey); }
/// <summary> /// Decryption steps /// 1. Validate version byte /// 2. (optional) Validate Authentication tag /// 3. Decrypt the message /// </summary> 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 EncryptionExceptionFactory.InvalidCipherTextSize(cipherText.Length, minimumCipherTextLength); } // Validate the version byte int startIndex = 0; if (cipherText[startIndex] != this.algorithmVersion) { // Cipher text was computed with a different algorithm version than this. throw EncryptionExceptionFactory.InvalidAlgorithmVersion(cipherText[startIndex], this.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 = this.PrepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount); if (!SecurityUtility.CompareBytes(authenticationTag, cipherText, authenticationTagOffset, authenticationTag.Length)) { // Potentially tampered data, throw an exception throw EncryptionExceptionFactory.InvalidAuthenticationTag(); } } // Decrypt the text and return return(this.DecryptData(iv, cipherText, cipherTextOffset, cipherTextCount)); }
/// <summary> /// Derives all the required keys from the given root key /// </summary> internal AeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName) : base(rootKey) { this.algorithmName = algorithmName; int keySizeInBytes = KeySize / 8; // Key validation if (rootKey.Length != keySizeInBytes) { throw EncryptionExceptionFactory.InvalidKeySize( this.algorithmName, rootKey.Length, keySizeInBytes); } // Derive keys from the root key // // Derive encryption key string encryptionKeySalt = string.Format( EncryptionKeySaltFormat, this.algorithmName, KeySize); byte[] buff1 = new byte[keySizeInBytes]; SecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), this.RootKey, buff1); this.encryptionKey = new SymmetricKey(buff1); // Derive mac key string macKeySalt = string.Format(MacKeySaltFormat, this.algorithmName, KeySize); byte[] buff2 = new byte[keySizeInBytes]; SecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), this.RootKey, buff2); this.macKey = new SymmetricKey(buff2); // Derive iv key string ivKeySalt = string.Format(IvKeySaltFormat, this.algorithmName, KeySize); byte[] buff3 = new byte[keySizeInBytes]; SecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), this.RootKey, buff3); this.ivKey = new SymmetricKey(buff3); }
/// <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 (this.isDeterministic) { SecurityUtility.GetHMACWithSHA256(plainText, this.dataEncryptionKey.IVKey, iv); } else { SecurityUtility.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] = this.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 (!this.cryptoProviderPool.TryDequeue(out aesAlg)) { aesAlg = new AesCryptoServiceProvider(); try { // Set various algorithm properties aesAlg.Key = this.dataEncryptionKey.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(this.dataEncryptionKey.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. this.cryptoProviderPool.Enqueue(aesAlg); } return(outBuffer); }
/// <summary> /// Computes SHA256 value of the plain text key bytes /// </summary> /// <returns>A string containing SHA256 hash of the root key</returns> internal virtual string GetKeyHash() { return(SecurityUtility.GetSHA256Hash(this.RootKey)); }