Search code examples
c#csigningcryptoapidsa

ECDSA signing in c# verify in c


I'm trying to sign data in C#, using ECDSA algorithm (this part looks OK) and to verify signature in C using Windows crypto API.

Signature part:

CngKeyCreationParameters keyCreationParameters = new CngKeyCreationParameters();
keyCreationParameters.ExportPolicy = CngExportPolicies.AllowPlaintextExport;
keyCreationParameters.KeyUsage = CngKeyUsages.Signing;

CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256, null, keyCreationParameters);

ECDsaCng dsa = new ECDsaCng(key); //dsa = Digital Signature Algorithm
byte[] privateKey = dsa.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
File.WriteAllText("privatekey.txt", String.Join(",", privateKey));


byte[] publicKey = dsa.Key.Export(CngKeyBlobFormat.EccPublicBlob);
File.WriteAllText("publicKey.txt", String.Join(",", publicKey));


CngKey importedKey = CngKey.Import(File.ReadAllText("privatekey.txt").Split(',').Select(m => byte.Parse(m)).ToArray(), CngKeyBlobFormat.EccPrivateBlob);
ECDsaCng importedDSA = new ECDsaCng(importedKey); //dsa = Digital Signature Algorithm
byte[] signed = dsa.SignData(new byte[] { 1, 2, 3, 4, 5 });
File.WriteAllText("signed.txt", String.Join(",", signed));

At this point I'm able to create a signature key and export it to a byte buffer in a file.

Problems come when I'm trying to import this public key in C using windows crypto API.

BYTE KeyBlob[] = { // public key exported by above c# code
    69,67,83,49,32,0,0,0,227,146,138,255,218,235,122,141,44,110,211,95,59,227,226,163,81,188,242,115,60,171,46,141,221,117,169,139,42,143,67,85,144,242,232,188,22,158,230,15,110,6,214,252,252,242,224,241,110,186,1,244,176,65,88,184,94,19,98,174,158,7,154,152
};

int _tmain()
{
    HCRYPTPROV hProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD dwBlobLen;
    BYTE* pbKeyBlob;

    if (!CryptAcquireContext(
        &hProv,
        NULL,
        MS_ENHANCED_PROV,
        PROV_RSA_FULL,
        CRYPT_VERIFYCONTEXT))
    {
        printf(" Error in AcquireContext 0x%08x \n", GetLastError());
        return 1;
    }

    if (!CryptImportKey(
        hProv,
        KeyBlob,
        sizeof(DesKeyBlob),
        0,
        CRYPT_EXPORTABLE,
        &hKey))
    {
        printf("Error 0x%08x in importing the key \n",
            GetLastError());
    }

which returns

Error 0x80090007 in importing the key

which is (believing winerror.h) :

//
// MessageId: NTE_BAD_VER
//
// MessageText:
//
// Bad Version of provider.
//
#define NTE_BAD_VER                      _HRESULT_TYPEDEF_(0x80090007L)

What do I do wrong?


Solution

  • Thanks to this wonderful website I just found : referencesouce.microsoft.com, I've been able to disassemble what the C# API does when verifying a signature and importing a key.

    Apparently I need ncrypt/bcrypt, and signature is verified against a hash, and not the data itself:

    public bool VerifyData(Stream data, byte[] signature) {
                if (data == null) {
                    throw new ArgumentNullException("data");
                }
                if (signature == null) {
                    throw new ArgumentNullException("signature");
                }
    
                using (BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(HashAlgorithm, BCryptNative.ProviderName.MicrosoftPrimitiveProvider)) {
                    hashAlgorithm.HashStream(data);
                    byte[] hashValue = hashAlgorithm.HashFinal();
    
                    return VerifyHash(hashValue, signature);
                }
            }
    
            [SecuritySafeCritical]
            public override bool VerifyHash(byte[] hash, byte[] signature) {
                if (hash == null) {
                    throw new ArgumentNullException("hash");
                }
                if (signature == null) {
                    throw new ArgumentNullException("signature");
                }
    
                // We need to get the raw key handle to verify the signature. Asserting here is safe since verifiation
                // is not a protected operation, and we do not expose the handle to the user code.
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
    
                // This looks odd, but Key.Handle is really a duplicate so we need to dispose it
                using (SafeNCryptKeyHandle keyHandle = Key.Handle) {
                    CodeAccessPermission.RevertAssert();
    
                    return NCryptNative.VerifySignature(keyHandle, hash, signature);
                }
            }
    

    Here is a native solution, for whoever needs that:

    #include <windows.h>
    #include <wincrypt.h>
    #include <stdio.h>
    
    char key[72] = { 69,67,83,49,32,0,0,0,227,146,138,255,218,235,122,141,44,110,211,95,59,227,226,163,81,188,242,115,60,171,46,141,221,117,169,139,42,143,67,85,144,242,232,188,22,158,230,15,110,6,214,252,252,242,224,241,110,186,1,244,176,65,88,184,94,19,98,174,158,7,154,152 };
    char sign[64] = { 165,50,54,149,14,175,128,54,21,30,129,165,137,203,45,123,180,121,118,20,15,61,253,186,65,129,21,26,54,84,40,205,103,254,108,34,126,205,116,183,44,189,5,180,28,119,228,70,127,116,227,248,232,144,53,226,185,251,217,179,148,88,208,152 };
    char message[] = { 1, 2, 3, 4, 5 };
    
    BOOL crypt_init(char* key, unsigned long key_len)
    {
        HCRYPTPROV    hProv = NULL;
    
        BCRYPT_ALG_HANDLE       hHashAlg = NULL, hSignAlg = NULL;
        BCRYPT_HASH_HANDLE      hHash = NULL;
        PBYTE                   pbHash = NULL;
        PBYTE                   pbHashObject = NULL;
        DWORD                   cbHashObject = 0,
                                cbHash = 0,
                                cbData = 0;
    
        NTSTATUS status;
    
        if (ERROR_SUCCESS != NCryptOpenStorageProvider(&hProv, NULL, 0)) {
            printf("CryptAcquireContext failed - err=0x%x.\n", GetLastError());
            return FALSE;
        }
    
        NCRYPT_KEY_HANDLE keyHandle;
    
        if (ERROR_SUCCESS != NCryptImportKey(hProv, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, &keyHandle, (PBYTE)key, 72, 0)) {
            printf("CryptAcquireContext failed - err=0x%x.\n", GetLastError());
            return FALSE;
        }
    
        if (!BCRYPT_SUCCESS(status = BCryptOpenAlgorithmProvider(
            &hHashAlg,
            BCRYPT_SHA256_ALGORITHM,
            NULL,
            0)))
        {
            printf("BCryptOpenAlgorithmProvider failed - err=0x%x.\n", status);
            return false;
        }
    
        if(!BCRYPT_SUCCESS(status = BCryptGetProperty(hHashAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0))) {
            printf("BCryptGetProperty failed - err=0x%x.\n", status);
            return FALSE;
        }
    
        pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHashObject);
        if (NULL == pbHashObject) {
            printf("memory allocation failed\n");
            return FALSE;
        }
    
        if (!BCRYPT_SUCCESS(status = BCryptGetProperty(hHashAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0))) {
            printf("BCryptGetProperty failed - err=0x%x.\n", status);
            return FALSE;
        }
    
        pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHash);
        if (NULL == pbHash)
        {
            printf("memory allocation failed\n");
            return FALSE;
        }
    
        if (!BCRYPT_SUCCESS(status = BCryptCreateHash(hHashAlg, &hHash, pbHashObject, cbHashObject, NULL, 0, 0)))
        {
            printf("BCryptCreateHash failed - err=0x%x.\n", status);
            return FALSE;
        }
    
        if (!BCRYPT_SUCCESS(status = BCryptHashData(hHash, (PBYTE)message, sizeof(message), 0)))
        {
            printf("BCryptHashData failed - err=0x%x.\n", status);
            return FALSE;
        }
    
        if (!BCRYPT_SUCCESS(status = BCryptFinishHash(hHash, pbHash, cbHash, 0)))
        {
            printf("BCryptFinishHash failed - err=0x%x.\n", status);
            return FALSE;
        }
    
        if(ERROR_SUCCESS != NCryptVerifySignature(keyHandle, NULL, pbHash, cbHash, (PBYTE) sign, sizeof(sign), 0)) {
            printf("BCryptVerifySignature failed - err=0x%x.\n", status);
            return FALSE;
        }
    
    
        return TRUE;
    }
    
    int main() {
        crypt_init(key, 72);
    }