Вызов advapi.dll с помощью Pinvoke: CryptDecrypt и CryptEncrypt неожиданное поведение

Это продолжение этого вопроса.

Я вызываю функции криптографии WinAPI из VB.net, чтобы обеспечить совместимость с устаревшим продуктом, который делится полученными данными.

Код работал хорошо в течение многих лет, однако недавно я столкнулся с проблемой при работе на 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, что меня смущает.

Я считаю, что моя проблема сейчас заключается в кодировке, как вы обсуждаете. Ранее данные были переданы в CryptEncrypt/CryptDecrypt как.NET String вместо того, чтобы сначала быть преобразованным в byte(), как я делаю сейчас. Однако я не знаю, как неуправляемый код справился бы с этой строкой, предположительно кодировкой.NET по умолчанию или, возможно, кодировкой Windows?

Я пробовал UTF8 и Unicode, однако ни один из них не отразит поведение исходного кода и зашифрует / расшифрует ранее сохраненные строки до их первоначального значения. Это также причина, по которой я хочу продолжать использовать неуправляемые методы для точного отражения унаследованного поведения (приложение VB6, использующее неуправляемые методы, используется для генерации зашифрованных данных перед их расшифровкой в ​​.NET).

Мой код теперь шифрует и дешифрует, но, как я уже сказал, выходные значения не совпадают с исходными данными, несмотря на использование тех же криптографических методов. Спасибо за вашу постоянную помощь.

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 не будет правильно интерпретировать.

Учитывая все это, мне интересно, почему вы не просто используете управляемые API в System.Security.Cryptography пространство имен для этого. Я думаю, вы обнаружите, что это намного проще, и вам не нужно беспокоиться о дескрипторах, проблемах с 32/64 битами и т. Д.

Другие вопросы по тегам