简体   繁体   English

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

[英]Calling advapi.dll using Pinvoke: CryptDecrypt and CryptEncrypt unexpected behaviour

This is a follow up from this question . 这是该问题的后续措施。

I am calling the WinAPI Cryptography functions from VB.net to ensure compatibility with a legacy product that shares the resulting data. 我正在从VB.net调用WinAPI加密功能,以确保与共享结果数据的旧产品兼容。

The code functioned fine for many years however I have recently run into a problem when running on Windows Server 2012. After taking a look at various examples and the answer to my previous question I gave the code an overhaul, including changing what was previously a string variable to a Byte array, which I believe is more correct functionality. 该代码可以正常工作很多年,但是最近我在Windows Server 2012上运行时遇到了问题。在查看了各种示例和上一个问题的答案之后,我对代码进行了全面检查,包括更改了以前的字符串变量为字节数组,我认为这是更正确的功能。 How it previously ever worked with string I have no idea, but after fixing the original Server 2012 issue it was failing due to NTE_BAD_LENGTH of the string. 我以前不知道它如何与字符串一起工作,但是在解决了原始的Server 2012问题之后,由于字符串的NTE_BAD_LENGTH而失败。

Now, after converting the string to a byte array, The code does not appear to encrypt in the same way it did previously, nor will it decrypt what is generated by CryptEncrypt. 现在,将字符串转换为字节数组后,该代码似乎无法像以前一样进行加密,也不会解密CryptEncrypt生成的内容。

The main bulk of the code follows: 该代码的主要内容如下:

(Calls to CryptCreateHash, CryptHashData, CryptDeriveKey precede this) (在此之前调用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) 

My method prototypes are defined as: 我的方法原型定义为:

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

An Example output is as follows: 示例输出如下:
Test Data: Testing123 测试数据:Testing123
PlainText Data in buffer: 84, 101, 115, 116, 105, 110, 103, 49, 50, 51, 缓冲区中的PlainText数据:84、101、115、116、105、110、103、49、50、51,
Encrypted: CW?? 加密:CW? ???_ ??? _
Encrypted Data in buffer: 12, 67, 87, 133, 158, 9, 139, 250, 255, 95, 缓冲区中的加密数据:12、67、87、133、158、9、139、250、255、95,
Decrypted Data in buffer: 12, 67, 87, 133, 158, 9, 139, 250, 255, 95, 缓冲区中的解密数据:12,67,87,133,158,9,139,250,255,95,
Decrypted: CW?? 解密:CW? ???_ ??? _

As you can see, post decryption the buffer stays exactly the same. 如您所见,解密后缓冲区保持完全相同。

I have no idea why this is happening, my only theory is that it is something to do with which charset I am encoding the Byte() with, although I have tried several and it makes no difference. 我不知道为什么会这样,我唯一的理论是,这与我用来编码Byte()的字符集有关,尽管我尝试了几次,但没有区别。 This may also apply to the password parameter (Byte() passed to CryptHashData ). 这也可能适用于密码参数(传递给CryptHashData Byte())。

Any help is greatly appreciated. 任何帮助是极大的赞赏。

EDIT 1: Thanks for the help, I realised the buffer copy issue earlier today, I was attempting to isolate the problem in my test app but ended up over complicating my variables, yes. 编辑1:感谢您的帮助,我今天早些时候意识到缓冲区复制问题,我试图在我的测试应用程序中隔离问题,但最终导致变量变复杂,是的。

Indeed, arrays in VB are instantiated with the highest index so 127 creates a length 128 array, confusing I know. 实际上,VB中的数组是使用最高索引实例化的,所以127创建了一个长度为128的数组,这让我感到困惑。

I believe my problem now lies with the encoding, as you discuss. 正如您所讨论的,我相信我的问题现在出在编码上。 Previously the data was handed into CryptEncrypt / CryptDecrypt as a .NET String instead of first being converted to a byte(), like I am doing now. 以前,数据是作为.NET字符串传递到CryptEncrypt / CryptDecrypt ,而不是像我现在所做的那样首先转换为byte()。 However I do not know how the unmanaged code would have dealt with this string, presumably the default .NET encoding or maybe the Windows encoding? 但是我不知道非托管代码将如何处理该字符串,大概是默认的.NET编码还是Windows编码?

I have tried UTF8 and Unicode however neither will mirror the behaviour of the original code and encypt/decrypt previously stored strings to their original value. 我已经尝试了UTF8和Unicode,但是它们都不会镜像原始代码的行为,也不会将以前存储的字符串加密/解密为原始值。 This is also the reason I want to continue to use the unmanaged methods, to mirror exactly legacy behaviour (A VB6 app using the unmanaged methods is used to generate the encrypted data before it is decrypted in .NET). 这也是我要继续使用非托管方法以完全反映旧行为的原因(使用非托管方法的VB6应用程序用于在.NET中解密之前生成加密数据)。

My Code now encrypts and decrypts, but, as I said, the values output do not match the original data, despite using the same cryptographic techniques. 我的代码现在可以加密和解密,但是,正如我所说,尽管使用了相同的加密技术,但输出的值与原始数据不匹配。 Thank you for your continued help. 感谢您一直以来的帮助。

The reason your decrypted data appears the same as the encrypted data is because you're displaying the wrong buffer. 解密后的数据看起来与加密后的数据相同的原因是因为您显示了错误的缓冲区。 You're decrypting to _encryptedBufferCopy but then copying _bigBuffer to _outBuffer , and displaying that as the decrypted data. 您正在解密到_encryptedBufferCopy ,然后将_bigBuffer复制到_outBuffer ,并将其显示为解密的数据。 But _bigBuffer contains the encrypted data. 但是_bigBuffer包含加密的数据。

You have too many temporary buffers. 您有太多的临时缓冲区。 Also, it's a little disconcerting to see you allocate an array with dataBufferLength - 1 . 另外,看到您使用dataBufferLength - 1分配数组有点令人不安。 I'm trying to figure out why the -1 . 我试图弄清楚为什么-1 Or is that because of VB's insistence that the parameter is the highest index rather than the length? 还是因为VB坚持认为参数是最高索引而不是长度?

You should also note that the last parameter to CryptDecrypt is ByRef . 您还应该注意, CryptDecrypt的最后一个参数是ByRef When the function returns, that is supposed to contain the number of bytes in the decrypted plaintext. 当函数返回时,该函数应包含已解密明文中的字节数。 Your call to CryptDecrypt is passing _encryptedBufferCopy.Length , which seems like it should be an error. 您对CryptDecrypt调用传递了_encryptedBufferCopy.Length ,这似乎应该是一个错误。 In any case, the function won't be able to set the decrypted length. 无论如何,该功能将无法设置解密长度。

One problem you're going to have is that you're using ASCII encoding. 您将要遇到的一个问题是您正在使用ASCII编码。 ASCII is a 7-bit encoding, which means that if you have any characters with codes over 127, calling encoding.GetBytes(textString) is going to give you ? ASCII是一种7位编码,这意味着如果您有任何字符的代码超过127,则调用encoding.GetBytes(textString)将为您提供? characters for those with the high bit set. 高位设置的字符。

Probably your best bet is to use Encoding.UTF8 . 最好的选择是使用Encoding.UTF8

Once the data is encrypted, you can't reliably turn it into a string by calling encoding.GetString . 数据加密后,就无法通过调用encoding.GetString可靠地将其转换为字符串。 That data buffer can contain any byte value, including what would be considered control characters that might not be displayed, might draw funny characters, or who knows what. 该数据缓冲区可以包含任何字节值,包括可能不显示的控制字符,可能绘制有趣的字符或谁知道的字符。 So where you have: 所以你有:

''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

That's almost certain to create a string that can't be displayed. 几乎可以肯定会创建一个无法显示的字符串。 The ? ? characters you're seeing here represent bytes with character values above 127, which your ASCII encoding won't interpret correctly. 您在此处看到的字符表示字符值大于127的字节,您的ASCII编码将无法正确解释这些字符。

All things considered, I'm wondering why you don't just use the managed APIs in the System.Security.Cryptography namespace for this. 考虑到所有问题,我想知道为什么您不仅仅使用System.Security.Cryptography命名空间中的托管API。 I think you'll find that it's much easier, and you don't have to worry about handles and 32/64 bit issues, etc. 我认为您会发现它容易得多,而且您不必担心句柄和32/64位问题等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM