【问题标题】:Calling advapi.dll using Pinvoke: CryptDecrypt and CryptEncrypt unexpected behaviour使用 Pinvoke 调用 advapi.dll:CryptDecrypt 和 CryptEncrypt 意外行为
【发布时间】:2013-12-16 17:25:21
【问题描述】:

这是this question 的后续报道。

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

代码多年来一直运行良好,但是我最近在 Windows Server 2012 上运行时遇到了问题。在查看了各种示例和我之前问题的答案后,我对代码进行了大修,包括更改以前是字节数组的字符串变量,我认为这是更正确的功能。我不知道它以前是如何处理字符串的,但在修复了最初的 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, _
                             <MarshalAs(UnmanagedType.Bool)> ByVal final As Boolean, _
                             ByVal dwFlags As Integer, _
                             ByVal data As Byte(), _
                             ByRef pdwDataLen As Integer, _
                             ByVal dwBufLen As Integer) _
                          As <MarshalAs(UnmanagedType.Bool)> Boolean


Friend Declare Function CryptDecrypt Lib "advapi32.dll" _
                              (ByVal hKey As IntPtr, _
                              ByVal hHash As IntPtr, _
                              <MarshalAs(UnmanagedType.Bool)> ByVal final As Boolean, _
                              ByVal dwFlags As Integer, _
                              ByVal data As Byte(), _
                              ByRef pdwDataLen As Integer) _
                            As <MarshalAs(UnmanagedType.Bool)> Boolean

示例输出如下:
测试数据:Testing123
缓冲区中的纯文本数据: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() 有关,尽管我已经尝试了几个并且没有任何区别。这也可能适用于密码参数(Byte() 传递给CryptHashData)。

非常感谢任何帮助。

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

事实上,VB 中的数组是用最高索引实例化的,所以 127 创建了一个长度为 128 的数组,我知道这令人困惑。

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

我尝试过 UTF8 和 Unicode,但是两者都不会反映原始代码的行为,也不会将先前存储的字符串加密/解密为原始值。这也是我想继续使用非托管方法以准确反映遗留行为的原因(使用非托管方法的 VB6 应用程序用于在 .NET 中解密之前生成加密数据)。

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

【问题讨论】:

  • 试试Encoding.Default。如果这不起作用,请尝试Encoding.GetEncoding(1252)。当然,您最好的选择是确定旧版应用程序正在使用什么。我不知道该怎么做。是的,托管 API 应该能够做到这一点并保持与旧版应用的兼容性。
  • 谢谢,我试过这些,但都没有产生正确的结果。旧版应用程序还使用 String 而不是字节数组调用非托管代码,这有效,但可能是问题的根源。我认为值得尝试使用托管 API,快速浏览后它们还需要 byte() 这让我担心我会面临完全相同的问题,因为非托管似乎正在工作(它可以加密/解密自己的字符串,但与以前的数据不兼容)。

标签: c# .net vb.net winapi advapi32


【解决方案1】:

您的解密数据与加密数据显示相同的原因是您显示的缓冲区错误。您正在解密到_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 位问题等。

【讨论】:

  • 您好,再次感谢您的帮助。请看我的编辑。您是否认为可以使托管 API 以与运行非托管代码的 VB6 应用程序加密的数据直接兼容的方式进行解密,反之亦然?谢谢
  • 嗨,我已经设法让它在本地使用字节数组工作,由于某种原因,它只在使用 declare 原型而不是属性原型时工作。现在它将使用Encoding.GetEncoding(1252) 正确解密。但是在现场环境中,我仍然得到 0x80090004 “错误长度”。你对为什么会这样有任何想法吗?谢谢
  • 我已修复它,将缓冲区大小设为 512,然后只需提取所需的数据即可。我正在使用流加密,因此输入的数据长度 = 输出的数据长度让我可以轻松做到这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-04
  • 2017-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多