Search code examples
opensslrsasigncngwincrypt

RSA SHA512 signature generated by Windows CNG(Cryptography Next generation ) NCryptSignHash not matching with signature generated by openssl RSA_sign


We are trying to generate RSA SHA512 signature with CNG, we wrote code and generated hash value not matching with OpenSSL.

Private key used for signing we generated with OpenSSL command in DER format as below.

openssl genpkey -out privkey.pem -algorithm rsa
openssl pkcs8 -topk8 -inform PEM -outform DER -in privkey.pem -out privkey.der -nocrypt
openssl rsa -in privkey.pem -pubout -outform DER -out pubkey.der

Using below code to generate the signature with Windows CNG:

#include<stdio.h>
#include<windows.h>
#include<wincrypt.h>
#pragma comment (lib, "crypt32")
#pragma comment(lib, "ncrypt.lib")
#include<ntstatus.h>
void
isi_hexdump(const char *msg, const unsigned char *in, unsigned int len)
{
  printf("%s [%d][", msg, len);
  for (; len > 0; len--, in++) {
    printf("%02X", *in);
  }
  printf("]\n");
}

static int
readBinFile(unsigned char **buffer, char *file, long *size)
{
  FILE *fp;
  size_t foo;
printf("\n inside readBinFile");
  fp = fopen(file,"rb");
  if (fp == NULL) {
    printf("Error: reading the file %s\n", file);
    return -1;
    }

  fseek(fp, 0, SEEK_END);
  *size = ftell(fp);
  fseek(fp, 0, SEEK_SET);
  *buffer = malloc(*size);

  if ((foo = fread(*buffer, 1, *size, fp)) != *size)
    {
    printf("Error: reading the file to buffer %s:%ld\n", file, *size);
    return -1;
    }
printf("\n exit from readBinFile <%s>",*buffer);
isi_hexdump("Pavan char at :",*buffer,*size);
  return 0;
}

NTSTATUS
ComputeHash(
    _In_reads_bytes_(DataLength)
                PBYTE           Data,
    _In_        DWORD           DataLength,
    _Outptr_result_bytebuffer_maybenull_(*DataDigestLengthPointer)
                PBYTE           *DataDigestPointer,
    _Out_       DWORD           *DataDigestLengthPointer
    )
{
    NTSTATUS                Status;
    
    BCRYPT_ALG_HANDLE       HashAlgHandle   = NULL;
    BCRYPT_HASH_HANDLE      HashHandle      = NULL;
    
    PBYTE                   HashDigest       = NULL;
    DWORD                   HashDigestLength = 0;

    DWORD                   ResultLength     = 0;
    
    *DataDigestPointer = NULL;
    *DataDigestLengthPointer = 0;

    //
    // Open a Hash algorithm handle
    //

    Status = BCryptOpenAlgorithmProvider(
                                        &HashAlgHandle,
                                        BCRYPT_SHA512_ALGORITHM,
                                        NULL,
                                        0);
    if(Status)
    {
       

    }

    
    //
    // Calculate the length of the Hash
    //
    
    Status= BCryptGetProperty(
                                        HashAlgHandle, 
                                        BCRYPT_HASH_LENGTH, 
                                        (PBYTE)&HashDigestLength, 
                                        sizeof(HashDigestLength), 
                                        &ResultLength, 
                                        0);
    if(Status)
    {
       

    }

    //allocate the Hash buffer on the heap
    HashDigest = (PBYTE)HeapAlloc (GetProcessHeap (), 0, HashDigestLength);
    if( NULL == HashDigest )
    {
        printf("\nNO memory");
        Status = STATUS_NO_MEMORY;
     

    }
    
    //
    // Create a Hash
    //

    Status = BCryptCreateHash(
                                        HashAlgHandle, 
                                        &HashHandle, 
                                        NULL, 
                                        0, 
                                        NULL, 
                                        0, 
                                        0);
    if(Status)
    {
       

    }
    
    //
    // Hash Data(s)
    //
    
    Status = BCryptHashData(
                                        HashHandle,
                                        (PBYTE)Data,
                                        DataLength,
                                        0);
                                        
/*    Status = BCryptHashData(
                                        HashHandle,
                                        (PBYTE)Data,
                                        sizeof(Data),
                                        0); */
    if(Status)
    {
       

    }
    
    //
    // Close the Hash
    //
    
    Status = BCryptFinishHash(
                                        HashHandle, 
                                        HashDigest, 
                                        HashDigestLength, 
                                        0);
    if(Status)
    {
       

    }
    isi_hexdump("Digest :",HashDigest,HashDigestLength);
    *DataDigestPointer = HashDigest;
    HashDigest = NULL;
    *DataDigestLengthPointer = HashDigestLength; 

    Status = 0;

cleanup:

    if( NULL != HashDigest )
    {
        HeapFree( GetProcessHeap(), 0, HashDigest );
        HashDigest = NULL;
    }

    if( NULL != HashHandle )
    {
        Status = BCryptDestroyHash(HashHandle);
        HashHandle = NULL;
    }

    if( NULL != HashAlgHandle )
    {
        BCryptCloseAlgorithmProvider(HashAlgHandle,0);
    }

    return Status;
}
int main()
{
  long privksize = 0;
  unsigned char *privkey = NULL;
  if (readBinFile(&privkey, "privkey.der", &privksize) != 0)
    return 1;
  CRYPT_DECODE_PARA decode_para = {0, 0, 0};
  BCRYPT_RSAKEY_BLOB *rsa_private_key = NULL;
  DWORD rsa_private_key_size = 0;
  BOOL ret_val = FALSE;
  PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;
  ret_val = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                    PKCS_PRIVATE_KEY_INFO, 
                    privkey,
                    privksize,
                    CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG,
                    0,
                    (void**)&PrivateKeyInfo,
                    &rsa_private_key_size);

  if(!ret_val) 
  {
      printf("\nCryptDecodeObjectEx() Failed : status <0x%x>, ret val <%d>",GetLastError(), ret_val);
  }
  else
  {
     printf("\nCryptDecodeObjectEx() Success : status <0x%x>, ret val <%d>",GetLastError(), ret_val);
  }
  
  ret_val = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            CNG_RSA_PRIVATE_KEY_BLOB,
            PrivateKeyInfo->PrivateKey.pbData,
            PrivateKeyInfo->PrivateKey.cbData, 
            CRYPT_DECODE_ALLOC_FLAG,
            0,
            (void**)&rsa_private_key, 
            &rsa_private_key_size);
  if(!ret_val) 
  {
      printf("\nCryptDecodeObjectEx() Failed : status <0x%x>, ret val <%d>",GetLastError(), ret_val);
  }
  else
  {
     printf("\nCryptDecodeObjectEx() Success : status <0x%x>, ret val <%d>",GetLastError(), ret_val);
  }
  LocalFree(PrivateKeyInfo);
  BCRYPT_KEY_HANDLE phKey;
  NCRYPT_PROV_HANDLE hProvider = 0;
  if (NCryptOpenStorageProvider(
        &hProvider,
        MS_KEY_STORAGE_PROVIDER,
        0)) {
        printf("\nNCryptOpenStorageProvider() Failed : status <0x%x>",GetLastError());
  }
  NCRYPT_KEY_HANDLE hKeyNew = 0;
  NTSTATUS                Status;
 
  if (NCryptImportKey(hProvider,
        0,
        BCRYPT_RSAPRIVATE_BLOB,
        //NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
        NULL,
        &hKeyNew,
        (BYTE *)rsa_private_key,
        rsa_private_key_size,
        NCRYPT_DO_NOT_FINALIZE_FLAG)) {
       printf("\nNCryptOpenStorageProvider() Failed : status <0x%x>",GetLastError());
  }
  
  DWORD export_policy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
  if (NCryptSetProperty(hKeyNew,
         NCRYPT_EXPORT_POLICY_PROPERTY,
         (PBYTE)&export_policy,
         sizeof(export_policy),
         NCRYPT_PERSIST_FLAG)) {
        printf("\nFailed to NCryptSetProperty");
   }
   if(NCryptFinalizeKey(hKeyNew, 0)) {

        printf("\nFailed to NCryptFinalizeKey");
   }
   
   
   
   
  PBYTE           MessageDigest   = NULL;
  DWORD           MessageDigestLength = 0;
  PBYTE           MessageToSign = "1234567890123456789012345678901234567890123456789012345678901234";
  DWORD           MessageLength = strlen(MessageToSign);
  Status = ComputeHash(MessageToSign,
                       MessageLength,
                       &MessageDigest,
                       &MessageDigestLength);
    if( Status )
    {
        printf("\nFailed to calculate hash");
    }
  printf("\n hashvalue \n");
  isi_hexdump("Digest :",MessageDigest,MessageDigestLength);
    PBYTE                   SignatureBlob   = NULL;
    DWORD                   SignatureBlobLength = 0;
   DWORD                   ResultLength    = 0;
   BCRYPT_PKCS1_PADDING_INFO   PKCS1PaddingInfo = {0};
   PKCS1PaddingInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM;
   
  
    Status = NCryptSignHash(
                                        hKeyNew,                    // Key handle used to sign the hash
                                        &PKCS1PaddingInfo,          // Padding information
                                        MessageDigest,              // Hash of the message
                                        MessageDigestLength,        // Length of the hash
                                        NULL,                       // Signed hash buffer
                                        0,                          // Length of the signature(signed hash value)
                                        &SignatureBlobLength,       // Number of bytes copied to the signature buffer
                                        BCRYPT_PAD_PKCS1);
    
    if( Status)
    {
        printf("\nFailed******** 1");

    }
    printf("\nPavan sign length <%d>",SignatureBlobLength);         
    //allocate the signature buffer
    SignatureBlob = (PBYTE)HeapAlloc (GetProcessHeap (), 0, SignatureBlobLength);
    if( NULL == SignatureBlob )
    {
        Status = NTE_NO_MEMORY;
        printf("\nFailed******** 1.0");

    }
    memset(SignatureBlob, '\0', SignatureBlobLength);

    Status = NCryptSignHash(
                                        hKeyNew,                    // Key handle used to sign the hash      
                                        &PKCS1PaddingInfo,          // Padding information
                                        MessageDigest,              // Hash of the message
                                        MessageDigestLength,        // Length of the hash
                                        SignatureBlob,              // Signed hash buffer
                                        SignatureBlobLength,        // Length of the signature(signed hash value)
                                        &SignatureBlobLength,       // Number of bytes copied to the signature buffer
                                        BCRYPT_PAD_PKCS1);
    
    if( Status)
    {
        printf("\nFailed******** 2 status <0x%x>",Status);

    }
    else
    {
        printf("\nSuccess NCryptSignHash");
    }
    isi_hexdump("Signature :",SignatureBlob,SignatureBlobLength);
    return 0;

}

Signature generated by Windows CNG:

NCryptSignHashSignature : [256][6B1CA169DA91DA0CEC2485E571B3EEA1595A94C3B15CA46DF3348E515E28516E3F7E7D9FBC7BC554D294DA994681301FF786058E453EFAA1B713B6D69C6A9C284289B6605B4FB1FD1F95A03632C2A4303D375DB60B6041F93E1E6BC9A2D4F9E1B46756D5CFE1D2054C602913ED905427B76BD9279E08716F7D1FF91897E08577046B3E9AF34A65C8D80C38FAF055CFFB26F1FB49E03070278B56555673729F80514F3A5BB8382C143EB1FA20F0519FD86435CB9333615378F05F0EE17C6824F0C2CA50A3AA65C4BBDBE50652CB0500A7438ECBBA77B9B6D1BB1BA1A86597CA630B4BD7AA132BC661ED66D8B64A06C96822E0FE87080AFDF2FB00CB79583AD95D]

Using below code to generate the signature with Openssl:

#include<stdio.h>
#include<windows.h>
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/des.h>
#include <openssl/ssl.h>
#include<wincrypt.h>

void
isi_hexdump(const char *msg, const unsigned char *in, unsigned int len)
{
  printf("%s [%d][", msg, len);
  for (; len > 0; len--, in++) {
    printf("%02X", *in);
  }
  printf("]\n");
}

static int
readBinFile(unsigned char **buffer, char *file, long *size)
{
  FILE *fp;
  size_t foo;
printf("\n inside readBinFile");
  fp = fopen(file,"rb");
  if (fp == NULL) {
    printf("Error: reading the file %s\n", file);
    return -1;
    }

  fseek(fp, 0, SEEK_END);
  *size = ftell(fp);
  fseek(fp, 0, SEEK_SET);
  *buffer = malloc(*size);

  if ((foo = fread(*buffer, 1, *size, fp)) != *size)
    {
    printf("Error: reading the file to buffer %s:%ld\n", file, *size);
    return -1;
    }
printf("\nExit from readBinFile <%s>",*buffer);
  return 0;
}

int main()
{

  unsigned char *privkey = NULL;
  unsigned char *pubkey = NULL;
  unsigned char *sig = NULL;
  long privksize, pubksize;
  unsigned int sigSize;
  char srcstr[] = "1234567890123456789012345678901234567890123456789012345678901234";
  int verified = -1;

  if (readBinFile(&privkey, "privkey.der", &privksize) != 0)
    return 1;
  if (readBinFile(&pubkey, "pubkey.der", &pubksize) != 0)
    return 1;

  printf("data='%s'\n", srcstr);
  sig = calloc(1, sigSize);

   EVP_PKEY *private_key = NULL;
   RSA *rsa = NULL;

    if ((private_key = d2i_AutoPrivateKey(NULL, &privkey, privksize)) == NULL) {
     printf("\nFailed d2i_AutoPrivateKey ");
    }
   sigSize = EVP_PKEY_size(private_key);

    sig = calloc(1, sigSize);
        if ((rsa = EVP_PKEY_get1_RSA(private_key)) == NULL) {
      printf("\nFailed EVP_PKEY_get1_RSA ");
    }
    if ((RSA_sign(EVP_MD_type(EVP_sha512()),
                          srcstr, strlen(srcstr), sig, &sigSize, rsa)) != 1) {
        printf("\nFailed RSA_sign");
    }
    isi_hexdump("OpenSSL sig",sig,sigSize);
    return 0;
}

Signature with OpenSSL: 045F86E2D7AFE8001A42A1B113F60F7B96513DB829034DF6BAA27D5E7A13AF52896FBD4DE870D0C3F6734AE8AB061BA6959500BF1473F8726E0CB2819A3FC14318AE3CEFED15AA43C82339290A88EBAE363507FE835A02110D5CE8ABF9594F9195AA8033DCD78C10AA589C9BC523A138655E8485C87A8AB4FAE77ED5DA4268C1BC373A9B1C88572AEB3FD020BBF891FAFE223F3DE5FF89E56B7A916663D76303FAD7C69F2014ACF0FB6418305426FB6CAFF6C332126DF138D7FF9220B0B384BD3A053AA123A521D0AACC5D474AD2A2D06416F38647BC0B4F6AE25AA7BBAC3F7DF6BAFFB9EE8EB4E8E00D4C67252E2C2CAFEDB2F344424B9034371FDDA6C8D152

We also tried to generate the signature with OpenSSL and validate it with NCryptVerifySignature windows CNG API but verification is failing.


Solution

  • The signing methods of both codes do not hash implicitly, i.e. the already hashed data must be passed. While this is satisfied for the CNG code, it is not satisfied for the OpenSSL code.

    For the OpenSSL code, the hash can be determined and applied as follows:

    ...
    char srcstr[] = "1234567890123456789012345678901234567890123456789012345678901234";
    unsigned char* digest;
    unsigned int digLen;
    digest_message((const unsigned char*)srcstr, strlen((const char*)srcstr), &digest, &digLen);
    ...
    if ((RSA_sign(EVP_MD_type(EVP_sha512()), digest, digLen, sig, &sigSize, rsa)) != 1) {
    ...
    

    with

    void digest_message(const unsigned char* message, size_t message_len, unsigned char** digest, unsigned int* digest_len)
    {
        EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
        EVP_DigestInit_ex(mdctx, EVP_sha512(), NULL);
        EVP_DigestUpdate(mdctx, message, message_len);
        *digest = (unsigned char*)OPENSSL_malloc(EVP_MD_size(EVP_sha512()));
        EVP_DigestFinal_ex(mdctx, *digest, digest_len);
        EVP_MD_CTX_free(mdctx);
    }
    

    from this example of the OpenSSL documentation.

    With this change, both codes provide the same signature on my machine (as expected for RSASSA-PKCS1-v1_5).