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.
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).