2016-04-21 36 views
5

Sto tentando di implementare la firma e la verifica SHA256 in Delphi utilizzando OpenSSL libeay32.dll. Perciò in una prima fase ho creato un RSA 2048 bit coppia di chiavi utilizzando il seguente OpenSSL comandi:Verifica della firma SHA256 con OpenSSL in Delphi non riuscita

openssl genrsa -out private.pem 2048 
openssl rsa -in private.pem -outform PEM -pubout -out public.pem 

Così lontano così facile. Il prossimo passo che ho fatto è stata la creazione di una funzione che è stato in grado di leggere le chiavi pubbliche e private dai file PEM:

function TSignSHA256.ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
var locFile : RawByteString; 
    locBIO : pBIO; 
begin 
    locFile := UTF8Encode(aFileName); 

    locBIO := BIO_new(BIO_s_file()); 

    try 
    BIO_read_filename(locBIO, PAnsiChar(locFile)); 

    result := NIL; 
    case aType of 
     kfPrivate : result := PEM_read_bio_PrivateKey(locBIO, result, nil, nil); 
     kfPublic : result := PEM_read_bio_PUBKEY(locBIO, result, nil, nil); 
    end; 
    finally 
    BIO_free(locBIO); 
    end; 
end; 

che sembrava funzionare così. Così ho implementato qualche procedura segno:

procedure TSignSHA256.Sign; 
var locData : RawByteString; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile('private.pem', kfPrivate); 
    locData := ReadMessage('message.txt'); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestSignInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestSignUpdate(locCtx, PAnsiChar(locData), Length(locData)); 
    EVP_DigestSignFinal(locCtx, NIL, locSize); 

    locStream := TBytesStream.Create; 
    try 
     locStream.SetSize(locSize); 
     EVP_DigestSignFinal(locCtx, PAnsiChar(locStream.Memory), locSize); 
     WriteSignature('message.sig', locStream.Bytes, locSize); 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

Come si può vedere la procedura è la lettura di un file chiamato message.txt, calcolando la firma e la conservazione che in ordine a al message.sig. Se si esegue il seguente comando OpenSSL il risultato è Verificati OK:

openssl dgst -sha256 -verify public.pem -signature message.sig message.txt 

Così sembra che la mia procedura di firma sta lavorando anche corretto. Così ho finalmente implementato una procedura di verifica:

function TSignSHA256.Verify : Boolean; 
var locData : RawByteString; 
    locSig : TArray<Byte>; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile('public.pem', kfPublic); 
    locData := ReadMessage('message.txt'); 
    locSig := ReadSignature('message.sig'); 
    locSize := Length(locSig); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestVerifyInit(locCtx, NIL, EVP_sha256(), NIL, locKey); //Returns 1 
    EVP_DigestVerifyUpdate(locCtx, PAnsiChar(locData), Length(locData)); //Returns 1 

    locStream := TBytesStream.Create(locSig); 
    try 
     result := (EVP_DigestVerifyFinal(locCtx, PAnsiChar(locStream.Memory), locSize) = 1); //Returns false! WHY??? 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

Come potete vedere ho implementato questa procedura esattamente allo stesso modo come ho fatto applicare la procedura di firma. Purtroppo il risultato di questo è false. Il codice di errore restituito da OpenSSL è

error04091077:lib(4):func(145):reason:(119) 

Ciò si traduce in un errore nel lib RSA, funziona int_rsa_verify, ragione errata lunghezza firma. Ho cercato su Google ma non ho trovato alcuna informazione utile su quell'errore. Ho anche cercato di capire i sorgenti di OpenSSL, ma non sono così approfondito in C e sembra che possano volerci anni prima che riesca a capirlo.

La mia sensazione personale è che ho fatto qualcosa di sbagliato leggendo la chiave pubblica. Ma questo è solo un sentimento e non ho idea di come potrei farlo in un modo diverso. La mia seconda ipotesi sarebbe che ho sbagliato qualcosa inizializzando il contesto nella procedura di verifica. Ma non ho idea di cosa possa essere.

Perché la verifica della firma non riesce?

+0

Ti manca la gestione degli errori, iniziare con il controllo se '' EVP_DigestVerifyInit' e EVP_DigestVerifyUpdate' successo (controllare i valori di ritorno) – Remko

+2

See [ Firma e verifica EVP] (http://wiki.openssl.org/index.php/EVP_Signing_and_Verifying) sul wiki OpenSSL. Ti dà esempi che funzionano fuori dalla scatola. – jww

+0

@Remko: ho appena lasciato la gestione degli errori per la leggibilità. EVP_DigestVerifyInit e EVP_DigistVerifyUpdate restituiscono entrambi 1 che significa successo. Ho modificato il mio codice per renderlo più chiaro. –

risposta

1

Una firma non è una firma testuale. Consiste in un array di byte per il quale i byte possono avere qualsiasi valore. Stai convertendo quell'array di byte direttamente in e da stringhe ANSI. Questo fallirà se la matrice contiene valori al di fuori dell'intervallo ANSI (qualunque cosa sia, presumerei ASCII).

È necessario considerare la firma come dati binari. È possibile utilizzare un codec di base 64 se è necessario trattarlo come una stringa (contenente testo).

+0

Cambiare la chiamata di funzione da 'result: = (EVP_DigestVerifyFinal (locCtx, PAnsiChar (locStream.Memory), locSize) = 1);' a 'risultato: = (EVP_DigestVerifyFinal (locCtx, @ locStream.Memory, locSize) = 1); 'non fa differenza. –

+0

Potresti confrontare direttamente i valori binari della firma? Non riesco a vedere il codice che c'è in mezzo. Si potrebbero fare le stesse cose con il testo codificato in binario e il modulo della chiave pubblica e privata (che deve essere lo stesso). –

+0

Beh, non so esattamente cosa stia succedendo in mezzo. EVP_DigestVerifyFinal è una chiamata diretta in libeay32.dll. Il collegamento viene eseguito con 'function EVP_DigestVerifyFinal (ctx: pEVP_MD_CTX; const d: PAnsiChar; var cnt: Cardinal): Intero; cdecl; 'e' function EVP_DigestVerifyFinal; 'libeay32.dll' esterno; nella parte dell'implementazione. Di causa ho anche provato 'function EVP_DigestVerifyFinal (ctx: pEVP_MD_CTX; const d: Pointer; var cnt: Cardinal): Integer; cdecl; 'ma non ha fatto differenza. –

3

OK, ho trovato la soluzione. In effetti ho dovuto affrontare due errori. Il primo errore era che stavo passando la firma in EVP_DigestVerifyFinal in un modo sbagliato. Questo è ciò che ha affermato Maarten Bodewes nella sua risposta e lo accetterò come risposta alla mia domanda.

Il secondo problema era nella definizione del punto di ingresso nella DLL.Avevo dichiarato il terzo parametro di EVP_DigistVerifyFinal come un parametro var. Probabilmente una copia dell'errore passato & come il terzo parametro di EVP_DigistSignFinal È un parametro var.

Per tutti coloro che dovranno mai fare lo stesso, inserisco qui la mia soluzione. È stato ispirato dalla lettura di EVP Signing and Verifying, DelphiOpenSSL e dei sorgenti OpenSSL (principalmente dgst.c). Il codice è stato implementato e testato con Delphi XE2.

Si noti che il mio codice non esegue alcuna gestione degli errori e non mi interessa troppo di liberare memoria. Ciò significa che il codice non è pronto per la produzione e dovresti usarlo con cura!

L'unità di importazione:

unit uOpenSSLCrypt; 

interface 

type 
    pBIO = Pointer; 
    pBIO_METHOD = Pointer; 

    pEVP_MD_CTX = Pointer; 
    pEVP_MD = Pointer; 

    pEVP_PKEY_CTX = Pointer; 
    pEVP_PKEY = Pointer; 

    ENGINE = Pointer; 

    TPWCallbackFunction = function(buffer : PAnsiChar; length : Integer; verify : Integer; data : Pointer) : Integer; cdecl; 

    //Error functions 
    function ERR_get_error : Cardinal; cdecl; 
    function ERR_error_string(e : Cardinal; buf : PAnsiChar) : PAnsiChar; cdecl; 

    function ERR_GetErrorMessage : String; 

    //BIO functions 
    function BIO_new(_type : pBIO_METHOD) : pBIO; cdecl; 
    function BIO_new_file(const aFileName : PAnsiChar; const aMode : PAnsiChar) : pBIO; cdecl; 
    function BIO_free(a: pBIO): integer; cdecl; 
    function BIO_s_file : pBIO_METHOD; cdecl; 
    function BIO_f_md : pBIO_METHOD; cdecl; 
    function BIO_ctrl(bp : pBIO; cmd : Integer; larg : Longint; parg : Pointer) : Longint; cdecl; 
    function BIO_read(b : pBIO; buf : Pointer; len : Integer) : integer; cdecl; 
    function BIO_get_md_ctx(bp: pBIO; mdcp: Pointer): Longint; 
    function BIO_read_filename(bp : pBIO; filename : PAnsiChar) : Integer; 

    function PEM_read_bio_PrivateKey(bp : pBIO; x : pEVP_PKEY; cb : TPWCallbackFunction; u : pointer) : pEVP_PKEY; cdecl; 
    function PEM_read_bio_PUBKEY(bp : pBIO; x : pEVP_PKEY; cb : TPWCallbackFunction; u : Pointer) : pEVP_PKEY; cdecl; 

    //EVP functions 
    function EVP_MD_CTX_create() : pEVP_MD_CTX; cdecl; 
    procedure EVP_MD_CTX_destroy(ctx : pEVP_MD_CTX); cdecl; 
    function EVP_sha256() : pEVP_MD; cdecl; 

    function EVP_PKEY_size(key: pEVP_PKEY): integer; cdecl; 
    function EVP_DigestSignInit(aCtx : pEVP_MD_CTX; aPCtx : pEVP_PKEY_CTX; aType : pEVP_MD; aEngine : ENGINE; aKey : pEVP_PKEY ) : Integer; cdecl; 
    function EVP_DigestSignUpdate(ctx : pEVP_MD_CTX; const d : Pointer; cnt : Cardinal) : Integer; cdecl; 
    function EVP_DigestSignFinal(ctx : pEVP_MD_CTX; const d : PByte; var cnt : Cardinal) : Integer; cdecl; 

    function EVP_DigestVerifyInit(aCtx : pEVP_MD_CTX; aPCtx : pEVP_PKEY_CTX; aType : pEVP_MD; aEngine : ENGINE; aKey : pEVP_PKEY ) : Integer; cdecl; 
    function EVP_DigestVerifyUpdate(ctx : pEVP_MD_CTX; const d : Pointer; cnt : Cardinal) : Integer; cdecl; 
    function EVP_DigestVerifyFinal(ctx : pEVP_MD_CTX; const d : PByte; cnt : Cardinal) : Integer; cdecl; 

    function CRYPTO_malloc(aLength : LongInt; const f : PAnsiChar; aLine : Integer) : Pointer; cdecl; 
    procedure CRYPTO_free(str : Pointer); cdecl; 

const BIO_C_SET_FILENAME = 108; 
     BIO_C_GET_MD_CTX = 120; 

     BIO_CLOSE = $01; 
     BIO_FP_READ = $02; 

implementation 

uses System.SysUtils, Windows; 

const LIBEAY_DLL_NAME = 'libeay32.dll'; 

function ERR_get_error : Cardinal; external LIBEAY_DLL_NAME; 
function ERR_error_string;   external LIBEAY_DLL_NAME; 

function ERR_GetErrorMessage : String; 
var locErrMsg: array [0..160] of Char; 
begin 
    ERR_error_string(ERR_get_error, @locErrMsg); 
    result := String(StrPas(PAnsiChar(@locErrMsg))); 
end; 

function BIO_new;     external LIBEAY_DLL_NAME; 
function BIO_new_file;   external LIBEAY_DLL_NAME; 
function BIO_free;    external LIBEAY_DLL_NAME; 
function BIO_ctrl;    external LIBEAY_DLL_NAME; 
function BIO_s_file;    external LIBEAY_DLL_NAME; 
function BIO_f_md;    external LIBEAY_DLL_NAME; 
function BIO_read;    external LIBEAY_DLL_NAME; 

function BIO_get_md_ctx(bp : pBIO; mdcp : Pointer) : Longint; 
begin 
    result := BIO_ctrl(bp, BIO_C_GET_MD_CTX, 0, mdcp); 
end; 

function BIO_read_filename(bp : pBIO; filename : PAnsiChar) : Integer; 
begin 
    result := BIO_ctrl(bp, BIO_C_SET_FILENAME, BIO_CLOSE or BIO_FP_READ, filename); 
end; 

function PEM_read_bio_PrivateKey; external LIBEAY_DLL_NAME; 
function PEM_read_bio_PUBKEY;  external LIBEAY_DLL_NAME; 

function EVP_MD_CTX_create; external LIBEAY_DLL_NAME; 
procedure EVP_MD_CTX_destroy; external LIBEAY_DLL_NAME; 
function EVP_sha256;   external LIBEAY_DLL_NAME; 

function EVP_PKEY_size;   external LIBEAY_DLL_NAME; 
function EVP_DigestSignInit; external LIBEAY_DLL_NAME; 
function EVP_DigestSignUpdate; external LIBEAY_DLL_NAME name 'EVP_DigestUpdate'; 
function EVP_DigestSignFinal; external LIBEAY_DLL_NAME; 

function EVP_DigestVerifyInit; external LIBEAY_DLL_NAME; 
function EVP_DigestVerifyUpdate; external LIBEAY_DLL_NAME name 'EVP_DigestUpdate'; 
function EVP_DigestVerifyFinal; external LIBEAY_DLL_NAME; 

function CRYPTO_malloc; external LIBEAY_DLL_NAME; 
procedure CRYPTO_free; external LIBEAY_DLL_NAME; 

end. 

L'implementazione:

unit uSignSHA256; 

interface 

uses uOpenSSLCrypt; 

type 
    TKeyFileType = (kfPrivate, kfPublic); 

    TSignSHA256 = class(TObject) 
    private 
    function ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
    function ReadMessage(aName : String) : RawByteString; 
    function ReadSignature(aName : String; var aLength : Cardinal) : Pointer; 
    procedure FreeSignature(aSig : Pointer); 

    procedure WriteSignature(aName : String; aSignature : TArray<Byte>; aLength : Integer); 

    public 
    constructor Create; 
    destructor Destroy; override; 

    procedure Sign(aKeyFile : String; aMsgFile : String; aSigFile : String); 
    function Verify(aKeyFile : String; aMsgFile : String; aSigFile : String) : Boolean; 
    end; 

implementation 

uses System.Classes, System.SysUtils; 

{ TSignSHA256 } 

constructor TSignSHA256.Create; 
begin 

end; 

destructor TSignSHA256.Destroy; 
begin 

    inherited; 
end; 

procedure TSignSHA256.FreeSignature(aSig : Pointer); 
begin 
    CRYPTO_free(aSig); 
end; 

function TSignSHA256.ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
var locFile : RawByteString; 
    locBIO : pBIO; 
begin 
    locFile := UTF8Encode(aFileName); 

    locBIO := BIO_new(BIO_s_file()); 

    try 
    BIO_read_filename(locBIO, PAnsiChar(locFile)); 

    result := NIL; 
    case aType of 
     kfPrivate : result := PEM_read_bio_PrivateKey(locBIO, nil, nil, nil); 
     kfPublic : result := PEM_read_bio_PUBKEY(locBIO, nil, nil, nil); 
    end; 
    finally 
    BIO_free(locBIO); 
    end; 
end; 

function TSignSHA256.ReadMessage(aName : String) : RawByteString; 
var locFileStream : TFileStream; 
    locSize  : Cardinal; 
    locBytes  : TArray<Byte>; 
    locText  : String; 
begin 
    locFileStream := TFileStream.Create(aName, fmOpenRead); 
    try 
    locSize := locFileStream.Size; 

    SetLength(locBytes, locSize); 
    locFileStream.Read(locBytes[0], locSize); 
    finally 
    FreeAndNIL(locFileStream); 
    end; 

    SetString(locText, PAnsiChar(locBytes), locSize); 
    result := UTF8Encode(locText); 
end; 

function TSignSHA256.ReadSignature(aName : String; var aLength : Cardinal) : Pointer; 
var locSigBio : pBIO; 
    locFile : RawByteString; 
    locMode : RawByteString; 
begin 
    locFile := UTF8Encode(aName); 
    locMode := UTF8Encode('rb'); 

    locSigBio := BIO_new_file(PAnsiChar(locFile), PAnsiChar(locMode)); 
    try 
    result := CRYPTO_malloc(aLength, NIL, 0); 
    aLength := BIO_read(locSigBio, result, aLength); 
    finally 
    BIO_free(locSigBio); 
    end; 
end; 

procedure TSignSHA256.Sign(aKeyFile : String; aMsgFile : String; aSigFile : String); 
var locData : RawByteString; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile(aKeyFile, kfPrivate); 
    locData := ReadMessage(aMsgFile); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestSignInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestSignUpdate(locCtx, PAnsiChar(locData), Length(locData)); 
    EVP_DigestSignFinal(locCtx, NIL, locSize); 

    locStream := TBytesStream.Create; 
    try 
     locStream.SetSize(locSize); 
     EVP_DigestSignFinal(locCtx, PByte(locStream.Memory), locSize); 
     WriteSignature(aSigFile, locStream.Bytes, locSize); 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

function TSignSHA256.Verify(aKeyFile : String; aMsgFile : String; aSigFile : String) : Boolean; 
var locData : RawByteString; 
    locSig : Pointer; 
    locKey : pEVP_PKEY; 
    locBio : pBIO; 
    locCtx : pEVP_MD_CTX; 
    locKeyCtx : pEVP_PKEY_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile(aKeyFile, kfPublic); 
    locData := ReadMessage(aMsgFile); 
    locSize := EVP_PKEY_size(locKey); 

    locBio := BIO_new(BIO_f_md); 
    try 
    BIO_get_md_ctx(locBio, @locCtx); 
    locSHA256 := EVP_sha256(); 

    EVP_DigestVerifyInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestVerifyUpdate(locCtx, PAnsiChar(locData), Length(locData)); 

    try 
     locSig := ReadSignature(aSigFile, locSize); 
     result := (EVP_DigestVerifyFinal(locCtx, PByte(locSig), locSize) = 1); 
    finally 
     FreeSignature(locSig); 
    end; 
    finally 
    BIO_free(locBio); 
    end; 
end; 

procedure TSignSHA256.WriteSignature(aName : String; aSignature : TArray<Byte>; aLength : Integer); 
var locFileStream : TFileStream; 
begin 
    locFileStream := TFileStream.Create(aName, fmCreate); 
    try 
    locFileStream.Write(aSignature[0], aLength); 
    finally 
    FreeAndNIL(locFileStream); 
    end; 
end; 

end.