如何 AES-128 在 Delphi 中使用密码加密字符串并在 C# 中解密?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9188045/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to AES-128 encrypt a string using a password in Delphi and decrypt in C#?
提问by Troy
I'd like to AES-128 encrypt a string in Delphi with a password. I'd like to upload this to my server and be able to decrypt given the same password in C#.
我想用密码在 Delphi 中用 AES-128 加密一个字符串。我想将它上传到我的服务器,并且能够在 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#?I can change the Delphi code. Nothing is in production yet. I'm not even stuck on using LockBox. But, I would like to avoid putting this in a DLL for P/Invoke.
如何在 C# 中解密结果字符串?我可以更改 Delphi 代码。还没有任何东西在生产中。我什至没有坚持使用 LockBox。但是,我想避免将它放在 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.)
(我的例子表明我的加密序列本身就是一个字符串。这对我来说不是必需的。字节流就可以了。)
采纳答案by Troy
I finally found a compatible solution between Delphi and C# for AES-128. It's also works on Wine. Here's my Delphi code:
我终于为 AES-128 找到了 Delphi 和 C# 之间的兼容解决方案。它也适用于 Wine。这是我的德尔福代码:
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 = 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);
}
}
}
000008;
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# 代码:
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
回答by Troy
- Don't use LockBox 3. It's not a good quality library.
- 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, i.e. $00 by a C/C++ application...). 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. If you pass that string around, it won't be decryptable unless the reverse transformation is applied, as long as it is not "lossy".
- 不要使用 LockBox 3。它不是一个高质量的库。
- 不要将加密数据返回到“文本”字符串中。加密数据是任意字节序列,而不是字符串(作为文本数据)。Delphi 使用“长度控制”的字符串并且可以存储几乎任何它的下摆,但是可能但您可能会遇到传递包含字节序列的字符串的问题,这些字符串可能被其他语言以错误的方式解释,即 $00 被 C/C++ 应用程序.. .) 如果库本身使用字符串,那么这是一个低质量库的症状
- 不要转换加密数据!当您将加密的 ANSIString 转换为 Unicode 时(我猜这是您最后一次转换的原因),您正在破坏加密值。如果您传递该字符串,除非应用逆向转换,否则它将无法解密,只要它不是“有损”。
回答by Sean B. Durkin
Contrary to any troll flame-bait that you might read, LockBox 3 is actually a good quality cryptographic library. The standards compliance of LB3 is impecable. 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. If this is not possible, then you should choose another library. I will deal with each of these options below.
与您可能读到的任何巨魔火焰诱饵相反,LockBox 3 实际上是一个高质量的加密库。LB3 的标准合规性是无可挑剔的。与其他语言和库的互操作性问题可能与标准之外的选项有关。如果在 Delphi 端使用 Lockbox,那么您只需要确保在其他语言端以相同的方式处理这些选项。如果这是不可能的,那么您应该选择另一个库。我将在下面处理这些选项中的每一个。
There is nothing wrong with the alternative solutions (OpenSSL, CryptoAPI and Eldos). Some of them may be black-box. This might be an issue for some peoople.
替代解决方案(OpenSSL、CryptoAPI 和 Eldos)没有任何问题。其中一些可能是黑匣子。对于某些人来说,这可能是一个问题。
Converting password to key. AES-128 uses a 16 byte key. Also the standard mechanism to generate a key from "key data" or "password data" is natively based on a 16 byte input seed. 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. This is because the algorithm to convert a string password to a binary 16-byte key is outside the AES standard. 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. 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. If the string payload is not precisely 16 bytes, then payload will be hashed with SHA-1 to produce a 20 byte hash output. The low 16 bytes of this hash are then passed to the standard AES key generation function. So, your options for ensuring interoperability in relation to key initialization are:
1.1. Transport binary keys instead of string passwords.
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.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). This should be pretty safe if the other language's implementation is half decent.
UTF-16 versus ansi-string/UTF-8 passwords This is not so much an option, but a trap for young players. 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. 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"). It pays to remember than UTF-16 encoding of 'mypassword' is not the same as UTF-8 'mypassword'.
IV setup. The AES standard does not deal with the question of how to set up the codec' Initialization Vector (IV). The size of the IV is the same as the size of the underlying block. For AES this is 128 bits or 16 bytes. When encrypting, lockbox creates a 16 byte nonce. This nonce becomes the value of the IV, and it is emitted in the clear at the head of the ciphertext message. Read the documentation on the other side's method/policy for IV initialization. Your options are:
3.1 If the other side prepends the IV to the ciphertext, then you are sweet.
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. Before decryption, tell you foreign codec what the IV is (assuming it's API is capable of this).
Block quantisation The AES block size is 16 bytes. When the plaintext message is not precisely a whole multiple 16 bytes, something must be done to make it a whole multiple. 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). If the plaintext is a whole number of blocks, no quantisation is needed or done, otherwise standard CipherText stealing is used. If the plaintext size is very small (between 1 and 15 bytes) ciphertext stealing is not possible, and a padding scheme is used instead. 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"). 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. 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.
将密码转换为密钥。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 代码点)。如果其他语言的实现还不错的话,这应该是相当安全的。
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' 不同。
四、设置。AES 标准不处理如何设置编解码器的初始化向量 (IV) 的问题。IV 的大小与底层块的大小相同。对于 AES,这是 128 位或 16 字节。加密时,密码箱会创建一个 16 字节的随机数。这个 nonce 成为 IV 的值,并在密文消息的头部以明文形式发出。阅读有关 IV 初始化的另一方方法/策略的文档。您的选择是:
3.1 如果对方在密文前面加上了 IV,那么你就很好。
3.2 否则,在另一边,在解密时,自己读取密文的前16个字节,将其余部分传递给外国编解码器。在解密之前,告诉您外部编解码器 IV 是什么(假设它的 API 能够做到这一点)。
块量化 AES 块大小为 16 字节。当明文消息不是 16 字节的整数倍时,必须采取措施使其成为整数倍。这个过程称为块量化,标准中没有处理,而是留给实现。许多实现将使用块填充。没有标准的块填充方案,有很多可供选择。LockBox 不为 CBC 使用块填充(其他模式可能是不同的情况)。如果明文是整数块,则不需要或不进行量化,否则使用标准密文窃取。如果明文大小非常小(在 1 到 15 个字节之间),则无法窃取密文,而是使用填充方案。为确保与块量化相关的互操作性,您可以选择:
4.1 检查与块量化相关的外部编解码器的文档(它可能位于“消息填充”标题下)。如果外国编解码器使用密文窃取,那么你就很高兴(只要确保没有短消息)。
4.2 否则你可以做你自己的填充。在密码箱方面,密码箱对已经在整个块中的消息没有任何作用。很可能外国编解码器具有相同的策略 - 但您再次需要检查外国编解码器的文档。
回答by Leonardo
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.
我只是遇到了同样的问题。我知道这是一个古老的话题,但它对我帮助很大。我只是把它留在这里作为记录。
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 = ##代码##000008;
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.
Based on Sean's answer, I assume that the mode should be changed to CTS when having more than 1 block. I didn't try it, because 1 block is enough for me, but it should be easy to adapt the code.
根据肖恩的回答,我假设当有超过 1 个块时,模式应该更改为 CTS。我没有尝试,因为1块对我来说已经足够了,但它应该很容易适应代码。
回答by Brad Weaver
I was able to successfully implement Troy's Delphi code in 10.2 Tokyo with a couple of modifications.
通过一些修改,我能够在 10.2 Tokyo 中成功实现 Troy 的 Delphi 代码。
I removed TNTLxUtils from the Uses as it was not needed (and I didn't have it) and added 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.
我从 Uses 中删除了 TNTLxUtils,因为它不需要(而且我没有)并添加了 IdGlobal。使用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.
谢谢,Troy 为我指明了正确的方向,提供了一种无需购买工具包即可实现的免费加密方法。
##代码##
