在Delphi中使用OpenSSL验证SHA256签名失败

问题描述:

我想在使用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,这意味着成功。我编辑了我的代码以使其更加清晰。 –

签名不是文本签名。它由一个字节数组组成,字节可以有任何值。您正在将该字节数组直接转换为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;'但没有区别。 –

好的,我找到了解决方案。事实上,我必须处理两个错误。第一个错误是我以错误的方式将签名传入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.