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

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编解码器视为字符串(包含文本),则可以使用它。