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?