Asp.net 在SQL Server中使用始终加密的已损坏数据

Asp.net 在SQL Server中使用始终加密的已损坏数据,asp.net,.net,sql-server,encryption,always-encrypted,Asp.net,.net,Sql Server,Encryption,Always Encrypted,一旦我们开始将数据写入加密表,一旦我们尝试读取加密数据,就会发现问题。症状与描述的症状相同 这里有一点背景。我们有一个web应用程序,它将客户机信息写入SQL Server数据库中的客户机表。作为一个转换解决方案,我们创建了额外的表客户端,并更新了我们的应用程序以写入两个表:原始表和加密表。我们有4个web应用实例,托管在同一个VM和同一个IIS上 我们的web应用程序的所有4个实例都映射到文件系统上的同一文件夹,二进制代码或web.config中没有差异 我们注意到其中一个实例随机写入损坏的值

一旦我们开始将数据写入加密表,一旦我们尝试读取加密数据,就会发现问题。症状与描述的症状相同

这里有一点背景。我们有一个web应用程序,它将客户机信息写入SQL Server数据库中的客户机表。作为一个转换解决方案,我们创建了额外的表客户端,并更新了我们的应用程序以写入两个表:原始表和加密表。我们有4个web应用实例,托管在同一个VM和同一个IIS上

我们的web应用程序的所有4个实例都映射到文件系统上的同一文件夹,二进制代码或web.config中没有差异

我们注意到其中一个实例随机写入损坏的值。这些写操作在写操作之间的几秒钟内没有重新启动/回收web应用程序

以下是特定客户的信息:

客户姓氏:Hoyer

良好的加密值—我们稍后可以阅读的值:

0x015EF5BB1B1EA45EADFA9EFC3611D3F5661616C4B38BEDB06B33D6B6DC084714F235E0818C14DEEC0A95C5547DE8DC3D3A402A4DB8C992AB3716B651037C8ED2E7
已损坏的加密值:

0x01848FA1EA78BA1FCFC615728CEE9882937A52AAF649472F0B7829A28463060E34080F924AC5CD987AA0C5275507C0A480EC9D44B63B256552EFFE7C1562FEC1DA
环境:

主机:Windows 2012 R2 Microsoft Windows NT 6.3 14393 SQL Server 2016 v13.0.1742.0 .NET Framework 4.6.2 web.config中的目标框架:4.6.2 数据库只有一个主键和一个列键 有人能猜出是什么导致了这种奇怪的行为吗?

更新:最初,由于测试结果不准确,我做出了错误的判断。我将划掉错误的事实,但出于历史目的将它们留在这里

经过一周的调试和测试,我得出结论,这种行为的根源在.NETFramework中RSACryptoServiceProvider类的某个地方

让我这么想的事实:

我的应用程序被配置为每小时循环一次,我注意到在应用程序重新启动后,10次中有1次发生数据损坏;若应用程序开始写入损坏的数据,那个么它将永久写入,直到应用程序重新启动或回收 我使用反射来查看对象的内部,涉及AlwaysEncrypted功能: 在System.Data.SqlClient.SqlSymmetricKeyCache内部,我正在监视列密钥解密私有字段的结果,另一个私有静态字段的缓存 我替换了SqlColumnEncryptionCertificateStoreProvider的默认实现,以便记录所有解密列加密密钥的请求 我等待另一个数据损坏发生,然后查看我的补丁提供者和解密密钥的缓存。我发现SqlColumnEncryptionCertificateStoreProvider返回的解密列密钥是0x0000000000000000。。。正确,但缓存中的“”似乎已损坏0x0000000000000000。。。 我还发现了这一点,这让我认为高负载的ASP.NET应用程序一旦在多线程环境中使用,就会与RSACryptoServiceProvider类产生问题。这正是我的情况,SqlColumnEncryptionCertificateStoreProvider没有任何线程同步机制来避免这个问题,这个问题发生在RSACryptoServiceProvider内部

在查看了始终加密的相关类的源代码之后,我只找到了使用解密列密钥的类

// 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 SqlClientSymmetricKeyplaintextKey内部的密钥损坏是不太可能的,因为SqlClientSymmetricKey是字节数组的简单包装器

在我看来,_cache.AddcacheLookupKey、encryptionKey、expirationTime中的密钥损坏也不太可能

这让我对这种情况的发生只有一个合乎逻辑的解释。 在字节数组中,我们解密的密钥作为引用传递到任何地方,在特定情况下,该密钥的使用者会将数组中的字节值搞砸。但不幸的是,我在代码中找不到任何地方来证明这个理论

变通办法。在我的应用程序开始在Global.asax中提供请求之前,我添加了一个加密表的简单读取,然后问题就消失了。基本上,这个技巧可以帮助我保证只有一次非并发地从DB读取数据时触发列密钥解密和SqlSymmetricKeyCache初始化


很高兴听到微软团队对这一奇怪行为的一些评论。

数据究竟是如何进入表格的?是否可能一个值被编码为'Hoyer'varchar,另一个值被编码为N'Hoyer'nvarchar?你安装了吗?您是否考虑过使用SQL Server 2016的最新版本?您是一个service pack,落后于7个客户?@AaronBertrand,我们使用SqlCommand对象设置命令文本,通过简单的SQL插入,将数据写入表中。我怀疑我们是否可以写N'Hoyer',因为在应用程序中只有一个地方可以写,并且它显式地将参数的类型设置为VARCHAR。我们没有尝试安装上述公告,因为它应该修复数据读取,但我们有一个dat问题 一篇文章。我们没有考虑过SQL Server更新,问题是在数据加密阶段,它与SQL Server无关,而是在.NET框架的SQL驱动部分中完成的。