我正在尝试使用OpenSSL libeay32.dll在Delphi中实现SHA256签名和验证。因此,我在第一步中使用以下OpenSSL命令创建了一个RSA 2048位密钥对:
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命令,结果是验证确定:
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 ,原因错误的签名长度。我搜索了谷歌,但我没有找到任何关于该错误的有用信息。我也尝试了解OpenSSL的来源,但我并不是那么深入C而且似乎需要很长时间才能弄明白。
我个人的感觉是我在阅读公钥时做错了什么。但这只是一种感觉,我不知道如何以不同的方式做到这一点。我的第二个猜测是我在验证过程中做了一些错误的上下文。但我不知道那可能是什么。
为什么签名验证失败?
答案 0 :(得分:3)
好的,我找到了解决方案。实际上我不得不处理两个错误。第一个错误是我以错误的方式将签名传递给EVP_DigestVerifyFinal。这就是Maarten Bodewes在回答中所说的,我接受这个作为我问题的答案。
第二个问题在于我对DLL入口点的定义。我已经将EVP_DigistVerifyFinal的第三个参数声明为var param。可能是复制和过去的错误,因为EVP_DigistSignFinal的第三个参数是var param。
对于每个必须做同样事情的人,我在这里发布我的解决方案。它的灵感来自于阅读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.
答案 1 :(得分:1)
签名不是文字签名。它由一个字节数组成,字节可以有任何值。您正在将该字节数组直接转换为ANSI字符串。如果数组包含ANSI范围之外的值(无论可能是什么,我都假设ASCII),那将失败。
您需要将签名视为二进制数据。如果需要将其视为字符串(包含文本),则可以使用base 64编解码器。