DEC AES加密输出与固件/其他工具不一致问题排查
Delphi DEC套件AES加密与第三方工具/设备解密不匹配问题
使用Delphi的DEC加密套件编写固件AES加密代码,需与小型设备自带AES框架的解密逻辑同步,但输出与固件工具、在线AES工具无法匹配:
- 密钥、IV全为0时,DEC输出仅与其他工具存在UINT32字节序差异;
- 密钥含非0值(如
$1, $0, $0, $0)时,输出完全不符,调整位序操作无效。
预期输出
固件工具及在线工具的加密输出为:D5 AA 45 05 7C A9 A2 6D 43 D1 63 36 84 1C 3D 2A
所用代码
program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, DECBaseClass, DECCipherBase, DECHash, DECCiphers, DECTypes, DECFormatBase, DECFormat; type Binary = RawByteString; var ACipherClass: TDECCipherClass = TCipher_AES; ACipherMode: TCipherMode = cmCBCx; ATextFormat: TDECFormatClass = TFormat_Mime64; InvertUINT32 : boolean = False; InvertText : boolean = False; type //TMK6AESTextSize = Array[0..15] of UInt32; TMK6AESTextSize = Array[0..3] of UInt32; TMK6Key = Array[0..3] of UINT32; const mk6Key : TMK6Key = ($1, $0, $0, $0); //mk6Key : TMK6Key = ($09CF4F3C, $ABF71588, $28AED2A6, $2B7E1516 ); // rotated //mk6IV : TMK6Key = ($00010203 , $04050607 , $08090A0B , $0C0D0E0F); mk6IV : TMK6Key = ($0 , $0 , $0 , $0); //mk6IV : TMK6Key = ($0C0D0E0F, $08090A0B, $04050607, $00010203); // mk6Plaintext : TMK6AESTextSize = // ( $E2BEC16B ,$969F402E ,$117E3DE9 ,$2A179373 , // $578A2DAE ,$9CAC031E ,$AC6FB79E ,$518EAF45 , // $461CC830 ,$11E45CA3 ,$19C1FBE5 ,$EF520A1A , // $45249FF6 ,$179B4FDF ,$7B412BAD ,$10376CE6 ); // mk6Plaintext : TMK6AESTextSize = // ( $E2BEC16B ,$969F402E ,$117E3DE9 ,$2A179373 ); //mk6Plaintext : TMK6AESTextSize = // ( $2A179373, $117E3DE9, $969F402E, $E2BEC16B ); mk6Plaintext : TMK6AESTextSize = ( $0, $0, $0, $0 ); type TUINT32Byte = Array[0..3] of Byte; PUINT32Byte = ^TUINT32Byte; function InvUINT32( value : UINT32 ) : UINT32; var v1, v2 : PUINT32Byte; begin v1 := @value; v2 := @Result; v2^[3] := v1^[0]; v2^[2] := v1^[1]; v2^[1] := v1^[2]; v2^[0] := v1^[3]; end; function GenMK6Text( const txt : TMK6AESTextSize ) : TMK6AESTextSize; var i : integer; begin if InvertText then begin for i := 0 to High(txt) do Result[i] := InvUINT32(txt[i]); end else Result := txt; end; function GenMK6Key( const aKey : TMK6Key ) : TMK6Key; var i : integer; begin Result := aKey; if InvertUINT32 then begin for i := 0 to High(aKey) do Result[i] := InvUINT32(aKey[i]); end; end; function EncryptBin(const buf : TMK6AESTextSize): Binary; var AData: Binary; cipher : TCipher_AES128; //AES aKey : TMK6Key; aIV : TMK6Key; aBuf : TMK6AESTextSize; begin cipher := TCipher_AES128.Create; try cipher.Mode := ACipherMode; aKey := GenMK6Key(mk6Key); aIV := GenMK6Key(mk6IV); aBuf := GenMK6Text(buf); cipher.Init( aKey, sizeof(mk6key), aIV, sizeof(mk6IV) ); SetLength(AData, sizeof(buf)); cipher.Encode(aBuf, AData[1], Length(AData)); Result := AData; finally cipher.Free; end; end; function DecryptBin(buf : Binary): TMK6AESTextSize; var AData: Binary; //ACheck: Binary; //APass: Binary; ALen: Integer; ARes : Binary; cipher : TCipher_AES128; aKey : TMK6Key; aIV : TMK6Key; begin cipher := TCipher_AES128.Create; try //AData := ValidFormat(ATextFormat).Decode(AText); AData := buf; ALen := Length(AData); // - cipher.Context.BufferSize; //ALen := Length(AData); // - cipher.Context.BufferSize; //ACheck := System.Copy(Adata, ALen + 1, cipher.Context.BufferSize); cipher.Mode := ACipherMode; aKey := GenMK6Key(mk6Key); aIV := GenMK6Key(mk6IV); cipher.Init(aKey, sizeof(mk6key), aIV, sizeof(mk6IV)); SetLength(ARes, ALen); cipher.Decode(AData[1], ARes[1], ALen); //if ACheck <> cipher.CalcMAC then // raise Exception.Create('Invalid data'); Assert(Length(ARes) >= sizeof(Result), 'WTF'); Move(ARes[1], Result, sizeof(Result)); Result := GenMK6Text(Result); finally cipher.Free; end; end; var binBuf : Binary; decBuf : TMK6AESTextSize; i : integer; begin try binBuf := EncryptBin(mk6Plaintext); for i := 1 to Length(binBuf) do begin Write( IntToHex( Ord(binBuf[i]), 2 ) + ' ' ); end; Writeln; decBuf := DecryptBin(binBuf); for i := 0 to Length(decBuf) - 1 do Writeln( IntToHex( decBuf[i], 4 ) ); readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
问题原因分析
- DEC的CBC模式变种差异:代码中使用
cmCBCx模式,这是DEC套件特有的带MAC或填充的CBC变种,而固件和在线工具使用的是标准CBC模式,两者逻辑不一致导致输出差异。 - 密钥的双重字节序处理缺失:当密钥非0时,仅反转单个UINT32的字节序不够——DEC可能会将UINT32数组按内存顺序直接拼接成密钥,而设备/在线工具可能要求UINT32数组整体反转后再拼接。比如密钥
($1, $0, $0, $0),DEC直接用内存字节是01 00 00 00 00 00 00 00 ...,而设备可能需要00 00 00 00 00 00 00 00 ... 00 00 00 01。 - 明文/密钥的内存传递方式:直接将UINT32数组传递给DEC的
Init和Encode方法,DEC对内存的解析顺序可能与设备预期不符,应该先手动转换为符合要求的字节数组再传递。
修正建议
- 切换为标准CBC模式:将
ACipherMode从cmCBCx改为cmCBC,确保与标准AES逻辑对齐。 - 完整处理密钥/明文的字节序:
// 示例:将UINT32数组转换为设备要求的字节数组 function KeyToBytes(const Key: TMK6Key): RawByteString; var I: Integer; B: TUINT32Byte; begin Result := ''; // 先反转UINT32数组的顺序,再反转每个UINT32的字节序 for I := High(Key) downto Low(Key) do begin Move(Key[I], B, SizeOf(UINT32)); Result := Result + Chr(B[3]) + Chr(B[2]) + Chr(B[1]) + Chr(B[0]); end; end; - 直接传递字节数组:在
EncryptBin中,将明文、密钥、IV都转换为RawByteString后,再调用DEC的方法,避免内存解析差异。
内容的提问来源于stack exchange,提问作者mrabat
相关产品推荐
相关产品推荐

