Verifying SHA256 signature with OpenSSL in Delphi fails
我尝试使用openssl libeay32.dll在Delphi中实现sha256签名和验证。因此,在第一步中,我使用以下openssl命令创建了一个rsa 2048位密钥对:
1 2 | openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -outform PEM -pubout -out public.pem |
就这么简单。我所做的下一步是创建一个能够从PEM文件中读取公钥和私钥的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; |
这似乎也奏效了。所以我执行了一些签名程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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命令,结果将被验证为正常:
1 | openssl dgst -sha256 -verify public.pem -signature message.sig message.txt |
所以看来我的签字程序也很正常。因此,我最终实施了一个验证程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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; |
正如您所看到的,我执行这个过程的方式与我执行签名过程的方式完全相同。不幸的是,结果是错误的。openssl返回的错误代码是
1 | error04091077:lib(4):func(145):reason:(119) |
这转化为lib rsa中的一个错误,函数int-rsa-verify,原因是签名长度错误。我搜索了谷歌,但没有找到任何关于那个错误的有用信息。我也试着理解OpenSSL源代码,但我对C的了解不深,似乎要花很长时间才能弄清楚。
我个人的感觉是我在读公钥时出错了。但这只是一种感觉,我不知道我如何以不同的方式来做。我的第二个猜测是我在验证过程中初始化上下文时做了错误的事情。但我不知道那可能是什么。
为什么签名验证失败?
好的,我找到了解决方案。事实上,我不得不处理两个错误。第一个错误是我以错误的方式将签名传递到evp-digestverifyfinal。这就是马丁·博德韦斯在回答中所说的,我接受这一点作为我问题的答案。
第二个问题在我对dll入口点的定义中。我已经将evp_digistverifyfinal的第三个参数声明为var参数。作为evp_digistsignfinal的第三个参数,可能是一个copy&past错误,它是一个var参数。
对于所有必须这样做的人,我将我的解决方案发布在这里。它的灵感来自于读取EVP签名和验证、DelphiOpenSSL和OpenSSL源(主要是dgst.c)。代码是用DelphiXE2实现和测试的。
请注意,我的代码不做任何错误处理,也不太关心释放内存。这意味着代码还没有准备好生产,您应该小心地使用它!
进口单位:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 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. |
实施情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | 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. |
签名不是文本签名。它由一个字节数组组成,字节可以有任何值。您正在将该字节数组直接转换为ANSI字符串和从ANSI字符串转换为ANSI字符串。如果数组包含的值超出了ansi范围(不管是什么,我都假定为ascii),那么这将失败。
您需要将签名视为二进制数据。如果需要将base 64编解码器视为字符串(包含文本),则可以使用它。