delphi 如何使用Crypto API解密AES-256加密的zip文件

w8f9ii69  于 2023-04-11  发布在  其他
关注(0)|答案(1)|浏览(499)

我试图使用TZipFile和Microsoft Crypto在 Delphi 中解压缩AES-256加密文件。它总是返回一个坏数据错误并返回乱码。

背景...

我使用标准的TZipFile类打开zip,并注册了压缩处理程序,如下所示:

const zcAESEncrypted = 99;
RegisterCompressionHandler( TZipCompression(zcAESEncrypted), nil, DecompressStream );

它使用以下方法:

function DecompressStream(InStream: TStream; const ZipFile: TZipFile; const Item: TZipHeader): TStream;
var
  LStream : TStream;
  isEncrypted: Boolean;
begin
  isEncrypted := (Item.Flag and 1) = 1;

  if isEncrypted and (ZipFile is TEncryptedZipFile) and ( Item.CompressionMethod = zcAESEncrypted ) then
    LStream := AESDecryptStream(InStream, TEncryptedZipFile(ZipFile).Password, Item.CompressedSize )
  else
    LStream := InStream;
  Result := TZDecompressionStream.Create(LStream, -15, LStream <> InStream);
end;

AES解密的代码如下:

function AESDecryptStream(InStream: TStream; const Password: string; FileSize: Integer): TStream;
const
  AES_KEY_SIZE   = 16 ;
  IN_CHUNK_SIZE  = AES_KEY_SIZE * 10; // a buffer must be a multiple of the key size
var
  chunkSize: Integer;
  hProv: HCRYPTPROV;
  hHash: HCRYPTHASH;
  hKey: HCRYPTKEY;
  lpBuffer: PByte;
  len, keySize: Int64;
  fsOut : TFileStream;
  keyStr: WideString;
  isFinal: BOOL;
  outLen: Integer;
  readTotalSize: Integer;
begin
  keyStr := WideString(Password);
  len := lstrlenW( PWideChar( keyStr ));
  keySize := len * sizeof(WideChar); // size in bytes

  // Acquire a CryptoAPI context
  if not CryptAcquireContext(@hProv, nil, nil, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) then
    RaiseLastOSError;

  // Create a hash object
  if not CryptCreateHash(hProv, CALG_SHA_256, 0, 0, @hHash) then
    RaiseLastOSError;

  // Hash the encrypted data
  if not CryptHashData(hHash, @keyStr, keySize, 0) then
    RaiseLastOSError;

  // Create a key object from the password
  if not CryptDeriveKey(hProv, CALG_AES_256, hHash, 0, @hKey) then
    RaiseLastOSError;

  Result := TMemoryStream.Create();
  fsOut := TFileStream.Create( 'C:\Temp\Stream_Out.txt', fmCreate); // For debugging only

  lpBuffer := AllocMem(IN_CHUNK_SIZE);
  isFinal := FALSE;
  readTotalSize := 0;

  while isFinal = False do
  begin
    chunkSize := imin( IN_CHUNK_SIZE, FileSize - readTotalSize );

    outLen := InStream.ReadData( lpBuffer, chunkSize );
    if outLen = 0 then
       break;

    readTotalSize := readTotalSize + outLen;
    if readTotalSize >= FileSize then
      isFinal := TRUE;

    if not CryptDecrypt(hKey, 0, isFinal, 0, lpBuffer, @outLen) then
      RaiseLastOSError;    // Project Project1.exe raised exception class EOSError with message 'System Error.  Code: -2146893819. Bad Data.'

    fsOut.Write( lpBuffer, outLen);
    Result.Write( lpBuffer, outLen );
  end;

  FreeMem( lpBuffer );
  fsOut.Free;

  // Release the hash object
  CryptDestroyHash(hHash);

  // Release the key object
  CryptDestroyKey(hKey);

  // Release the CryptoAPI context
  CryptReleaseContext(hProv, 0);
end;

此代码基于:
https://gist.github.com/hasherezade/2860d94910c5c5fb776edadf57f0bef6
虽然这看起来有点可疑,但这是我能找到的唯一一个例子,而且确实声称可以工作(与AES-128)。
但是,代码在CryptDecrypt()函数上引发了一个错误:
Project Project1.exe引发异常类EOSError,并显示消息“系统错误。代码:-2146893819,数据错误。”
根据Winzip文档中列出的要求:
https://www.winzip.com/en/support/aes-encryption/
有一些明显的缺点:

  • 它缺少salt值(AES-256为16字节),我将其作为文件的前16个字节
  • 它不执行密钥生成的1000次迭代

然而,我看不出这些变化应该如何使用加密API实现。潜在的,这甚至是不可能的?有什么建议吗?

ct2axkht

ct2axkht1#

对于AES-256,AES_KEY_SIZE需要为32。您链接到的代码使用的是AES-128,它使用的密钥大小为16。
另外,你对CryptHashData()的调用是错误的。你需要删除@并使用PWideChar转换:
CryptHashData(hHash, PWideChar(keyStr), keySize, 0);
另外,这不是一个错误,但你真的应该使用UnicodeString而不是WideString。由于stringUnicodeString,你根本不需要keyStr变量,只需按原样使用Password
CryptHashData(hHash, PWideChar(Password), ByteLength(Password), 0);

相关问题