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

Tro*_*roy 4 c# delphi encryption turbopower lockbox-3

我想用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;
Run Code Online (Sandbox Code Playgroud)

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

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

Tro*_*roy 15

我终于找到了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.
Run Code Online (Sandbox Code Playgroud)

这是我的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Sea*_*kin 10

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

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

  1. 将密码转换为密钥.AES-128使用16字节密钥.此外,从"密钥数据"或"密码数据"生成密钥的标准机制本身基于16字节输入种子.互操作性从Delphi端的字符串密码生成二进制密钥更安全,只是将二进制密钥传输到另一端,而不是传输字符串密码.这是因为将字符串密码转换为二进制16字节密钥的算法超出了AES标准.无论如何,你可以这样做.当给密码箱一个字符串密码来初始化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的值,并且在密文消息的头部以明文形式发出.阅读另一方关于IV初始化的方法/策略的文档.你的选择是:

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

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

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

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

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


小智 5

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


小智 5

通过一些修改,我能够在 10.2 Tokyo 中成功实现 Troy 的 Delphi 代码。

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

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

谢谢,Troy 为我指明了正确的方向,提供了一种无需购买工具包即可实现的免费加密方法。

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.
Run Code Online (Sandbox Code Playgroud)