我正在尝试使用
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 来实现。潜在地,这甚至不可能?有什么建议吗?
对于 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);