如何在Delphi中使用密码对AES-128加密字符串并在C#中解密?

我想用AES-128用密码加密Delphi中的字符串。 我想将它上传到我的服务器,并能够在C#中使用相同的密码进行解密。

在Delphi中,我使用的是TurboPower LockBox 3:

function EncryptText_AES_128(input: string; password: string): string; var Codec: TCodec; CipherText: AnsiString; begin Codec := TCodec.Create(nil); try Codec.CryptoLibrary := TCryptographicLibrary.Create(Codec); // Codec.StreamCipherId := BlockCipher_ProgID; Codec.BlockCipherId := Format(AES_ProgId, [128]); Codec.ChainModeId := CBC_ProgId; // Codec.Password := Password; Codec.EncryptString(input, CipherText); // Result := string(CipherText); finally Codec.Free; end; end; 

如何在C#中解密生成的字符串? 我可以改变Delphi代码。 什么都没有生产。 我甚至没有坚持使用LockBox。 但是,我想避免将它放在P / Invoke的DLL中。

(我的例子显示我的加密序列本身就是一个字符串。这对我来说不是必需的。字节流很好。)

我终于找到了Delphi和C#之间用于AES-128的兼容解决方案。 它也适用于Wine。 这是我的Delphi代码:

 unit TntLXCryptoUtils; interface function AES128_Encrypt(Value, Password: string): string; function AES128_Decrypt(Value, Password: string): string; implementation uses SysUtils, Windows, IdCoderMIME, TntLXUtils; //------------------------------------------------------------------------------------------------------------------------- // Base64 Encode/Decode //------------------------------------------------------------------------------------------------------------------------- function Base64_Encode(Value: TBytes): string; var Encoder: TIdEncoderMIME; begin Encoder := TIdEncoderMIME.Create(nil); try Result := Encoder.EncodeBytes(Value); finally Encoder.Free; end; end; function Base64_Decode(Value: string): TBytes; var Encoder: TIdDecoderMIME; begin Encoder := TIdDecoderMIME.Create(nil); try Result := Encoder.DecodeBytes(Value); finally Encoder.Free; end; end; //------------------------------------------------------------------------------------------------------------------------- // WinCrypt.h //------------------------------------------------------------------------------------------------------------------------- type HCRYPTPROV = Cardinal; HCRYPTKEY = Cardinal; ALG_ID = Cardinal; HCRYPTHASH = Cardinal; const _lib_ADVAPI32 = 'ADVAPI32.dll'; CALG_SHA_256 = 32780; CALG_AES_128 = 26126; CRYPT_NEWKEYSET = $00000008; PROV_RSA_AES = 24; KP_MODE = 4; CRYPT_MODE_CBC = 1; function CryptAcquireContext(var Prov: HCRYPTPROV; Container: PChar; Provider: PChar; ProvType: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptAcquireContextW'; function CryptDeriveKey(Prov: HCRYPTPROV; Algid: ALG_ID; BaseData: HCRYPTHASH; Flags: LongWord; var Key: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDeriveKey'; function CryptSetKeyParam(hKey: HCRYPTKEY; dwParam: LongInt; pbData: PBYTE; dwFlags: LongInt): LongBool stdcall; stdcall; external _lib_ADVAPI32 name 'CryptSetKeyParam'; function CryptEncrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt; BufLen: LongInt): LongBool;stdcall;external _lib_ADVAPI32 name 'CryptEncrypt'; function CryptDecrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDecrypt'; function CryptCreateHash(Prov: HCRYPTPROV; Algid: ALG_ID; Key: HCRYPTKEY; Flags: LongWord; var Hash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptCreateHash'; function CryptHashData(Hash: HCRYPTHASH; Data: PChar; DataLen: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptHashData'; function CryptReleaseContext(hProv: HCRYPTPROV; dwFlags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptReleaseContext'; function CryptDestroyHash(hHash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyHash'; function CryptDestroyKey(hKey: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyKey'; //------------------------------------------------------------------------------------------------------------------------- {$WARN SYMBOL_PLATFORM OFF} function __CryptAcquireContext(ProviderType: Integer): HCRYPTPROV; begin if (not CryptAcquireContext(Result, nil, nil, ProviderType, 0)) then begin if HRESULT(GetLastError) = NTE_BAD_KEYSET then Win32Check(CryptAcquireContext(Result, nil, nil, ProviderType, CRYPT_NEWKEYSET)) else RaiseLastOSError; end; end; function __AES128_DeriveKeyFromPassword(m_hProv: HCRYPTPROV; Password: string): HCRYPTKEY; var hHash: HCRYPTHASH; Mode: DWORD; begin Win32Check(CryptCreateHash(m_hProv, CALG_SHA_256, 0, 0, hHash)); try Win32Check(CryptHashData(hHash, PChar(Password), Length(Password) * SizeOf(Char), 0)); Win32Check(CryptDeriveKey(m_hProv, CALG_AES_128, hHash, 0, Result)); // Wine uses a different default mode of CRYPT_MODE_EBC Mode := CRYPT_MODE_CBC; Win32Check(CryptSetKeyParam(Result, KP_MODE, Pointer(@Mode), 0)); finally CryptDestroyHash(hHash); end; end; function AES128_Encrypt(Value, Password: string): string; var hCProv: HCRYPTPROV; hKey: HCRYPTKEY; lul_datalen: Integer; lul_buflen: Integer; Buffer: TBytes; begin Assert(Password <> ''); if (Value = '') then Result := '' else begin hCProv := __CryptAcquireContext(PROV_RSA_AES); try hKey := __AES128_DeriveKeyFromPassword(hCProv, Password); try // allocate buffer space lul_datalen := Length(Value) * SizeOf(Char); Buffer := TEncoding.Unicode.GetBytes(Value + ' '); lul_buflen := Length(Buffer); // encrypt to buffer Win32Check(CryptEncrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen, lul_buflen)); SetLength(Buffer, lul_datalen); // base 64 result Result := Base64_Encode(Buffer); finally CryptDestroyKey(hKey); end; finally CryptReleaseContext(hCProv, 0); end; end; end; function AES128_Decrypt(Value, Password: string): string; var hCProv: HCRYPTPROV; hKey: HCRYPTKEY; lul_datalen: Integer; Buffer: TBytes; begin Assert(Password <> ''); if Value = '' then Result := '' else begin hCProv := __CryptAcquireContext(PROV_RSA_AES); try hKey := __AES128_DeriveKeyFromPassword(hCProv, Password); try // decode base64 Buffer := Base64_Decode(Value); // allocate buffer space lul_datalen := Length(Buffer); // decrypt buffer to to string Win32Check(CryptDecrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen)); Result := TEncoding.Unicode.GetString(Buffer, 0, lul_datalen); finally CryptDestroyKey(hKey); end; finally CryptReleaseContext(hCProv, 0); end; end; end; end. 

这是我的C#代码:

 public class TntCryptoUtils { private static ICryptoTransform __Get_AES128_Transform(string password, bool AsDecryptor) { const int KEY_SIZE = 16; var sha256CryptoServiceProvider = new SHA256CryptoServiceProvider(); var hash = sha256CryptoServiceProvider.ComputeHash(Encoding.Unicode.GetBytes(password)); var key = new byte[KEY_SIZE]; var iv = new byte[KEY_SIZE]; Buffer.BlockCopy(hash, 0, key, 0, KEY_SIZE); //Buffer.BlockCopy(hash, KEY_SIZE, iv, 0, KEY_SIZE); // On the Windows side, the IV is always 0 (zero) // if (AsDecryptor) return new AesCryptoServiceProvider().CreateDecryptor(key, iv); else return new AesCryptoServiceProvider().CreateEncryptor(key, iv); } public static string AES128_Encrypt(string Value, string Password) { byte[] Buffer = Encoding.Unicode.GetBytes(Value); // using (ICryptoTransform transform = __Get_AES128_Transform(Password, false)) { byte[] encyptedBlob = transform.TransformFinalBlock(Buffer, 0, Buffer.Length); return Convert.ToBase64String(encyptedBlob); } } public static string AES128_Decrypt(string Value, string Password) { byte[] Buffer = Convert.FromBase64String(Value); // using (ICryptoTransform transform = __Get_AES128_Transform(Password, true)) { byte[] decyptedBlob = transform.TransformFinalBlock(Buffer, 0, Buffer.Length); return Encoding.Unicode.GetString(decyptedBlob); } } } 

与您可能阅读的任何巨魔火焰诱饵相反,LockBox 3实际上是一个高质量的加密库。 LB3的标准符合性是不可避免的。 与其他语言和库的互操作性可能存在问题的地方与标准之外的选项有关。 如果在Delphi端使用Lockbox,那么您只需要确保在另一种语言中以相同的方式处理这些选项。 如果这不可能,那么您应该选择另一个库。 我将在下面处理以下每个选项。

替代解决方案(OpenSSL,CryptoAPI和Eldos)没有任何问题。 其中一些可能是黑盒子。 这可能是一些人的问题。

  1. 将密码转换为密钥。 AES-128使用16字节密钥。 此外,从“密钥数据”或“密码数据”生成密钥的标准机制本身基于16字节输入种子。 互操作性从Delphi端的字符串密码生成二进制密钥更安全,只是将二进制密钥传输到另一端,而不是传输字符串密码。 这是因为将字符串密码转换为二进制16字节密钥的算法超出了AES标准。 无论如何,你可以这样做。 当给lockbox一个字符串密码来初始化AES-128编解码器时,它会将字符串有效负载视为一个字节数组。 如果有效载荷恰好是16个字节,那么很好,它可以直接传递给AES密钥生成算法,该算法在标准中指定。 如果字符串有效负载不是精确的16字节,则有效负载将使用SHA-1进行散列,以产生20字节的散列输出。 然后将此哈希的低16字节传递给标准AES密钥生成函数。 因此,您确保与密钥初始化相关的互操作性的选项是:

    1.1。 传输二进制密钥而不是字符串密码。

    1.2。 如果选项1.2太不方便,则传输密码,但在另一端模仿相同的密码到密钥算法。

    1.3。 如果1和2由于某种原因不起作用,请尝试将密码限制为恰好16个字节(8个UTF-8字符或16个UTF-16代码点)。 如果另一种语言的实现方式不合适,这应该是非常安全的。

  2. UTF-16与ansi-string / UTF-8密码这不是一个选项,而是一个年轻球员的陷阱。 我们程序员倾向于将“字符串”视为“字符串”。 但事实并非如此。 在Delphi 2010中,字符串的有效负载以UTF-16LE编码存储,代码单元大小为2个字节。 但是在其他语言中,例如PHP和python,在默认模式下,字符串是单字节代码单元编码,UTF-8或基于MS windows代码页基础的东西(MS称为“ansistring”)。 值得注意的是,’mypassword’的UTF-16编码与UTF-8’mypassword’不同。

  3. IV设置。 AES标准没有涉及如何设置编解码器’初始化向量(IV)的问题。 IV的大小与底层块的大小相同。 对于AES,这是128位或16字节。 加密时,密码箱会创建一个16字节的nonce。 该随机数变为IV的值,并且在密文消息的头部以明文forms发出。 阅读另一方关于IV初始化的方法/策略的文档。 你的选择是:

    3.1如果对方将IV预先设置为密文,那么你就是甜蜜的。

    3.2否则,另一方面,在解密时,自己读取密文的前16个字节,并将余数传递给外部编解码器。 在解密之前,告诉外国编解码器IV是什么(假设它的API能够做到这一点)。

  4. 块量化AES块大小为16个字节。 当明文消息不是一个完整的多个16字节时,必须采取措施使其成为一个整数倍。 此过程称为块量化,不在标准中处理,而是留给实现。 许多实现将使用块填充。 没有标准的块填充方案,有很多可供选择。 LockBox不为CBC使用块填充(其他模式可能是不同的情况)。 如果明文是整数个块,则不需要或不进行量化,否则使用标准CipherText窃取。 如果明文大小非常小(1到15个字节之间),则不可能进行密文窃取,而是使用填充方案。 为确保与块量化相关​​的互操作性,您的选择是:

    4.1检查文档中有关块量化的外部编解码器(它可能位于“消息填充”标题下)。 如果外国编解码器使用密文窃取,那么你很好(只是确保没有短消息)。

    4.2否则你可以做自己的填充。 在密码箱方面,密码箱对已经存在于整个块中的消息不执行任何操作。 很可能外国编解码器具有相同的策略 – 但您还需要检查外部编解码器的文档。

  • 不要使用LockBox 3.它不是一个高质量的库。
  • 不要将加密数据返回“文本”字符串。 加密数据是字节的任意序列,而不是字符串(作为文本数据)。 Delphi使用“长度控制”字符串并且可以存储几乎任何它的下边缘,但可能会遇到问题,这些字符串包含可能被其他语言错误解释的字节序列,即C / C ++应用程序的$ 00。 )。 如果库本身使用字符串,那么它就是一个低质量库的症状
  • 不要转换加密数据! 当您将加密的ANSIString转换为Unicode时(我猜这是您上次演员的原因),您正在销毁加密值。 如果你传递该字符串,除非应用反向转换,否则它将不会被解密,只要它不是“有损”。

我刚遇到同样的问题。 我知道这是一个古老的话题,但它给了我很多帮助。 我只是把它留在这里备案。

 Function LockBoxDecrypt(Password As String, Data() As Byte) As String Dim AesProvider = AesCryptoServiceProvider.Create() Dim IV(15) As Byte, PaddedData(15) As Byte Array.Copy(Data, 0, IV, 0, 8) Array.Copy(Data, 8, PaddedData, 0, Data.Length - 8) AesProvider.Key = SHA1.Create().ComputeHash(Encoding.Default.GetBytes(Password)).Take(16).ToArray() AesProvider.IV = IV AesProvider.Mode = CipherMode.CFB AesProvider.Padding = PaddingMode.None Return Encoding.Default.GetString(AesProvider.CreateDecryptor().TransformFinalBlock(PaddedData, 0, PaddedData.Length), 0, Data.Length - 8) End Function 

基于肖恩的答案,我假设当模块超过1时,模式应该更改为CTS。 我没有尝试过,因为1块对我来说足够了,但应该很容易调整代码。

我能够在10.2东京成功实现Troy的Delphi代码并进行了一些修改。

我从用途中删除了TNTLxUtils,因为它不需要(我没有它)并添加了IdGlobal。 使用IdGlobal的原因是您需要在Base64_Encode函数中将TBytes类型转换为TIdBytes,并在Base64_Decode中将TIBytes转换回TBytes。

注意:此单元仅适用于32位应用程序,因为它引用了32位Windows API。

谢谢,特洛伊指出我正确的方向,采用免费的加密方法,不需要购买工具包来实现。

 unit CryptoUtils; interface function AES128_Encrypt(Value, Password: string): string; function AES128_Decrypt(Value, Password: string): string; implementation uses SysUtils, Windows, IdCoderMIME, IdGlobal; //------------------------------------------------------------------------------------------------------------------------- // Base64 Encode/Decode //------------------------------------------------------------------------------------------------------------------------- function Base64_Encode(Value: TBytes): string; var Encoder: TIdEncoderMIME; begin Encoder := TIdEncoderMIME.Create(nil); try Result := Encoder.EncodeBytes(TIdBytes(Value)); finally Encoder.Free; end; end; function Base64_Decode(Value: string): TBytes; var Encoder: TIdDecoderMIME; begin Encoder := TIdDecoderMIME.Create(nil); try Result := TBytes(Encoder.DecodeBytes(Value)); finally Encoder.Free; end; end; //------------------------------------------------------------------------------------------------------------------------- // WinCrypt.h //------------------------------------------------------------------------------------------------------------------------- type HCRYPTPROV = Cardinal; HCRYPTKEY = Cardinal; ALG_ID = Cardinal; HCRYPTHASH = Cardinal; const _lib_ADVAPI32 = 'ADVAPI32.dll'; CALG_SHA_256 = 32780; CALG_AES_128 = 26126; CRYPT_NEWKEYSET = $00000008; PROV_RSA_AES = 24; KP_MODE = 4; CRYPT_MODE_CBC = 1; function CryptAcquireContext(var Prov: HCRYPTPROV; Container: PChar; Provider: PChar; ProvType: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptAcquireContextW'; function CryptDeriveKey(Prov: HCRYPTPROV; Algid: ALG_ID; BaseData: HCRYPTHASH; Flags: LongWord; var Key: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDeriveKey'; function CryptSetKeyParam(hKey: HCRYPTKEY; dwParam: LongInt; pbData: PBYTE; dwFlags: LongInt): LongBool stdcall; stdcall; external _lib_ADVAPI32 name 'CryptSetKeyParam'; function CryptEncrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt; BufLen: LongInt): LongBool;stdcall;external _lib_ADVAPI32 name 'CryptEncrypt'; function CryptDecrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDecrypt'; function CryptCreateHash(Prov: HCRYPTPROV; Algid: ALG_ID; Key: HCRYPTKEY; Flags: LongWord; var Hash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptCreateHash'; function CryptHashData(Hash: HCRYPTHASH; Data: PChar; DataLen: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptHashData'; function CryptReleaseContext(hProv: HCRYPTPROV; dwFlags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptReleaseContext'; function CryptDestroyHash(hHash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyHash'; function CryptDestroyKey(hKey: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyKey'; //------------------------------------------------------------------------------------------------------------------------- {$WARN SYMBOL_PLATFORM OFF} function __CryptAcquireContext(ProviderType: Integer): HCRYPTPROV; begin if (not CryptAcquireContext(Result, nil, nil, ProviderType, 0)) then begin if HRESULT(GetLastError) = NTE_BAD_KEYSET then Win32Check(CryptAcquireContext(Result, nil, nil, ProviderType, CRYPT_NEWKEYSET)) else RaiseLastOSError; end; end; function __AES128_DeriveKeyFromPassword(m_hProv: HCRYPTPROV; Password: string): HCRYPTKEY; var hHash: HCRYPTHASH; Mode: DWORD; begin Win32Check(CryptCreateHash(m_hProv, CALG_SHA_256, 0, 0, hHash)); try Win32Check(CryptHashData(hHash, PChar(Password), Length(Password) * SizeOf(Char), 0)); Win32Check(CryptDeriveKey(m_hProv, CALG_AES_128, hHash, 0, Result)); // Wine uses a different default mode of CRYPT_MODE_EBC Mode := CRYPT_MODE_CBC; Win32Check(CryptSetKeyParam(Result, KP_MODE, Pointer(@Mode), 0)); finally CryptDestroyHash(hHash); end; end; function AES128_Encrypt(Value, Password: string): string; var hCProv: HCRYPTPROV; hKey: HCRYPTKEY; lul_datalen: Integer; lul_buflen: Integer; Buffer: TBytes; begin Assert(Password <> ''); if (Value = '') then Result := '' else begin hCProv := __CryptAcquireContext(PROV_RSA_AES); try hKey := __AES128_DeriveKeyFromPassword(hCProv, Password); try // allocate buffer space lul_datalen := Length(Value) * SizeOf(Char); Buffer := TEncoding.Unicode.GetBytes(Value + ' '); lul_buflen := Length(Buffer); // encrypt to buffer Win32Check(CryptEncrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen, lul_buflen)); SetLength(Buffer, lul_datalen); // base 64 result Result := Base64_Encode(Buffer); finally CryptDestroyKey(hKey); end; finally CryptReleaseContext(hCProv, 0); end; end; end; function AES128_Decrypt(Value, Password: string): string; var hCProv: HCRYPTPROV; hKey: HCRYPTKEY; lul_datalen: Integer; Buffer: TBytes; begin Assert(Password <> ''); if Value = '' then Result := '' else begin hCProv := __CryptAcquireContext(PROV_RSA_AES); try hKey := __AES128_DeriveKeyFromPassword(hCProv, Password); try // decode base64 Buffer := Base64_Decode(Value); // allocate buffer space lul_datalen := Length(Buffer); // decrypt buffer to to string Win32Check(CryptDecrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen)); Result := TEncoding.Unicode.GetString(Buffer, 0, lul_datalen); finally CryptDestroyKey(hKey); end; finally CryptReleaseContext(hCProv, 0); end; end; end; end. 
Interesting Posts