/// <summary> /// Decrypt the keys that need to be sent to the enclave /// </summary> /// <param name="keysTobeSentToEnclave">Keys that need to sent to the enclave</param> /// <param name="serverName"></param> /// <returns></returns> private List <ColumnEncryptionKeyInfo> GetDecryptedKeysToBeSentToEnclave(Dictionary <int, SqlTceCipherInfoEntry> keysTobeSentToEnclave, string serverName) { List <ColumnEncryptionKeyInfo> decryptedKeysToBeSentToEnclave = new List <ColumnEncryptionKeyInfo>(); foreach (SqlTceCipherInfoEntry cipherInfo in keysTobeSentToEnclave.Values) { SqlClientSymmetricKey sqlClientSymmetricKey = null; SqlEncryptionKeyInfo? encryptionkeyInfoChosen = null; SqlSecurityUtility.DecryptSymmetricKey(cipherInfo, serverName, out sqlClientSymmetricKey, out encryptionkeyInfoChosen); if (sqlClientSymmetricKey == null) { throw SQL.NullArgumentInternal("sqlClientSymmetricKey", ClassName, GetDecryptedKeysToBeSentToEnclaveName); } if (cipherInfo.ColumnEncryptionKeyValues == null) { throw SQL.NullArgumentInternal("ColumnEncryptionKeyValues", ClassName, GetDecryptedKeysToBeSentToEnclaveName); } if (!(cipherInfo.ColumnEncryptionKeyValues.Count > 0)) { throw SQL.ColumnEncryptionKeysNotFound(); } //cipherInfo.CekId is always 0, hence used cipherInfo.ColumnEncryptionKeyValues[0].cekId. Even when cek has multiple ColumnEncryptionKeyValues //the cekid and the plaintext value will remain the same, what varies is the encrypted cek value, since the cek can be encrypted by //multiple CMKs decryptedKeysToBeSentToEnclave.Add(new ColumnEncryptionKeyInfo(sqlClientSymmetricKey.RootKey, cipherInfo.ColumnEncryptionKeyValues[0].databaseId, cipherInfo.ColumnEncryptionKeyValues[0].cekMdVersion, cipherInfo.ColumnEncryptionKeyValues[0].cekId)); } return(decryptedKeysToBeSentToEnclave); }
/// <summary> /// Decrypts the ciphertext. /// </summary> internal static byte[] DecryptWithKey(byte[] cipherText, SqlCipherMetadata md, string serverName) { Debug.Assert(serverName != null, @"serverName should not be null in DecryptWithKey."); // Initialize cipherAlgo if not already done. if (!md.IsAlgorithmInitialized()) { SqlSecurityUtility.DecryptSymmetricKey(md, serverName); } Debug.Assert(md.IsAlgorithmInitialized(), "Decryption Algorithm is not initialized"); try { byte[] plainText = md.CipherAlgorithm.DecryptData(cipherText); // this call succeeds or throws. if (null == plainText) { throw SQL.NullPlainText(); } return(plainText); } catch (Exception e) { // compute the strings to pass string keyStr = GetBytesAsString(md.EncryptionKeyInfo.Value.encryptedKey, fLast: true, countOfBytes: 10); string valStr = GetBytesAsString(cipherText, fLast: false, countOfBytes: 10); throw SQL.ThrowDecryptionFailed(keyStr, valStr, e); } }
/// <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> /// Encrypts the plaintext. /// </summary> internal static byte[] EncryptWithKey(byte[] plainText, SqlCipherMetadata md, string serverName) { Debug.Assert(serverName != null, @"serverName should not be null in EncryptWithKey."); // Initialize cipherAlgo if not already done. if (!md.IsAlgorithmInitialized()) { SqlSecurityUtility.DecryptSymmetricKey(md, serverName); } Debug.Assert(md.IsAlgorithmInitialized(), "Encryption Algorithm is not initialized"); byte[] cipherText = md.CipherAlgorithm.EncryptData(plainText); // this call succeeds or throws. if (null == cipherText || 0 == cipherText.Length) { throw SQL.NullCipherText(); } return(cipherText); }
private string GetCacheLookupKey(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, string keyStoreName) { StringBuilder cacheLookupKeyBuilder = new StringBuilder(keyStoreName, capacity: keyStoreName.Length + masterKeyPath.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(signature.Length) + 3 /*separators*/ + 10 /*boolean value + somebuffer*/); cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); cacheLookupKeyBuilder.Append(masterKeyPath); cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); cacheLookupKeyBuilder.Append(allowEnclaveComputations); cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); cacheLookupKeyBuilder.Append(Convert.ToBase64String(signature)); cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); string cacheLookupKey = cacheLookupKeyBuilder.ToString(); return(cacheLookupKey); }
/// <summary> /// Derives all the required keys from the given root key /// </summary> /// <param name="rootKey">Root key used to derive all the required derived keys</param> internal SqlAeadAes256CbcHmac256EncryptionKey(byte[] rootKey, string algorithmName) : base(rootKey) { _algorithmName = algorithmName; int keySizeInBytes = KeySize / 8; // Key validation if (rootKey.Length != keySizeInBytes) { throw SQL.InvalidKeySize(_algorithmName, rootKey.Length, keySizeInBytes); } // Derive keys from the root key // // Derive encryption key string encryptionKeySalt = string.Format(_encryptionKeySaltFormat, _algorithmName, KeySize); byte[] buff1 = new byte[keySizeInBytes]; SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(encryptionKeySalt), RootKey, buff1); _encryptionKey = new SqlClientSymmetricKey(buff1); // Derive mac key string macKeySalt = string.Format(_macKeySaltFormat, _algorithmName, KeySize); byte[] buff2 = new byte[keySizeInBytes]; SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(macKeySalt), RootKey, buff2); _macKey = new SqlClientSymmetricKey(buff2); // Derive iv key string ivKeySalt = string.Format(_ivKeySaltFormat, _algorithmName, KeySize); byte[] buff3 = new byte[keySizeInBytes]; SqlSecurityUtility.GetHMACWithSHA256(Encoding.Unicode.GetBytes(ivKeySalt), RootKey, buff3); _ivKey = new SqlClientSymmetricKey(buff3); }
/// <summary> /// <para> Retrieves the query metadata for a specific query from the cache.</para> /// </summary> internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) { // Return immediately if caching is disabled. if (!SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled) { return(false); } // Check the cache to see if we have the MD for this query cached. string cacheLookupKey = GetCacheLookupKeyFromSqlCommand(sqlCommand); if (cacheLookupKey == null) { IncrementCacheMisses(); return(false); } Dictionary <string, SqlCipherMetadata> ciperMetadataDictionary = _cache.Get(cacheLookupKey) as Dictionary <string, SqlCipherMetadata>; // If we had a cache miss just return false. if (ciperMetadataDictionary == null) { IncrementCacheMisses(); return(false); } // Iterate over all the parameters and try to get their cipher MD. foreach (SqlParameter param in sqlCommand.Parameters) { SqlCipherMetadata paramCiperMetadata; bool found = ciperMetadataDictionary.TryGetValue(param.ParameterNameFixed, out paramCiperMetadata); // If we failed to identify the encryption for a specific parameter, clear up the cipher MD of all parameters and exit. if (!found) { foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { paramToCleanup.CipherMetadata = null; } IncrementCacheMisses(); return(false); } // Cached cipher MD should never have an initialized algorithm since this would contain the key. Debug.Assert(paramCiperMetadata == null || !paramCiperMetadata.IsAlgorithmInitialized()); // We were able to identify the cipher MD for this parameter, so set it on the param. param.CipherMetadata = paramCiperMetadata; } // Create a copy of the cipherMD in order to load the key. // The key shouldn't be loaded in the cached version for security reasons. foreach (SqlParameter param in sqlCommand.Parameters) { SqlCipherMetadata cipherMdCopy = null; if (param.CipherMetadata != null) { cipherMdCopy = new SqlCipherMetadata( param.CipherMetadata.EncryptionInfo, 0, param.CipherMetadata.CipherAlgorithmId, param.CipherMetadata.CipherAlgorithmName, param.CipherMetadata.EncryptionType, param.CipherMetadata.NormalizationRuleVersion); } param.CipherMetadata = cipherMdCopy; if (cipherMdCopy != null) { // Try to get the encryption key. If the key information is stale, this might fail. // In this case, just fail the cache lookup. try { SqlSecurityUtility.DecryptSymmetricKey(cipherMdCopy, sqlCommand.Connection.DataSource); } catch (Exception ex) { // Invalidate the cache entry. InvalidateCacheEntry(sqlCommand); // If we get one of the expected exceptions, just fail the cache lookup, otherwise throw. if (ex is SqlException || ex is ArgumentException || ex is ArgumentNullException) { foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { paramToCleanup.CipherMetadata = null; } IncrementCacheMisses(); return(false); } throw; } } } IncrementCacheHits(); return(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> /// Creates an instance of SqlAes256CbcAlgorithm class with a given key /// </summary> /// <param name="encryptionKey">Root key</param> /// <param name="encryptionType">Encryption Type. Expected values are either Determinitic or Randomized.</param> /// <param name="encryptionAlgorithm">Encryption Algorithm.</param> /// <returns></returns> internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm) { // Callers should have validated the encryption algorithm and the encryption key Debug.Assert(encryptionKey != null); Debug.Assert(string.Equals(encryptionAlgorithm, SqlAes256CbcAlgorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase) == true); // Validate encryption type if (!((encryptionType == SqlClientEncryptionType.Deterministic) || (encryptionType == SqlClientEncryptionType.Randomized))) { throw SQL.InvalidEncryptionType(SqlAes256CbcAlgorithm.AlgorithmName, encryptionType, SqlClientEncryptionType.Deterministic, SqlClientEncryptionType.Randomized); } // Get the cached encryption algorithm if one exists or create a new one, add it to cache and use it // // For now, we only have one version. In future, we may need to parse the algorithm names to derive the version byte. const byte algorithmVersion = 0x1; StringBuilder algorithmKeyBuilder = new StringBuilder(Convert.ToBase64String(encryptionKey.RootKey), SqlSecurityUtility.GetBase64LengthFromByteLength(encryptionKey.RootKey.Length) + 4 /*separators, type and version*/); #if DEBUG int capacity = algorithmKeyBuilder.Capacity; #endif //DEBUG algorithmKeyBuilder.Append(":"); algorithmKeyBuilder.Append((int)encryptionType); algorithmKeyBuilder.Append(":"); algorithmKeyBuilder.Append(algorithmVersion); string algorithmKey = algorithmKeyBuilder.ToString(); #if DEBUG Debug.Assert(algorithmKey.Length <= capacity, "We needed to allocate a larger array"); #endif //DEBUG SqlAes256CbcAlgorithm aesAlgorithm; if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out aesAlgorithm)) { SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new SqlAeadAes256CbcHmac256EncryptionKey(encryptionKey.RootKey, SqlAes256CbcAlgorithm.AlgorithmName); aesAlgorithm = new SqlAes256CbcAlgorithm(encryptedKey, encryptionType, algorithmVersion); // In case multiple threads reach here at the same time, the first one adds the value // the second one will be a no-op, the allocated memory will be claimed by Garbage Collector. _encryptionAlgorithms.TryAdd(algorithmKey, aesAlgorithm); } return(aesAlgorithm); }
/// <summary> /// <para> Retrieves Symmetric Key (in plaintext) given the encryption material.</para> /// </summary> internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnection connection, SqlCommand command) { string serverName = connection.DataSource; Debug.Assert(serverName is not null, @"serverName should not be null."); StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2 /*separators*/); #if DEBUG int capacity = cacheLookupKeyBuilder.Capacity; #endif //DEBUG cacheLookupKeyBuilder.Append(":"); cacheLookupKeyBuilder.Append(Convert.ToBase64String(keyInfo.encryptedKey)); cacheLookupKeyBuilder.Append(":"); cacheLookupKeyBuilder.Append(keyInfo.keyStoreName); string cacheLookupKey = cacheLookupKeyBuilder.ToString(); #if DEBUG Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array"); #endif //DEBUG // Lookup the key in cache if (!(_cache.Get(cacheLookupKey) is SqlClientSymmetricKey encryptionKey)) { Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath); // Key Not found, attempt to look up the provider and decrypt CEK if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command)) { throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(), SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command)); } // Decrypt the CEK // We will simply bubble up the exception from the DecryptColumnEncryptionKey function. byte[] plaintextKey; try { // to prevent conflicts between CEK caches, global providers should not use their own CEK caches provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey); } catch (Exception e) { // Generate a new exception and throw. string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10); throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e); } encryptionKey = new SqlClientSymmetricKey(plaintextKey); // If the cache TTL is zero, don't even bother inserting to the cache. if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero) { // In case multiple threads reach here at the same time, the first one wins. // The allocated memory will be reclaimed by Garbage Collector. DateTimeOffset expirationTime = DateTimeOffset.UtcNow.Add(SqlConnection.ColumnEncryptionKeyCacheTtl); _cache.Add(cacheLookupKey, encryptionKey, expirationTime); } } return(encryptionKey); }
/// <summary> /// <para> Retrieves Symmetric Key (in plaintext) given the encryption material.</para> /// </summary> internal bool GetKey(SqlEncryptionKeyInfo keyInfo, string serverName, out SqlClientSymmetricKey encryptionKey) { Debug.Assert(serverName != null, @"serverName should not be null."); StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2 /*separators*/); #if DEBUG int capacity = cacheLookupKeyBuilder.Capacity; #endif //DEBUG cacheLookupKeyBuilder.Append(":"); cacheLookupKeyBuilder.Append(Convert.ToBase64String(keyInfo.encryptedKey)); cacheLookupKeyBuilder.Append(":"); cacheLookupKeyBuilder.Append(keyInfo.keyStoreName); string cacheLookupKey = cacheLookupKeyBuilder.ToString(); #if DEBUG Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array"); #endif //DEBUG // Lookup the key in cache encryptionKey = _cache.Get(cacheLookupKey) as SqlClientSymmetricKey; if (encryptionKey == null) { Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths != null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); // Check against the trusted key paths // // Get the List corresponding to the connected server IList <string> trustedKeyPaths; if (SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.TryGetValue(serverName, out trustedKeyPaths)) { // If the list is null or is empty or if the keyPath doesn't exist in the trusted key paths, then throw an exception. if ((trustedKeyPaths == null) || (trustedKeyPaths.Count() == 0) || // (trustedKeyPaths.Where(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)) { (trustedKeyPaths.Any(s => s.Equals(keyInfo.keyPath, StringComparison.InvariantCultureIgnoreCase)) == false)) { // throw an exception since the key path is not in the trusted key paths list for this server throw SQL.UntrustedKeyPath(keyInfo.keyPath, serverName); } } // Key Not found, attempt to look up the provider and decrypt CEK SqlColumnEncryptionKeyStoreProvider provider; if (!SqlConnection.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out provider)) { throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, SqlConnection.GetColumnEncryptionSystemKeyStoreProviders(), SqlConnection.GetColumnEncryptionCustomKeyStoreProviders()); } // Decrypt the CEK // We will simply bubble up the exception from the DecryptColumnEncryptionKey function. byte[] plaintextKey; try { plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey); } catch (Exception e) { // Generate a new exception and throw. string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10); throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e); } encryptionKey = new SqlClientSymmetricKey(plaintextKey); // If the cache TTL is zero, don't even bother inserting to the cache. if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero) { // In case multiple threads reach here at the same time, the first one wins. // The allocated memory will be reclaimed by Garbage Collector. DateTimeOffset expirationTime = DateTimeOffset.UtcNow.Add(SqlConnection.ColumnEncryptionKeyCacheTtl); _cache.Add(cacheLookupKey, encryptionKey, expirationTime); } } return(true); }