使用 Crypto API 解密一个经过 AES-256 加密的 zip 文件

huangapple go评论109阅读模式
英文:

How to decrypt an AES-256 encrypted zip file using Crypto API

问题

我理解你在尝试在Delphi中使用TZipFile和Microsoft Crypto来解压AES-256加密的文件,但是出现了坏数据错误。根据你提供的代码,问题可能出在以下几个方面:

  1. 缺少盐值 (Salt Value): 根据Winzip文档,AES-256加密需要一个16字节的盐值,通常是文件的前16字节。你可能需要修改代码以包括这个盐值。

  2. 缺少1000次迭代的密钥生成:根据要求,需要进行1000次迭代的密钥生成。你可能需要查找相关的Crypto API函数来实现这一点。

  3. Crypto API的正确使用:确保你正确地使用了Crypto API函数,例如CryptAcquireContextCryptCreateHashCryptHashDataCryptDeriveKeyCryptDecrypt等。

  4. 错误处理:检查每个Crypto API函数调用是否返回了错误,如果有错误,应该正确地处理它们。

请注意,在修改代码之前,务必备份你的数据,以免意外丢失。希望这能帮到你解决问题。

英文:

I am attempting to decompression AES-256 encrypted files in Delphi using TZipFile and Microsoft Crypto. It always returns a bad data error and returns gibberish.

Background...

I am using the standard TZipFile class to open the zip, and have registered my compression handler as follows:

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

Which uses the following method:

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;

The code for the AES decryption is as follows:

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;

This code is based on:

https://gist.github.com/hasherezade/2860d94910c5c5fb776edadf57f0bef6

Which, while it seems a bit dubious, was the only example I could find and does claim to work (with AES-128).

However, the code raises an error on the CryptDecrypt() function:

> Project Project1.exe raised exception class EOSError with message 'System Error. Code: -2146893819. Bad Data.'

Based on the requirements laid out in the Winzip documentation:

https://www.winzip.com/en/support/aes-encryption/

There are some obvious failings:

  • It lacks a salt value (16 bytes for AES-256), which I take to be the first 16 bytes of the file

  • It does not perform 1000 iterations of the key generation

However, I cannot see how these changes should be implemented using the Crypto API. Potentially, this is not even possible? Any suggestions?

答案1

得分: 2

For 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);

英文:

For AES-256, AES_KEY_SIZE needs to be 32. The code you linked to is using AES-128, which uses a key size of 16.

Also, your call to CryptHashData() is wrong. You need to drop the @ and use a PWideChar cast instead:

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


Also, not an error, but you really should be using UnicodeString instead of WideString. And since string is UnicodeString, you don't need the keyStr variable at all, just use the Password as-is:

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

huangapple
  • 本文由 发表于 2023年3月23日 12:12:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75819195.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定