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

问题描述 投票:0回答:1

我正在尝试使用

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/

有一些明显的缺点:

  • 它缺少盐值(AES-256 为 16 个字节),我认为这是文件的前 16 个字节

  • 它不会执行 1000 次密钥生成迭代

但是,我看不出这些更改应该如何使用 Crypto API 来实现。潜在地,这甚至不可能?有什么建议吗?

delphi zip aes cryptoapi
1个回答
0
投票

对于 AES-256,

AES_KEY_SIZE
需要为 32。您链接到的代码使用的是 AES-128,它使用的密钥大小为 16.

此外,您对

CryptHashData()
的调用是错误的。您需要放下
@
并使用
PWideChar
演员:

CryptHashData(hHash, PWideChar(keyStr), keySize, 0);


此外,这不是错误,但您确实应该使用

UnicodeString
而不是
WideString
。由于
string
UnicodeString
,您根本不需要
keyStr
变量,只需按原样使用
Password

CryptHashData(hHash, PWideChar(Password), ByteLength(Password), 0);

© www.soinside.com 2019 - 2024. All rights reserved.