2016-04-21 107 views
5

我想在使用OpenSSL libeay32.dll的Delphi中實現SHA256簽名和驗證。爲此,在第一步驟我創建使用下面的OpenSSL一個RSA 2048位密鑰對命令:在Delphi中使用OpenSSL驗證SHA256簽名失敗

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

即遠那麼容易。我做的下一步是創建一個能夠從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; 

這似乎也適用。所以,我實現了一些跡象程序:

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; 

正如你可以看到程序讀取一個叫做message.txt文件,計算簽名並存儲到SIG message.sig。如果我運行下面的命令的OpenSSL的結果是驗證OK

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

所以看起來像我的簽名程序也正在正確的。於是,我終於實現了一個驗證過程:

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; 

正如你可以看到我實現了這個過程完全相同的方式和我一樣實行簽收手續。不幸的是,這樣的結果是false。通過OpenSSL的返回的錯誤代碼是

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

這相當於一個錯誤在LIB RSA,功能int_rsa_verify,原因錯誤的簽名長度。我搜索了Google,但沒有找到任何關於該錯誤的有用信息。我也嘗試瞭解OpenSSL的來源,但我對C並沒有那麼深入,似乎需要很長時間才能弄清楚。

我個人的感覺是,我在閱讀公鑰時做了錯誤的事情。但那只是一種感覺,我不知道如何以不同的方式做到這一點。我的第二個猜測是我在驗證過程中做了一些錯誤的事情。但我不知道這可能是什麼。

爲什麼簽名驗證失敗?

+0

你錯過的錯誤處理,先從如果'EVP_DigestVerifyInit'檢查和'EVP_DigestVerifyUpdate'成功(檢查返回值) – Remko

+2

參見[ EVP簽名和驗證](http://wiki.openssl.org/index.php/EVP_Signing_and_Verifying)。它給你提供了一些可以開箱即用的例子。 – jww

+0

@Remko:我只是將錯誤處理留給了可讀性。 EVP_DigestVerifyInit和EVP_DigistVerifyUpdate都返回1,這意味着成功。我編輯了我的代碼以使其更加清晰。 –

回答

1

簽名不是文本簽名。它由一個字節數組組成,字節可以有任何值。您正在將該字節數組直接轉換爲ANSI字符串。如果數組包含ANSI範圍之外的值(不管那可能是什麼,我認爲是ASCII),那將失敗。

您需要將簽名視爲二進制數據。如果您需要將其視爲字符串(包含文本),則可以使用基本64編解碼器。

+0

將函數調用從'result:=(EVP_DigestVerifyFinal(locCtx,PAnsiChar(locStream.Memory),locSize)= 1);'to'result:= (EVP_DigestVerifyFinal(locCtx,@ locStream.Memory,locSize)= 1);'沒有區別。 –

+0

您可以直接比較簽名的二進制值嗎?我無法看到介於兩者之間的代碼。你可以用二進制編碼的文本和公鑰和私鑰的模數(它們需要相同)來做同樣的事情。 –

+0

那麼,我不知道到底發生了什麼。 EVP_DigestVerifyFinal是直接調用libeay32.dll。鏈接是通過函數EVP_DigestVerifyFinal(ctx:pEVP_MD_CTX; const d:PAnsiChar; var cnt:Cardinal)完成的:Integer; cdecl;'和'function EVP_DigestVerifyFinal;外部'libeay32.dll';'在實現部分。因爲我也試過'函數EVP_DigestVerifyFinal(ctx:pEVP_MD_CTX; const d:指針; var cnt:Cardinal):Integer; cdecl;'但沒有區別。 –

3

好的,我找到了解決方案。事實上,我必須處理兩個錯誤。第一個錯誤是我以錯誤的方式將簽名傳入EVP_DigestVerifyFinal。這就是馬爾滕博德韋斯在他的回答中所說的,我會接受這個答案來回答我的問題。

第二個問題是在我對DLL入口點的定義之內。我已經將EVP_DigistVerifyFinal的第三個參數聲明爲var參數。可能是複製&過去的錯誤,因爲EVP_DigistSignFinal的第三個參數是var參數。

對於所有需要做同樣事情的人,我都會在這裏發佈我的解決方案。它的靈感來自於閱讀EVP Signing and Verifying,DelphiOpenSSL和OpenSSL資源(主要是dgst.c)。該代碼是用Delphi XE2實現和測試的。

請注意,我的代碼不會執行任何錯誤處理,也不關心釋放內存太多。這意味着代碼不是生產準備好的,你應該謹慎使用它!

進口單位:

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. 

實施:

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.