How does AES-128 encrypt a string using a password in Delphi and decrypt in C #?

I would like AES-128 to encrypt a string in Delphi with a password. I would like to upload this to my server and be able to decrypt, given the same password in C #.

In Delphi, I use 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. There is nothing in production yet. I am not even stuck with using LockBox. But I would like to avoid putting this in a DLL for P / Invoke.

(My example shows that my encrypted sequence is itself a string. This is not a requirement for me. The byte stream is fine.)

+4
source share
5 answers

I finally found a compatible solution between Delphi and C # for AES-128. He also works for Wine. Here is my Delphi code:

 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 is my C # code:

 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); } } } 
+15
source

Unlike any fiery bait that you can read, LockBox 3 is actually a quality cryptographic library. Compliance with LB3 standards is impeccable. Where you may have compatibility problems with other languages ​​and libraries, this applies to options that do not comply with the standard. If you use Lockbox on the Delphi side, you just need to make sure that these parameters are handled the same on the other side. If this is not possible, select a different library. I will consider each of these options below.

There is nothing wrong with alternative solutions (OpenSSL, CryptoAPI and Eldos). Some may be black. This can be a problem for some people.

  • Convert password to key. AES-128 uses a 16-byte key. Also, the standard key generation mechanism from “key data” or “password data” is based on a 16-byte input seed. It is safer to interact with a binary key from a string password on the Delphi side and simply transfer the binary key to the other side rather than transferring the string password. This is due to the fact that the algorithm for converting a string password to a binary 16-byte key is outside the scope of the AES standard. Anyway, you can do it anyway. When the lockbox is provided with a string password to initialize the AES-128 codec, it treats the payload of the string as an array of bytes. If the payload is exactly 16 bytes, then it can be transferred directly to the AES key generation algorithm, which is specified in the standard. If the string payload is not exactly 16 bytes, then the payload will be hashed with SHA-1 to get a 20-byte hash output. The lower 16 bytes of this hash are then passed to the standard AES key generation function. Thus, your interoperability options regarding key initialization:

    1.1. Transport binary keys instead of string passwords.

    1,2. If option 1.2 is too inconvenient, then transfer the password, but simulate the same algorithm with key to key on the other hand.

    1.3. If for some reason 1 and 2 do not work, try restricting passwords to exactly 16 bytes (8 UTF-8 characters or 16 UTF-16 codes). This should be pretty safe if the implementation in another language is half worthy.

  • UTF-16 compared to passwords ansi-string / UTF-8 This is not such an option, but a trap for young players. We programmers tend to think of “strings” as “strings”. But this is not so. In Delphi 2010, the payload of strings is stored in UTF-16LE encoding with a code size of 2 bytes. But in other languages, such as PHP and python, in default mode, strings are single-byte encodings of code blocks, or UTF-8, or something based on the MS Windows code page database (which MS calls "ansistring"). Remember that the encoding "mypassword" UTF-16 is different from UAPF-8 "mypassword".

  • Setting IV. The AES standard does not address the issue of how to configure the codec (IV) initialization vector. Size IV is the same as the size of the base unit. For AES, this is 128 bits or 16 bytes. When encrypted, lockbox creates a 16-byte nonce. This nonce becomes an IV value, and it is emitted explicitly at the beginning of the ciphertext message. Read the other party's method / policy documentation to initialize IV. Your options:

    3.1 If the other side adds an IV to the ciphertext, then you are nice.

    3.2 Otherwise, on the other hand, when decrypting, first read the first 16 bytes of the ciphertext and transfer the remainder to an external codec. Before decoding, tell the foreign codec what IV is (provided that the API is capable of this).

  • Block Quantization The AES block size is 16 bytes. When a plaintext message is not an integer of 16 bytes, something needs to be done to make it an integer multiple. This procedure is called block quantization and is not considered in the standard, but remains until implementation. Many implementations will use block indentation. There is no standard block filling scheme, and there are many to choose from. LockBox does not use block blocking for CBC (other modes may be in another case). If the plaintext is an integer number of blocks, quantification is not required or is not performed; otherwise, CipherText is stolen. If the size of the plaintext is very small (from 1 to 15 bytes), encryption of the ciphertext is not possible, and padding is used instead. To ensure interoperability regarding block quantization, your options are:

    4.1 Check the documentation for the external codec regarding quantization of the block (it may fall under the heading "message filling"). If a foreign codec uses encryption of encrypted text, then you are nice (just do not forget the short messages).

    4.2 Otherwise, you can make your own addition. On the lock side, the lockbox does nothing for messages that are already in entire blocks. It is very likely that the external codec has the same policy - but again you need to check the documentation for the external codec.

+10
source
  • Do not use LockBox 3. This is not a quality library.
  • Do not return encrypted data to the "text" strings. The encrypted data is an arbitrary sequence of bytes, not strings (in the form of text data). Delphi uses strings with a controlled length and can store almost anything, but it can, but you may run into problems running around strings that contain sequences of bytes that can be interpreted in the wrong way by other languages, i.e. $ 00 by the C / C application ++ ..). If the library itself uses strings, well, this symptom is a poor-quality library.
  • Do not convert encrypted data! . When you convert encrypted ANSIString to Unicode (I think the reason for your last cast), you destroy the encrypted value. If you pass this line around, it will not be decryptable unless the inverse transform is applied, unless it is “lost”.
+5
source

I had the same problem. I know this is an old topic, but it helped me a lot. I just leave 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 if there is more than one block. I have not tried, because for me one block is enough, but it is easy to adapt.

0
source

I was able to successfully implement the Troy Delphi code in 10.2 Tokyo with several modifications.

I removed TNTLxUtils from Uses as it was not needed (and I didn’t have it) and added IdGlobal. The reason for using IdGlobal is because you need to convert the TBytes types in TIdBytes to the Base64_Encode function and the TIBytes back to TBytes in Base64_Decode.

Note. This machine will only work in 32-bit applications because it refers to the 32-bit Windows API.

Thank you, Troy, for pointing me in the right direction to a free encryption method that does not require the acquisition of tools for implementation.

 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. 
0
source

Source: https://habr.com/ru/post/1395356/


All Articles