Search code examples
c++cryptographycng

Updating from Windows CryptDecrypt() to NCryptDecrypt() returns NTE_INVALID_PARAMETER(0x80090027) or STATUS_UNSUCCESSFUL(0xC0000001)?


Currently updating from Windows CryptDecrypt() [API is deprecated] to NCryptDecrypt() [Cryptography Next Generation].

The minimal sample code below tries to encrypt/decrypt using RSA 1024-bit.

Running with Microsoft Visual Studio Professional 2019, it outputs the error NTE_INVALID_PARAMETER (0x80090027)

Failed Decrypt NCryptDecrypt. Error -2146893785

or randomly STATUS_UNSUCCESSFUL (0xc0000001)

Failed Decrypt NCryptDecrypt. Error -1073741823

Encryption seems okay as no errors are generated, but decryption fails on NCryptDecrypt?

#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <winternl.h>
#include <ntstatus.h>
#include <sstream>
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <ncrypt.h>
#include <bcrypt.h>

#pragma comment(lib, "bcrypt.lib")
#pragma comment(lib, "ncrypt.lib")

//https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob

#pragma pack(push, 1)
struct MyRSAPublicBlob {
    BCRYPT_RSAKEY_BLOB blob{};
    BYTE exponent[3] = {};
    BYTE modulus[128] = {};

    MyRSAPublicBlob(const std::vector<BYTE>& mod, const std::vector<BYTE>& exp, ULONG modulusSize) : blob{}
    {
        blob.BitLength = modulusSize * 8;
        blob.Magic = BCRYPT_RSAPUBLIC_MAGIC;
        blob.cbModulus = modulusSize;
        blob.cbPublicExp = static_cast<DWORD>(exp.size());
        blob.cbPrime1 = 0;
        blob.cbPrime2 = 0;

        memcpy(exponent, exp.data(), exp.size());
        memcpy(modulus, mod.data(), mod.size());
    }

    MyRSAPublicBlob() : blob{} { }
};
#pragma pack(pop)

#pragma pack(push, 1)
struct MyRSAPrivateBlob {
    BCRYPT_RSAKEY_BLOB blob{};
    BYTE exponent[3] = {};
    BYTE modulus[128] = {};
    BYTE prime1[64] = {};
    BYTE prime2[64] = {};

    MyRSAPrivateBlob(const std::vector<BYTE>& mod, const std::vector<BYTE>& exp, ULONG modulusSize,
        const std::vector<BYTE>& p, const std::vector<BYTE>& q) : blob{}
    {
        blob.Magic = BCRYPT_RSAPRIVATE_MAGIC;
        blob.BitLength = modulusSize * 8;
        blob.cbModulus = modulusSize;
        blob.cbPublicExp = static_cast<DWORD>(exp.size());
        blob.cbPrime1 = static_cast<DWORD>(p.size());
        blob.cbPrime2 = static_cast<DWORD>(q.size());

        memcpy(exponent, exp.data(), exp.size());
        memcpy(modulus, mod.data(), mod.size());
        memcpy(prime1, p.data(), p.size());
        memcpy(prime2, q.data(), q.size());
    }

    MyRSAPrivateBlob() : blob{} { }
};
#pragma pack(pop)

int RSA1024_encrypt(char* plainText, int plainExtent, char* cipherText, int cipherExtent, struct MyRSAPublicBlob keyBlob)
{
    SECURITY_STATUS stat;
    NCRYPT_PROV_HANDLE hProv;
    NCRYPT_KEY_HANDLE hKey;
    DWORD numEncryptedBytes = 0;

    stat = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed NCryptOpenStorageProvider. Error " << stat;
        return 0;
    }

    stat = NCryptImportKey(hProv, NULL, BCRYPT_RSAPUBLIC_BLOB, NULL, &hKey, (PBYTE)&keyBlob.blob, sizeof(keyBlob),0);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed NCryptImportKey. Error " << stat;
        return 0;
    }

    stat = NCryptEncrypt(hKey,(PBYTE)plainText, plainExtent, NULL, (PBYTE)cipherText, cipherExtent, &numEncryptedBytes, NCRYPT_PAD_PKCS1_FLAG);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed NCryptEncrypt. Error " << stat;
        return 0;
    }

    return numEncryptedBytes;
}


int RSA1024_decrypt(char* plainText, int plainExtent, char* cipherText, int cipherExtent, struct MyRSAPrivateBlob keyBlob)
{
    SECURITY_STATUS stat;
    NCRYPT_PROV_HANDLE hProv;
    NCRYPT_KEY_HANDLE hKey;
    DWORD numDecryptedBytes = 0;

    stat = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed Decrypt NCryptOpenStorageProvider. Error " << stat;
        return 0;
    }

    stat = NCryptImportKey(hProv, NULL, BCRYPT_RSAPRIVATE_BLOB, NULL, &hKey, (PBYTE)&keyBlob.blob, sizeof(keyBlob),0);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed Decrypt NCryptImportKey. Error " << stat;
        return 0;
    }

    // NTE_INVALID_PARAMETER(0x80090027) or STATUS_UNSUCCESSFUL(0xC0000001)
    stat = NCryptDecrypt(hKey,(PBYTE)cipherText,cipherExtent, NULL, (PBYTE)plainText, plainExtent, &numDecryptedBytes, NCRYPT_PAD_PKCS1_FLAG);
    if (ERROR_SUCCESS != stat) {
        std::cout << "Failed Decrypt NCryptDecrypt. Error " << stat;
        return 0;
    }

    return numDecryptedBytes;
}


int main()
{
    constexpr BYTE publicExponent[] = {
        0x1, 0x0, 0x1
    };

    constexpr BYTE modulus[] = {
        0xb0,0x71,0x76,0x5d,0xef,0x1b,0x6f,0x3c,
        0xf2,0xde,0x31,0x8c,0xa1,0x66,0x50,0xee,
        0x01,0x96,0x64,0x7e,0x1d,0x3e,0xac,0xa9,
        0xad,0x06,0x31,0x56,0x97,0x1d,0x3c,0x71,
        0xeb,0xcc,0xef,0xa1,0x24,0xb0,0xb7,0x0a,
        0x33,0xf7,0x29,0x00,0x36,0x4c,0x4c,0x72,
        0xd9,0x88,0x39,0xa8,0xf6,0x27,0xc9,0x24,
        0xfc,0x4d,0x38,0xb3,0x03,0x06,0x56,0xe3,
        0xfc,0x67,0x6a,0xb3,0xe2,0xf1,0xc1,0x4a,
        0xf9,0xd2,0xab,0xdd,0xae,0xd0,0x2e,0xca,
        0x11,0x41,0x32,0xd8,0x07,0xff,0x3a,0xb2,
        0x27,0x3c,0xb9,0xcd,0x64,0x4f,0xbd,0x23,
        0xb6,0x01,0x77,0x33,0xd8,0x6f,0x5a,0x84,
        0xaa,0x9c,0x21,0x0b,0xd8,0xdb,0xe7,0xe2,
        0x7e,0x9b,0x6a,0x7b,0x4c,0x5e,0xae,0x1d,
        0xda,0x78,0x02,0x87,0xeb,0xe6,0x4f,0x7b
    };

    constexpr BYTE privateExponent[] = {
        0x1e,0xbc,0x24,0xfa,0x47,0xe4,0x67,0x84,
        0x1e,0x6a,0x46,0x07,0x51,0x36,0x19,0x72,
        0xdc,0x23,0xee,0x6d,0x69,0x7a,0xb9,0x68,
        0xf5,0x12,0xd7,0x15,0x56,0x4d,0x69,0x72,
        0x0e,0xb9,0x2c,0x24,0xcd,0xd7,0x5a,0x8b,
        0x14,0x72,0x41,0x5a,0x20,0x1b,0x3a,0x55,
        0xe7,0x3e,0xab,0x8c,0x9b,0x14,0x63,0x1d,
        0x66,0x35,0xad,0x62,0xc1,0x6c,0x21,0x46,
        0x5b,0x6e,0x1d,0x81,0xe1,0x5a,0xa1,0x45,
        0x37,0x9b,0x70,0xca,0xc3,0x7a,0xee,0x52,
        0xe2,0x50,0xe1,0x5b,0x1c,0xeb,0x57,0x91,
        0x3c,0xad,0xd5,0x6c,0x4f,0x90,0xcc,0x2b,
        0x3d,0xde,0x25,0x3d,0xfc,0x35,0x9f,0x35,
        0x4e,0xf1,0xbf,0x34,0x6a,0x32,0xa4,0xbc,
        0xee,0xcb,0x65,0xdf,0x4d,0x28,0xb8,0x4a,
        0x92,0xc1,0xe1,0xab,0x44,0xd0,0x8f,0x81
    };

    constexpr BYTE prime1[] = {
        0xd5,0x06,0x53,0x6b,0xba,0xd7,0x3e,0x8f,
        0x1d,0xae,0x2c,0xc6,0xb2,0x7c,0x73,0xb8,
        0x83,0xfd,0x5d,0x64,0xf4,0x17,0x77,0xb0,
        0xc8,0x4d,0xb3,0x19,0x19,0xf4,0xad,0xe4,
        0x4d,0x31,0x3c,0xef,0x04,0xc6,0xe8,0xb4,
        0x17,0x9f,0x66,0x4f,0x62,0xf8,0x78,0x03,
        0x39,0xa7,0xa8,0xf1,0x64,0x70,0x86,0x14,
        0x63,0xda,0xe6,0x68,0x98,0x3d,0x29,0x03
    };

    constexpr BYTE prime2[] = {
        0xd4,0x09,0xe2,0x00,0x3e,0x6d,0x55,0x59,
        0x87,0xcf,0xa1,0xee,0xe2,0x5d,0xbc,0x8d,
        0x80,0x43,0x92,0x71,0xdc,0x53,0xa8,0x88,
        0x00,0xc5,0x5b,0xde,0xb2,0x1c,0xb9,0x95,
        0xa7,0x64,0x57,0x0f,0x27,0x88,0xf9,0x31,
        0xab,0x68,0x0c,0xf0,0xfe,0x12,0x76,0x84,
        0xf4,0x09,0x5b,0xc2,0x87,0x0e,0x25,0x73,
        0x99,0x23,0x33,0x26,0x2d,0x8a,0xea,0x29
    };

    char plainText[] = "The quick brown fox jumps over the lazy dog";
    char cipherText[512] = { 0 };
    char plainTextOut[512] = { 0 };

    std::vector<BYTE> publicExponent_bin(std::begin(publicExponent), std::end(publicExponent));
    std::vector<BYTE> modulus_bin(std::begin(modulus), std::end(modulus));
    std::vector<BYTE> privateExponent_bin(std::begin(privateExponent), std::end(privateExponent));
    std::vector<BYTE> p(std::begin(prime1), std::end(prime1));
    std::vector<BYTE> q(std::begin(prime2), std::end(prime2));

    struct MyRSAPublicBlob keyBlob(modulus_bin, publicExponent_bin, 128);
    int numEncryptedBytes = RSA1024_encrypt(plainText, sizeof(plainText)-1, cipherText, sizeof(cipherText), keyBlob);

    struct MyRSAPrivateBlob keyPrivBlob(privateExponent_bin, publicExponent_bin, 128, p, q);
    int numDecryptedBytes = RSA1024_decrypt(plainTextOut, sizeof(plainTextOut), cipherText, numEncryptedBytes, keyPrivBlob);

    return 0;
}

The key values in the sample were taken from this output:

C:\Projects>openssl genrsa -out private-key.pem 1024
Generating RSA private key, 1024 bit long modulus (2 primes)
.....+++++
......+++++
e is 65537 (0x010001)

C:\Projects>openssl rsa -in private-key.pem -pubout -out public-key.pem
writing RSA key

C:\Projects>openssl rsa -in private-key.pem -text -noout
RSA Private-Key: (1024 bit, 2 primes)
modulus:
    00:b0:71:76:5d:ef:1b:6f:3c:f2:de:31:8c:a1:66:
    50:ee:01:96:64:7e:1d:3e:ac:a9:ad:06:31:56:97:
    1d:3c:71:eb:cc:ef:a1:24:b0:b7:0a:33:f7:29:00:
    36:4c:4c:72:d9:88:39:a8:f6:27:c9:24:fc:4d:38:
    b3:03:06:56:e3:fc:67:6a:b3:e2:f1:c1:4a:f9:d2:
    ab:dd:ae:d0:2e:ca:11:41:32:d8:07:ff:3a:b2:27:
    3c:b9:cd:64:4f:bd:23:b6:01:77:33:d8:6f:5a:84:
    aa:9c:21:0b:d8:db:e7:e2:7e:9b:6a:7b:4c:5e:ae:
    1d:da:78:02:87:eb:e6:4f:7b
publicExponent: 65537 (0x10001)
privateExponent:
    1e:bc:24:fa:47:e4:67:84:1e:6a:46:07:51:36:19:
    72:dc:23:ee:6d:69:7a:b9:68:f5:12:d7:15:56:4d:
    69:72:0e:b9:2c:24:cd:d7:5a:8b:14:72:41:5a:20:
    1b:3a:55:e7:3e:ab:8c:9b:14:63:1d:66:35:ad:62:
    c1:6c:21:46:5b:6e:1d:81:e1:5a:a1:45:37:9b:70:
    ca:c3:7a:ee:52:e2:50:e1:5b:1c:eb:57:91:3c:ad:
    d5:6c:4f:90:cc:2b:3d:de:25:3d:fc:35:9f:35:4e:
    f1:bf:34:6a:32:a4:bc:ee:cb:65:df:4d:28:b8:4a:
    92:c1:e1:ab:44:d0:8f:81
prime1:
    00:d5:06:53:6b:ba:d7:3e:8f:1d:ae:2c:c6:b2:7c:
    73:b8:83:fd:5d:64:f4:17:77:b0:c8:4d:b3:19:19:
    f4:ad:e4:4d:31:3c:ef:04:c6:e8:b4:17:9f:66:4f:
    62:f8:78:03:39:a7:a8:f1:64:70:86:14:63:da:e6:
    68:98:3d:29:03
prime2:
    00:d4:09:e2:00:3e:6d:55:59:87:cf:a1:ee:e2:5d:
    bc:8d:80:43:92:71:dc:53:a8:88:00:c5:5b:de:b2:
    1c:b9:95:a7:64:57:0f:27:88:f9:31:ab:68:0c:f0:
    fe:12:76:84:f4:09:5b:c2:87:0e:25:73:99:23:33:
    26:2d:8a:ea:29
exponent1:
    00:c1:4d:0d:33:ab:86:97:e8:ec:08:d9:ee:af:95:
    c8:b8:3d:65:12:73:82:1f:2d:68:08:4a:a1:62:fc:
    af:8f:7f:a4:20:32:e7:bd:50:f5:66:3e:2d:51:7c:
    66:15:8b:69:79:ce:ce:b9:c4:e7:6a:73:64:2d:05:
    79:11:f4:25:9b
exponent2:
    4a:ec:eb:15:56:f9:df:6c:f1:96:a7:0b:f8:a5:52:
    d9:55:77:8b:29:fc:c6:fb:08:83:ed:39:57:69:ec:
    c8:8f:5f:45:0f:96:65:4b:fb:72:57:b5:3e:cd:71:
    9a:28:93:36:80:90:12:1f:13:1a:9c:cc:82:29:b2:
    d5:e8:fe:71
coefficient:
    16:61:78:25:39:97:45:9d:f0:f1:f1:79:89:48:ac:
    11:8b:1c:bd:00:d4:b1:ed:6f:20:8e:da:6d:ad:8b:
    cf:59:d0:6b:87:6d:44:bd:25:5b:a5:09:99:f5:e1:
    2c:68:d0:e6:8e:6b:e7:bb:8a:b8:fd:d1:0e:d7:c8:
    d6:46:7f:3c

C:\Projects>

UPDATED

Fixed. As noted in the answer, to get the decryption working correctly, change this line of code from

struct MyRSAPrivateBlob keyPrivBlob(privateExponent_bin, publicExponent_bin, 128, p, q);

to

struct MyRSAPrivateBlob keyPrivBlob(modulus_bin, publicExponent_bin, 128, p, q);

Viola! Decryption works!

OPTIMIZATION

To speed things up even further by using the Chinese Remainder Theorem (CRT) optimization, replace the current BCRYPT_RSAPRIVATE_BLOB with BCRYPT_RSAFULLPRIVATE_BLOB, then expand MyRSAPrivateBlob to encompass all the following values:

BCRYPT_RSAKEY_BLOB
    PublicExponent[cbPublicExp] // Big-endian.
    Modulus[cbModulus] // Big-endian.
    Prime1[cbPrime1] // Big-endian.
    Prime2[cbPrime2] // Big-endian.
    Exponent1[cbPrime1] // Big-endian.
    Exponent2[cbPrime2] // Big-endian.
    Coefficient[cbPrime1] // Big-endian.
    PrivateExponent[cbModulus] // Big-endian.

Solution

  • A private key BLOB contains BCRYPT_RSAKEY_BLOB header, public exponent, modulus, prime1 and prime2, see here. Notably, the private exponent is not included. This is taken into account in the definition of MyRSAPrivateBlob, but not in the instantiation of keyPrivBlob. The latter must be:

    struct MyRSAPrivateBlob keyPrivBlob(modulus_bin, publicExponent_bin, 128, p, q);
    

    privateExponent and privateExponent_bin are not needed at all and can therefore be removed.

    With this change, decryption works.