简体   繁体   English

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

[英]How to AES-128 encrypt a string using a password in Delphi and decrypt in C#?

I'd like to AES-128 encrypt a string in Delphi with a password. 我想用AES-128用密码加密Delphi中的字符串。 I'd like to upload this to my server and be able to decrypt given the same password in C#. 我想将它上传到我的服务器,并能够在C#中使用相同的密码进行解密。

In Delphi, I'm using TurboPower LockBox 3: 在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;

How can I decrypt the resulting string in C#? 如何在C#中解密生成的字符串? I can change the Delphi code. 我可以改变Delphi代码。 Nothing is in production yet. 什么都没有生产。 I'm not even stuck on using LockBox. 我甚至没有坚持使用LockBox。 But, I would like to avoid putting this in a DLL for P/Invoke. 但是,我想避免将它放在P / Invoke的DLL中。

(My example shows that my encrypted sequence is itself a string. This is not a requirement for me. A stream of bytes is fine.) (我的例子表明我的加密序列本身就是一个字符串。这对我来说不是必需的。字节流很好。)

I finally found a compatible solution between Delphi and C# for AES-128. 我终于在Delphi和C#之间找到了AES-128的兼容解决方案。 It's also works on Wine. 它也适用于Wine。 Here's my Delphi code: 这是我的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.

And here's my C# code: 这是我的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);
        }
    }
}

Contrary to any troll flame-bait that you might read, LockBox 3 is actually a good quality cryptographic library. 与您可能阅读的任何巨魔火焰诱饵相反,LockBox 3实际上是一个高质量的加密库。 The standards compliance of LB3 is impecable. LB3的标准符合性是不可避免的。 Where you might have problems with interoperability with other languages & libraries is in relation to options that are outside of the standard. 与其他语言和库的互操作性可能存在问题的地方与标准之外的选项有关。 If using Lockbox on the Delphi side, then you just need to make sure that these options are handled the same way on the other language's side. 如果在Delphi端使用Lockbox,那么您只需要确保在另一种语言中以相同的方式处理这些选项。 If this is not possible, then you should choose another library. 如果这不可能,那么您应该选择另一个库。 I will deal with each of these options below. 我将在下面处理以下每个选项。

There is nothing wrong with the alternative solutions (OpenSSL, CryptoAPI and Eldos). 替代解决方案(OpenSSL,CryptoAPI和Eldos)没有任何问题。 Some of them may be black-box. 其中一些可能是黑盒子。 This might be an issue for some peoople. 这可能是一些人的问题。

  1. Converting password to key. 将密码转换为密钥。 AES-128 uses a 16 byte key. AES-128使用16字节密钥。 Also the standard mechanism to generate a key from "key data" or "password data" is natively based on a 16 byte input seed. 此外,从“密钥数据”或“密码数据”生成密钥的标准机制本身基于16字节输入种子。 It is safer for interoperability to generate the binary key from the string password on the Delphi side, and just transport the binary key to the other side, rather than transport the string password. 互操作性从Delphi端的字符串密码生成二进制密钥更安全,只是将二进制密钥传输到另一端,而不是传输字符串密码。 This is because the algorithm to convert a string password to a binary 16-byte key is outside the AES standard. 这是因为将字符串密码转换为二进制16字节密钥的算法超出了AES标准。 Nether-the-less, you can do it either way. 无论如何,你可以这样做。 When lockbox is given a string password to initialize an AES-128 codec, it looks at the string payload as an array of bytes. 当给密码箱一个字符串密码来初始化AES-128编解码器时,它会将字符串有效负载视为一个字节数组。 If the payload is precisely 16 bytes, then great, it can be passed directly to the AES key generation algorithm, which is specified in the standard. 如果有效载荷恰好是16个字节,那么很好,它可以直接传递给AES密钥生成算法,该算法在标准中指定。 If the string payload is not precisely 16 bytes, then payload will be hashed with SHA-1 to produce a 20 byte hash output. 如果字符串有效负载不是精确的16字节,那么将使用SHA-1对有效负载进行散列以产生20字节的散列输出。 The low 16 bytes of this hash are then passed to the standard AES key generation function. 然后将此哈希的低16字节传递给标准AES密钥生成函数。 So, your options for ensuring interoperability in relation to key initialization are: 因此,您确保与密钥初始化相关的互操作性的选项是:

    1.1. 1.1。 Transport binary keys instead of string passwords. 传输二进制密钥而不是字符串密码。

    1.2. 1.2。 If Option 1.2 is too inconvenient, then transport the password, but mimic the same password-to-key algorithm on the other side. 如果选项1.2太不方便,则传输密码,但在另一端模仿相同的密码到密钥算法。

    1.3. 1.3。 If 1 & 2 are not working for some reason, try to restrict passwords to exactly 16 bytes (8 UTF-8 characters or 16 UTF-16 code-points). 如果1和2由于某种原因不起作用,请尝试将密码限制为恰好16个字节(8个UTF-8字符或16个UTF-16代码点)。 This should be pretty safe if the other language's implementation is half decent. 如果另一种语言的实现方式不合适,这应该是非常安全的。

  2. UTF-16 versus ansi-string/UTF-8 passwords This is not so much an option, but a trap for young players. UTF-16与ansi-string / UTF-8密码这不是一个选项,而是年轻球员的陷阱。 We programmers tend to think of "strings" as "strings". 我们程序员倾向于将“字符串”视为“字符串”。 But it is not so. 但事实并非如此。 In Delphi 2010, the payload of strings are stored in a UTF-16LE encoding with a code-unit size of 2 bytes. 在Delphi 2010中,字符串的有效负载以UTF-16LE编码存储,代码单元大小为2个字节。 But in other languages, such as PHP and python, in the default mode, strings are single-byte code-unit encodings, either UTF-8 or something based on an MS windows code-page base (which MS calls "ansistring"). 但在其他语言中,例如PHP和python,在默认模式下,字符串是单字节代码单元编码,UTF-8或基于MS windows代码页基础的东西(MS称为“ansistring”)。 It pays to remember than UTF-16 encoding of 'mypassword' is not the same as UTF-8 'mypassword'. 值得注意的是,'mypassword'的UTF-16编码与UTF-8'mypassword'不同。

  3. IV setup. IV设置。 The AES standard does not deal with the question of how to set up the codec' Initialization Vector (IV). AES标准没有涉及如何设置编解码器'初始化向量(IV)的问题。 The size of the IV is the same as the size of the underlying block. IV的大小与底层块的大小相同。 For AES this is 128 bits or 16 bytes. 对于AES,这是128位或16字节。 When encrypting, lockbox creates a 16 byte nonce. 加密时,密码箱会创建一个16字节的nonce。 This nonce becomes the value of the IV, and it is emitted in the clear at the head of the ciphertext message. 该随机数变为IV的值,并且在密文消息的头部以明文形式发出。 Read the documentation on the other side's method/policy for IV initialization. 阅读另一方关于IV初始化的方法/策略的文档。 Your options are: 你的选择是:

    3.1 If the other side prepends the IV to the ciphertext, then you are sweet. 3.1如果对方将IV预先设置为密文,那么你就是甜蜜的。

    3.2 Otherwise, on the other side, when decrypting, read the first 16 bytes of the ciphertext yourself, and pass the remainder to the foreign codec. 3.2否则,另一方面,在解密时,自己读取密文的前16个字节,并将余数传递给外部编解码器。 Before decryption, tell you foreign codec what the IV is (assuming it's API is capable of this). 在解密之前,告诉外国编解码器IV是什么(假设它的API能够做到这一点)。

  4. Block quantisation The AES block size is 16 bytes. 块量化AES块大小为16个字节。 When the plaintext message is not precisely a whole multiple 16 bytes, something must be done to make it a whole multiple. 当明文消息不是一个完整的多个16字节时,必须做一些事情才能使它成为一个整数倍。 This procedure is called block quantisation and is not dealt with in the standard, but left up to the implementation. 此过程称为块量化,不在标准中处理,而是留给实现。 Many implementations will use block padding. 许多实现将使用块填充。 There is no standard block padding scheme and there are many to choose from. 没有标准的块填充方案,有很多可供选择。 LockBox does not use block padding for CBC (other modes may be a different case). LockBox不为CBC使用块填充(其他模式可能是不同的情况)。 If the plaintext is a whole number of blocks, no quantisation is needed or done, otherwise standard CipherText stealing is used. 如果明文是整数个块,则不需要或不进行量化,否则使用标准CipherText窃取。 If the plaintext size is very small (between 1 and 15 bytes) ciphertext stealing is not possible, and a padding scheme is used instead. 如果明文大小非常小(1到15个字节之间),则不可能进行密文窃取,而是使用填充方案。 To ensure interoperability in relation to block quantisation, your options are: 为确保与块量化相关​​的互操作性,您的选择是:

    4.1 Check your documentation for the foreign codec in relation to block quantisation (it may come under the heading of "message padding"). 4.1检查文档中有关块量化的外部编解码器(它可能位于“消息填充”标题下)。 If the foreign codec uses ciphertext stealing, then you are sweet (just make sure no short messages). 如果外国编解码器使用密文窃取,那么你很好(只是确保没有短消息)。

    4.2 Otherwise you could do your own padding. 4.2否则你可以做自己的填充。 On the lockbox side, lockbox does nothing to messages that are already in whole blocks. 在密码箱方面,密码箱对已经存在于整个块中的消息不执行任何操作。 Very probably the foreign codec has the same policy - but again you need to check the documentation for the foreign codec. 很可能外国编解码器具有相同的策略 - 但您还需要检查外部编解码器的文档。

  • Don't use LockBox 3. It's not a good quality library. 不要使用LockBox 3.它不是一个高质量的库。
  • Do no return encrypted data into "text" strings. 不要将加密数据返回“文本”字符串。 Encrypted data are arbitrary sequences of bytes, not strings (as textual data). 加密数据是字节的任意序列,而不是字符串(作为文本数据)。 Delphi uses "length controlled" strings and can store almost anything it hem, but may but you may encounter issues passing around strings that contain byte sequences that could be interpreted the wrong way by other languages, ie $00 by a C/C++ application...). Delphi使用“长度控制”字符串并且可以存储几乎任何下摆的字符串,但是可能会遇到问题,这些字符串包含可能被其他语言错误解释的字节序列,即C / C ++应用程序的$ 00。 )。 If the library itself uses strings, well, it's a symptom it's a low quality library 如果库本身使用字符串,那么它就是一个低质量库的症状
  • Do not transform encrypted data! 不要转换加密数据! When you convert your encrypted ANSIString into a Unicode one (I guess that's the reason of your last cast), you're destroying the encrypted value. 当您将加密的ANSIString转换为Unicode时(我猜这是您上次演员的原因),您正在销毁加密值。 If you pass that string around, it won't be decryptable unless the reverse transformation is applied, as long as it is not "lossy". 如果你传递该字符串,除非应用反向转换,否则它将不会被解密,只要它不是“有损”。

I was able to successfully implement Troy's Delphi code in 10.2 Tokyo with a couple of modifications. 我能够在10.2东京成功实现Troy的Delphi代码并进行了一些修改。

I removed TNTLxUtils from the Uses as it was not needed (and I didn't have it) and added IdGlobal. 我从用途中删除了TNTLxUtils,因为它不需要(我没有)并添加了IdGlobal。 The reason for using IdGlobal is that you need to convert the type TBytes to TIdBytes in the Base64_Encode function and TIBytes back to TBytes in Base64_Decode. 使用IdGlobal的原因是您需要在Base64_Encode函数中将TBytes类型转换为TIdBytes,并在Base64_Decode中将TIBytes转换回TBytes。

Note: This unit will only work in 32-bit applications as it references the 32-bit Windows API. 注意:此单元仅适用于32位应用程序,因为它引用了32位Windows API。

Thanks, Troy for pointing me in the right direction for a free method of encryption that doesn't require purchasing a toolkit to implement. 谢谢,特洛伊指出我正确的方向,一个免费的加密方法,不需要购买工具包来实现。

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.

I just had the same problem. 我刚遇到同样的问题。 I know this is an old topic, but it helped me a lot. 我知道这是一个古老的话题,但它对我帮助很大。 I'm just leaving it here for the record. 我只是把它留在这里备案。

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

Based on Sean's answer, I assume that the mode should be changed to CTS when having more than 1 block. 根据Sean的回答,我假设当模块超过1时,模式应该更改为CTS。 I didn't try it, because 1 block is enough for me, but it should be easy to adapt the code. 我没有尝试过,因为1块对我来说足够了,但应该很容易调整代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM