我想用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中。
(我的例子显示我的加密序列本身就是一个字符串。这对我来说不是必需的。字节流很好。)
答案 0 :(得分: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.
这是我的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);
}
}
}
答案 1 :(得分:10)
与您可能阅读的任何巨魔火焰诱饵相反,LockBox 3实际上是一个高质量的加密库。 LB3的标准符合性是不可避免的。您可能遇到与其他语言的互操作性问题的地方。库与标准之外的选项有关。如果在Delphi端使用Lockbox,那么您只需确保在其他语言的一侧以相同的方式处理这些选项。如果这不可能,那么您应该选择另一个库。我将在下面处理以下每个选项。
替代解决方案(OpenSSL,CryptoAPI和Eldos)没有任何问题。其中一些可能是黑盒子。这可能是一些人的问题。
将密码转换为密钥。 AES-128使用16字节密钥。也是从关键数据&#34;生成密钥的标准机制。或者&#34;密码数据&#34;本机上基于16字节输入种子。互操作性从Delphi端的字符串密码生成二进制密钥更安全,只是将二进制密钥传输到另一端,而不是传输字符串密码。这是因为将字符串密码转换为二进制16字节密钥的算法超出了AES标准。无论如何,你可以这样做。当给密码箱一个字符串密码来初始化AES-128编解码器时,它会将字符串有效负载视为一个字节数组。如果有效载荷恰好是16个字节,那么很好,它可以直接传递给AES密钥生成算法,该算法在标准中指定。如果字符串有效负载不是精确的16字节,那么将使用SHA-1对有效负载进行散列以产生20字节的散列输出。然后将此哈希的低16字节传递给标准AES密钥生成函数。因此,您确保与密钥初始化相关的互操作性的选项是:
1.1。传输二进制密钥而不是字符串密码。
1.2。如果选项1.2太不方便,则传输密码,但在另一端模仿相同的密码到密钥算法。
1.3。如果1&amp; 2由于某种原因不起作用,尝试将密码限制为恰好16个字节(8个UTF-8字符或16个UTF-16代码点)。如果其他语言的实现方式不合适,这应该是非常安全的。
UTF-16与ansi-string / UTF-8密码 这不是一个选择,而是年轻球员的陷阱。我们程序员倾向于考虑&#34;字符串&#34; as&#34; strings&#34;。但事实并非如此。在Delphi 2010中,字符串的有效负载以UTF-16LE编码存储,代码单元大小为2个字节。但在其他语言中,例如PHP和python,在默认模式下,字符串是单字节代码单元编码,UTF-8或基于MS windows代码页基础的东西(MS调用&#34; ansistring& #34)。记住比使用UTF-16编码的mypassword&#39;与UTF-8&#39; mypassword&#39;。
IV设置。 AES标准没有涉及如何设置编解码器的问题。初始化矢量(IV)。 IV的大小与底层块的大小相同。对于AES,这是128位或16字节。加密时,密码箱会创建一个16字节的nonce。该随机数变为IV的值,并且在密文消息的头部以明文形式发出。阅读另一方的文档,了解IV初始化的方法/策略。您的选择是:
3.1如果对方将IV预先设置为密文,那么你就是甜蜜的。
3.2否则,另一方面,在解密时,自己读取密文的前16个字节,并将余数传递给外部编解码器。在解密之前,告诉外国编解码器IV是什么(假设它的API能够做到这一点)。
块量化 AES块大小为16个字节。当明文消息不是一个完整的多个16字节时,必须做一些事情才能使它成为一个整数倍。此过程称为块量化,不在标准中处理,而是留给实现。许多实现将使用块填充。没有标准的块填充方案,有很多可供选择。 LockBox不为CBC使用块填充(其他模式可能是不同的情况)。如果明文是整数个块,则不需要或不进行量化,否则使用标准CipherText窃取。如果明文大小非常小(1到15个字节之间),则不可能进行密文窃取,而是使用填充方案。为确保与块量化相关的互操作性,您可以选择:
4.1检查文档中有关块量化的外部编解码器(它可能位于&#34;消息填充&#34;标题下)。如果外国编解码器使用密文窃取,那么你很好(只要确保没有短信)。
4.2否则你可以做自己的填充。在密码箱方面,密码箱对已经存在于整个块中的消息不执行任何操作。很可能外国编解码器具有相同的策略 - 但您还需要检查外部编解码器的文档。
答案 2 :(得分:5)
答案 3 :(得分:1)
我能够在10.2东京成功实现Troy的Delphi代码并进行了一些修改。
我从Uses中删除了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.
答案 4 :(得分:0)
我遇到了同样的问题。我知道这是一个古老的话题,但它对我帮助很大。我只是把它留在这里进行记录。
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块对我来说已经足够了,但应该很容易调整代码。