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

时间:2016-04-21 15:20:28

标签: delphi openssl rsa digital-signature

我正在尝试使用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而且似乎需要很长时间才能弄明白。

我个人的感觉是我在阅读公钥时做错了什么。但这只是一种感觉,我不知道如何以不同的方式做到这一点。我的第二个猜测是我在验证过程中做了一些错误的上下文。但我不知道那可能是什么。

为什么签名验证失败?

2 个答案:

答案 0 :(得分:3)

好的,我找到了解决方案。实际上我不得不处理两个错误。第一个错误是我以错误的方式将签名传递给EVP_DigestVerifyFinal。这就是Maarten Bodewes在回答中所说的,我接受这个作为我问题的答案。

第二个问题在于我对DLL入口点的定义。我已经将EVP_DigistVerifyFinal的第三个参数声明为var param。可能是复制和过去的错误,因为EVP_DigistSignFinal的第三个参数是var param。

对于每个必须做同样事情的人,我在这里发布我的解决方案。它的灵感来自于阅读EVP Signing and VerifyingDelphiOpenSSL和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编解码器。