SHA256 signature verification with OpenSSL in Delphi fails

I am trying to subscribe and verify SHA256 in Delphi using OpenSSL libeay32.dll. Therefore, at the first stage, I created an RSA key pair of 2048 bits using the following OpenSSL commands:

openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -outform PEM -pubout -out public.pem 

It is so simple. The next step I made was to create a function that could read public and private keys from PEM files:

 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; 

This seems to work too. Therefore, I followed some sign procedure:

 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; 

As you can see, the procedure reads a file called message.txt, calculating the signature and saving this whiteboard in message.sig. If I run the following OpenSSL command, the result will be OK Confirmed :

 openssl dgst -sha256 -verify public.pem -signature message.sig message.txt 

So it looks like my signing procedure is also working correctly. So I finally implemented a validation procedure:

 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; 

As you can see, I performed this procedure in the same way as when performing the signing procedure. Unfortunately, the result of this is false . The error code returned by OpenSSL is

 error04091077:lib(4):func(145):reason:(119) 

This translates to an error in the RSA lib, function int_rsa_verify, reason Signature length is incorrect. I searched Google, but I did not find useful information about this error. I also tried to understand the sources of OpenSSL, but I'm not so deep in C, and it seems like it may take years until I can figure it out.

My personal feeling is that I did something wrong by reading the public key. But this is only a feeling, and I have no idea how I could do it differently. My second assumption was that I did something wrong by initializing the context in the validation procedure. But I have no idea what it could be.

Why is signature verification missing?

+5
source share
2 answers

A signature is not a text signature. It consists of an array of bytes, for which bytes can have any value. You convert this byte array directly to and from ANSI strings. This will happen if the array contains values ​​outside the ANSI range (no matter what it might be, I would suggest ASCII).

You need to process the signature as binary data. You can use the base 64 codec if you need to consider it as a string (containing text).

+1
source

OK, I found a solution. In fact, I had to deal with two errors. The first mistake was that I incorrectly passed the signature to EVP_DigestVerifyFinal. This is exactly what Maarten Bodvis said in his answer, and I will accept this in response to my question.

The second problem was my definition of the entry point to the DLL. I declared the third parameter EVP_DigistVerifyFinal as the var parameter. The past copy error as the third parameter EVP_DigistSignFinal IS is probably a var parameter.

For anyone who will ever do the same, I post my solution here. This was inspired by reading Signing and Validating EVP , DelphiOpenSSL, and OpenSSL Sources (mainly dgst.c). The code has been implemented and tested using Delphi XE2.

Keep in mind that my code does not perform any error operations and does not care about freeing memory. This means that the code is not ready for production, and you should use it with caution!

Import Unit:

 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. 

Implementation:

 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. 
+3
source

Source: https://habr.com/ru/post/1247635/


All Articles