c# delphi encryption turbopower lockbox-3

¿Cómo AES-128 encripta una cadena usando una contraseña en Delphi y descifra en C#?



encryption turbopower (5)

  • No use LockBox 3. No es una biblioteca de buena calidad.
  • No devuelva datos cifrados en cadenas de "texto". Los datos encriptados son secuencias arbitrarias de bytes, no cadenas (como datos de texto). Delphi usa cadenas de "longitud controlada" y puede almacenar casi cualquier cosa, pero puede que tenga problemas al pasar cadenas que contienen secuencias de bytes que podrían ser interpretadas de forma incorrecta por otros idiomas, es decir, $ 00 por una aplicación C / C ++. .). Si la biblioteca usa cadenas, bueno, es un síntoma, es una biblioteca de baja calidad
  • ¡No transforme datos encriptados! Cuando conviertes tu ANSIString encriptado en un Unicode (creo que esa es la razón de tu último elenco), estás destruyendo el valor encriptado. Si pasa esa secuencia, no se podrá descifrar a menos que se aplique la transformación inversa, siempre que no sea "con pérdida".

Me gustaría que AES-128 cifrara una cadena en Delphi con una contraseña. Me gustaría subir esto a mi servidor y ser capaz de descifrar dada la misma contraseña en C #.

En Delphi, estoy usando 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ómo puedo descifrar la cadena resultante en C #? Puedo cambiar el código Delphi. Nada está en producción todavía. Ni siquiera estoy atrapado en el uso de LockBox. Pero me gustaría evitar poner esto en una DLL para P / Invoke.

(Mi ejemplo muestra que mi secuencia cifrada es en sí misma una cadena. Esto no es un requisito para mí. Una secuencia de bytes está bien).


A diferencia de cualquier cebo de llama trol que puedas leer, LockBox 3 es en realidad una biblioteca criptográfica de buena calidad. El cumplimiento de los estándares de LB3 es impecable. Es posible que tenga problemas de interoperabilidad con otros idiomas y bibliotecas en relación con las opciones que están fuera del estándar. Si utiliza Lockbox en el lado Delphi, entonces solo necesita asegurarse de que estas opciones se manejen de la misma manera en el lado del otro idioma. Si esto no es posible, entonces debe elegir otra biblioteca. Trataré con cada una de estas opciones a continuación.

No hay nada de malo con las soluciones alternativas (OpenSSL, CryptoAPI y Eldos). Algunos de ellos pueden ser black-box. Esto podría ser un problema para algunas personas.

  1. Conversión de contraseña a clave. AES-128 usa una clave de 16 bytes. Además, el mecanismo estándar para generar una clave a partir de "datos clave" o "datos de contraseña" se basa de forma nativa en una semilla de entrada de 16 bytes. Es más seguro para la interoperabilidad generar la clave binaria a partir de la contraseña de cadena en el lado Delphi, y simplemente transportar la clave binaria al otro lado, en lugar de transportar la contraseña de cadena. Esto se debe a que el algoritmo para convertir una contraseña de cadena a una clave binaria de 16 bytes está fuera del estándar AES. Nether-the-less, puedes hacerlo de cualquier manera. Cuando a lockbox se le da una contraseña de cadena para inicializar un códec AES-128, mira la carga útil de la cadena como una matriz de bytes. Si la carga útil es precisamente de 16 bytes, entonces genial, se puede pasar directamente al algoritmo de generación de clave AES, que se especifica en el estándar. Si la carga útil de la cuerda no es precisamente de 16 bytes, la carga útil se reducirá a hash con SHA-1 para producir una salida de hash de 20 bytes. Los 16 bytes bajos de este hash se pasan a la función de generación de clave AES estándar. Por lo tanto, sus opciones para garantizar la interoperabilidad en relación con la inicialización de claves son:

    1.1. Transporte claves binarias en lugar de contraseñas de cadena.

    1.2. Si la Opción 1.2 es demasiado inconveniente, transporta la contraseña, pero imita el mismo algoritmo de contraseña a clave en el otro lado.

    1.3. Si 1 y 2 no funcionan por alguna razón, intente restringir las contraseñas a exactamente 16 bytes (8 caracteres UTF-8 o 16 puntos de código UTF-16). Esto debería ser bastante seguro si la implementación del otro idioma es medio decente.

  2. Contraseñas UTF-16 contra ansi-string / UTF-8 Esto no es tanto una opción, sino una trampa para jugadores jóvenes. Los programadores tienden a pensar en "cadenas" como "cadenas". Pero no es así. En Delphi 2010, la carga de las cadenas se almacena en una codificación UTF-16LE con un tamaño de unidad de código de 2 bytes. Pero en otros lenguajes, como PHP y python, en el modo predeterminado, las cadenas son codificaciones de unidad de código de un byte, ya sea UTF-8 o algo basado en una base de página de códigos de MS Windows (que MS llama "ansistring"). Vale la pena recordar que la codificación UTF-16 de ''mypassword'' no es lo mismo que UTF-8 ''mypassword''.

  3. Configuración IV. El estándar AES no trata la cuestión de cómo configurar el códec ''Vector de inicialización (IV). El tamaño del IV es el mismo que el del bloque subyacente. Para AES esto es 128 bits o 16 bytes. Al encriptar, lockbox crea un nonce de 16 bytes. Este nonce se convierte en el valor de IV, y se emite en el claro en la cabecera del mensaje de texto cifrado. Lea la documentación del método / política del otro lado para la inicialización IV. Tus opciones son:

    3.1 Si el otro lado lo antepone al texto cifrado, entonces eres dulce.

    3.2 De lo contrario, en el otro lado, al descifrar, lea los primeros 16 bytes del texto cifrado usted mismo, y pase el resto al códec foráneo. Antes de descifrar, dígale al códec extraño qué es el IV (suponiendo que su API sea capaz de hacerlo).

  4. Cuantificación de bloques El tamaño de bloque AES es de 16 bytes. Cuando el mensaje de texto plano no es precisamente un múltiplo entero de 16 bytes, se debe hacer algo para convertirlo en un múltiplo completo. Este procedimiento se llama cuantificación de bloque y no se trata en el estándar, sino que queda en la implementación. Muchas implementaciones usarán relleno de bloques. No hay un esquema estándar de relleno de bloques y hay muchos para elegir. LockBox no usa relleno de bloque para CBC (otros modos pueden ser un caso diferente). Si el texto sin formato es un número entero de bloques, no se necesita ni se realiza ninguna cuantificación; de lo contrario, se utiliza el robo estándar de CipherText. Si el tamaño del texto simple es muy pequeño (entre 1 y 15 bytes) no es posible el robo de texto cifrado, y en su lugar se usa un esquema de relleno. Para garantizar la interoperabilidad en relación con la cuantificación de bloques, sus opciones son:

    4.1 Verifique su documentación para el códec foráneo en relación con la cuantificación de bloque (puede estar bajo el título de "relleno de mensaje"). Si el códec externo utiliza el robo de texto cifrado, entonces eres dulce (solo asegúrate de que no haya mensajes cortos).

    4.2 De lo contrario, podría hacer su propio relleno. En el lado de la caja de seguridad, la caja de seguridad no hace nada con los mensajes que ya están en bloques enteros. Es muy probable que el códec extranjero tenga la misma política, pero nuevamente debe verificar la documentación del códec extranjero.


Finalmente encontré una solución compatible entre Delphi y C # para AES-128. También funciona en Wine. Aquí está mi código 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.

Y aquí está mi código 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); } } }


Acabo de tener el mismo problema. Sé que este es un tema viejo, pero me ayudó mucho. Solo lo dejo aquí para el registro.

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

Según la respuesta de Sean, supongo que el modo debería cambiarse a CTS cuando tenga más de 1 bloque. No lo intenté, porque 1 bloque es suficiente para mí, pero debería ser fácil adaptar el código.


Pude implementar con éxito el código Delphi de Troy en 10.2 Tokio con un par de modificaciones.

Eliminé TNTLxUtils de los Usos porque no era necesario (y no lo tenía) y agregué IdGlobal. La razón para usar IdGlobal es que necesita convertir el tipo TBytes a TIdBytes en la función Base64_Encode y TIBytes a TBytes en Base64_Decode.

Nota: Esta unidad solo funcionará en aplicaciones de 32 bits ya que hace referencia a la API de Windows de 32 bits.

Gracias, Troy, por apuntarme en la dirección correcta para obtener un método de cifrado gratuito que no requiera la compra de un kit de herramientas para implementar.

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.