一旦我们开始将数据写入加密表,我们就会在尝试读取加密数据时发现问题。症状与描述HERE
的症状相同这里有一点背景知识。我们有一个网络应用程序,它将客户信息写入"客户端" SQL Server数据库中的表。作为转换解决方案,我们创建了附加表client_enc
并更新了我们的应用程序以写入两个表:原始表和加密表。我们有4个Web应用程序实例,托管在同一个VM上,同一个IIS。
我们的Web应用程序的所有4个实例都映射到文件系统上的同一文件夹(二进制代码或web.config中没有区别)。
我们注意到其中一个实例随机写入了损坏的值。这些写入发生时没有重新启动/回收Web应用程序(写入之间的几秒钟内)。
以下是特定客户的信息:
客户姓氏:" Hoyer"
良好的加密值(我们稍后可以阅读):
0x015EF5BB1B1EA45EADFA9EFC3611D3F5661616C4B38BEDB06B33D6B6DC084714F235E0818C14DEEC0A95C5547DE8DC3D3A402A4DB8C992AB3716B651037C8ED2E7
加密值损坏:
0x01848FA1EA78BA1FCFC615728CEE9882937A52AAF649472F0B7829A28463060E34080F924AC5CD987AA0C5275507C0A480EC9D44B63B256552EFFE7C1562FEC1DA
环境:
web.config
中的目标框架:4.6.2 有人可以猜测是什么导致了这种奇怪的行为吗?
答案 0 :(得分:1)
更新:最初,由于测试结果不准确,我做出了错误的判断。我将删除错误的事实,但会将它们留在这里用于历史目的。
经过一周的血腥调试和测试后,我得出结论,该行为的根源在.NET Framework中 RSACryptoServiceProvider 类的内部。
事实让我这么想:
我还发现了这个ARTICLE,这让我觉得高负载的ASP.NET应用程序可能会遇到 RSACryptoServiceProvider 类的问题,一旦它被用于多个线程环境。这正是我的情况和SqlColumnEncryptionCertificateStoreProvider没有任何线程同步机制来避免这个问题,这发生在 RSACryptoServiceProvider 内。
查看Always Encrypted相关类的源代码后,我发现只有ONE PLACE,其中使用了解密的列密钥。
// 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);
}
plaintextKey
值是100%正确的,因为我在从DecryptColumnEncryptionKey()方法返回之前记录它。
encryptionKey = new SqlClientSymmetricKey(plaintextKey)
内部的密钥损坏是不太可能的,因为SqlClientSymmetricKey是一个围绕字节数组的简单包装。
_cache.Add(cacheLookupKey, encryptionKey, expirationTime)
内部的密钥损坏在我看来也不太可能。
这让我只有一个合理解释如何发生这种情况。 由于字节数组(我们的解密密钥)作为引用传递到任何地方,在特定情况下,该密钥的使用者会搞砸数组中的字节值。但遗憾的是,我无法在代码中找到任何证明该理论的地方。
解决方法即可。一旦我在应用程序开始提供请求(在Global.asax内)之前添加了加密表的简单读取,那么问题就消失了。基本上,这个技巧可以帮助我保证只有一个非并发的DB数据读取触发了列密钥解密和SqlSymmetricKeyCache初始化。
很高兴听到微软团队就这种非常奇怪的行为发表的一些评论。