在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并没有那么深入,似乎需要很长时间才能弄清楚。
我个人的感觉是,我在阅读公钥时做了错误的事情。但那只是一种感觉,我不知道如何以不同的方式做到这一点。我的第二个猜测是我在验证过程中做了一些错误的事情。但我不知道这可能是什么。
为什么签名验证失败?
签名不是文本签名。它由一个字节数组组成,字节可以有任何值。您正在将该字节数组直接转换为ANSI字符串。如果数组包含ANSI范围之外的值(不管那可能是什么,我认为是ASCII),那将失败。
您需要将签名视为二进制数据。如果您需要将其视为字符串(包含文本),则可以使用基本64编解码器。
将函数调用从'result:=(EVP_DigestVerifyFinal(locCtx,PAnsiChar(locStream.Memory),locSize)= 1);'to'result:= (EVP_DigestVerifyFinal(locCtx,@ locStream.Memory,locSize)= 1);'没有区别。 –
您可以直接比较签名的二进制值吗?我无法看到介于两者之间的代码。你可以用二进制编码的文本和公钥和私钥的模数(它们需要相同)来做同样的事情。 –
那么,我不知道到底发生了什么。 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.
你错过的错误处理,先从如果'EVP_DigestVerifyInit'检查和'EVP_DigestVerifyUpdate'成功(检查返回值) – Remko
参见[ EVP签名和验证](http://wiki.openssl.org/index.php/EVP_Signing_and_Verifying)。它给你提供了一些可以开箱即用的例子。 – jww
@Remko:我只是将错误处理留给了可读性。 EVP_DigestVerifyInit和EVP_DigistVerifyUpdate都返回1,这意味着成功。我编辑了我的代码以使其更加清晰。 –