使用Pinvoke调用advapi.dll:CryptDecrypt和CryptEncrypt意外行为

这是这个问题的后续行动。

我从VB.net调用WinAPI Cryptography函数以确保与共享结果数据的旧产品兼容。

该代码运行良好多年,但是我最近在Windows Server 2012上运行时遇到了问题。在看了各种示例和我之前的问题的答案后,我给了代码一个大修,包括改变以前的字符串变量为Byte数组,我认为这是更正确的function。 以前它如何使用字符串我不知道,但在修复原始的Server 2012问题之后,由于字符串的NTE_BAD_LENGTH而失败了。

现在,在将字符串转换为字节数组之后,代码似乎不会像以前那样加密,也不会解密CryptEncrypt生成的内容。

代码的主要部分如下:

(调用CryptCreateHash,CryptHashData,CryptDeriveKey在此之前)

Dim _encoding As System.Text.ASCIIEncoding = New System.Text.ASCIIEncoding() Dim data As String = "Testing123" ''define test String Debug.WriteLine("Test Data: " & data) ''convert data to Byte() Dim _bigBuffer As Byte() = New Byte(127) {} ''Buffer to be encrypted Dim _dataBuffer As Byte() = _encoding.GetBytes(data) ''Get the text to be encrypted into a byte() Buffer.BlockCopy(_dataBuffer, 0, _bigBuffer, 0, _dataBuffer.Length) ''Copy the data into the big buffer Dim _dataBufferLength As UInteger = Convert.ToUInt32(_dataBuffer.Length) ''Get the length Debug.Write("PlainText Data in buffer: ") For Each _b As Byte In _dataBuffer Debug.Write(_b & ", ") Next ''Encrypt data. If CryptoApi.CryptEncrypt(_hKey, IntPtr.Zero, True, 0, _bigBuffer, _dataBufferLength, _bigBuffer.Length) = False Then Dim _error As Integer = Err.LastDllError Throw New Exception("Error during CryptEncrypt. Error Code: " & _error.ToString) End If ''print out the encrypted string Dim _encryptedDataBuffer As Byte() = New Byte(_dataBufferLength - 1) {} ''Buffer, size is size of the output encrypted string Buffer.BlockCopy(_bigBuffer, 0, _encryptedDataBuffer, 0, _dataBufferLength) ''copy from output buffer to new buffer Dim _encryptedStringFromBuffer As String = _encoding.GetString(_encryptedDataBuffer) ''Decode buffer back to string Debug.WriteLine("") Debug.WriteLine("Encrypted: " & _encryptedStringFromBuffer) ''Write encrypted string from buffer Debug.Write("Encrypted Data in buffer: ") For Each _b As Byte In _encryptedDataBuffer Debug.Write(_b & ", ") Next ''Copy encrypted strings buffer, ready to decrypted Dim _encryptedBufferCopy As Byte() = New Byte(127) {} ''new buffer to hold encrypted data Buffer.BlockCopy(_bigBuffer, 0, _encryptedBufferCopy, 0, _bigBuffer.Length) ''copy the output of encryption Dim _inputLengthCopy As UInteger = _dataBufferLength ''Copy the length of encrypted data ''Decrypt data. If CryptoApi.CryptDecrypt(_hKey, IntPtr.Zero, True, 0, _encryptedBufferCopy, _encryptedBufferCopy.Length) = False Then Dim _error As Integer = Err.LastDllError Throw New Exception("Error during CryptDecrypt. Error Code: " & _error.ToString) End If Dim _outBuffer As Byte() = New Byte(_dataBufferLength - 1) {} Buffer.BlockCopy(_bigBuffer, 0, _outBuffer, 0, _dataBufferLength) Dim _stringFromBuffer As String = _encoding.GetString(_outBuffer) Debug.WriteLine("") Debug.Write("Decrypted Data in buffer: ") For Each _b As Byte In _outBuffer Debug.Write(_b & ", ") Next Debug.WriteLine("") Debug.WriteLine("Decrypted: " & _stringFromBuffer) 

我的方法原型定义为:

 Friend Declare Function CryptEncrypt Lib "advapi32.dll" _ (ByVal hKey As IntPtr, _ ByVal hHash As IntPtr, _  ByVal final As Boolean, _ ByVal dwFlags As Integer, _ ByVal data As Byte(), _ ByRef pdwDataLen As Integer, _ ByVal dwBufLen As Integer) _ As  Boolean Friend Declare Function CryptDecrypt Lib "advapi32.dll" _ (ByVal hKey As IntPtr, _ ByVal hHash As IntPtr, _  ByVal final As Boolean, _ ByVal dwFlags As Integer, _ ByVal data As Byte(), _ ByRef pdwDataLen As Integer) _ As  Boolean 

示例输出如下:
测试数据:测试123
PlainText缓冲区中的数据:84,101,115,116,105,110,103,49,50,51,
加密:CW ?? ??? _
缓冲区中的加密数据:12,67,87,133,158,9,139,250,255,95,
缓冲区中的解密数据:12,67,87,133,158,9,139,250,255,95,
解密:CW? ??? _

如您所见,解密后,缓冲区保持完全相同。

我不知道为什么会发生这种情况,我唯一的理论是它与我编码Byte()的字符集有关,虽然我已经尝试了几个并且它没有任何区别。 这也可能适用于密码参数(传递给CryptHashData Byte())。

任何帮助是极大的赞赏。

编辑1:感谢您的帮助,我今天早些时候意识到缓冲区复制问题,我试图在我的测试应用程序中隔离问题,但最终导致我的变量复杂化,是的。

实际上,VB中的数组实例化的索引最高,所以127创建了一个长度为128的数组,这让我很困惑。

我相信我现在的问题在于编码,正如您所讨论的那样。 以前,数据作为.NET字符串传递给CryptEncrypt / CryptDecrypt而不是首先转换为byte(),就像我现在正在做的那样。 但是,我不知道非托管代码将如何处理此字符串,可能是默认的.NET编码或Windows编码?

我已经尝试过UTF8和Unicode,但是它们都不会镜像原始代码的行为,并且将先前存储的字符串加密/解密为其原始值。 这也是我想继续使用非托管方法,以完全镜像遗留行为的原因(使用非托管方法的VB6应用程序用于在.NET中解密之前生成加密数据)。

我的代码现在加密和解密,但正如我所说,尽管使用相同的加密技术,但输出的值与原始数据不匹配。 感谢您的继续帮助。

您的解密数据与加密数据相同的原因是因为您显示的是错误的缓冲区。 您正在解密_encryptedBufferCopy但随后将_bigBuffer复制到_outBuffer ,并将其显示为解密数据。 但_bigBuffer包含加密数据。

你有太多的临时缓冲区。 此外,看到您使用dataBufferLength - 1分配数组有点令人不安。 我想弄清楚为什么-1 。 或者是因为VB坚持认为参数是最高指数而不是长度?

您还应该注意CryptDecrypt的最后一个参数是ByRef 。 当函数返回时,应该包含解密明文中的字节数。 你对CryptDecrypt调用是通过_encryptedBufferCopy.Length ,这似乎应该是一个错误。 在任何情况下,该函数将无法设置解密长度。

您将遇到的一个问题是您正在使用ASCII编码。 ASCII是一个7位编码,这意味着如果你有任何代码超过127的字符,调用encoding.GetBytes(textString)会给你? 具有高位设置的字符。

可能你最好的选择是使用Encoding.UTF8

数据加密后,您无法通过调用encoding.GetString将其可靠地转换为字符串。 该数据缓冲区可以包含任何字节值,包括可能不会显示的控件字符,可能会绘制有趣的字符,或者谁知道什么。 所以你在哪里:

 ''print out the encrypted string Dim _encryptedDataBuffer As Byte() = New Byte(_dataBufferLength - 1) {} ''Buffer, size is size of the output encrypted string Buffer.BlockCopy(_bigBuffer, 0, _encryptedDataBuffer, 0, _dataBufferLength) ''copy from output buffer to new buffer Dim _encryptedStringFromBuffer As String = _encoding.GetString(_encryptedDataBuffer) ''Decode buffer back to string 

这几乎肯定会创建一个无法显示的字符串。 的? 您在此处看到的字符表示字符值大于127的字节,您的ASCII编码将无法正确解释。

考虑到所有事情,我想知道为什么你不只是在System.Security.Cryptography命名空间中使用托管API。 我想你会发现它更容易,你不必担心句柄和32/64位问题等。