Search code examples
c++cryptographyrsacryptoapicng

c++ Import RSA private key in CAPI/CNG from xml


I read in this question about Importing a public key from native components. I tried doing the same for private key based on the BLOB documentation, but I'm getting a NTE_BAD_DATA error.

Is my idea feasible? If so, Can you help?

My Try:

void old_RSA_decrypt(PBYTE blob, DWORD blobSize)
{
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD dwDecryptedLen = 0;
    DWORD length;
    std::ifstream f;
    f.open("c:\\Programming\\encrypted.txt", std::ios::binary);
    if (!f.is_open())
    {
        std::cout << "Error on open file: " << GetLastError() << std::endl;
        return;
    }

    f.seekg(0, f.end);
    length = f.tellg();
    f.seekg(0, f.beg);

    char * buffer = new char[length];
    f.read(buffer, length);

    if (!f)
        std::cout << "error: only " << f.gcount() << " could be read" << std::endl;
    f.close();

    PBYTE bBuffer = (PBYTE)buffer;

    //now to get the decryption thing going

    if (!CryptAcquireContext(
        &hCryptProv,
        NULL,
        MS_STRONG_PROV,
        PROV_RSA_FULL,
        CRYPT_VERIFYCONTEXT))

    {
        std::cout << "Error on CryptAcquireContext " << GetLastError() << std::endl;
        return;
    }
    if (!CryptImportKey(
        hCryptProv,
        blob,
        blobSize,
        NULL,
        0,
        &hKey))
    {
        std::cout << "Error on CryptImportKey " << GetLastError() << std::endl;
        return;
    }
    if (!CryptDecrypt(hKey, NULL, TRUE, 0, NULL, &dwDecryptedLen))
    {
        std::cout << "Error on CryptDecrypt (First Pass) " << GetLastError() << std::endl;
        return;
    }
    PBYTE decBuffer = new BYTE[dwDecryptedLen];
    for(int i = 0; i < length ; ++i)
        decBuffer[i] = bBuffer[i];

    if (!CryptDecrypt(hKey, NULL, TRUE, 0, decBuffer, &length))
    {
        std::cout << "Error on CryptDecrypt (Second Pass) " << GetLastError() << std::endl;
        return;
    }
    std::cout << "Yurika2!" << std::endl;
    std::ofstream of;
    of.open("c:\\Programming\\decrypted.txt", std::ios::binary);
    if (!of.is_open())
    {
        std::cout << "Error on open write file: " << GetLastError() << std::endl;
        return;
    }
    string sDecMsg = string(reinterpret_cast<char*>(decBuffer), length);
    of << sDecMsg << std::endl;
    of.close();
cleanup:
    delete[] buffer;
    delete[] decBuffer;

}

void do_decrypt()
{
    string modStr = "yVUndgQFuB5Z5FgC0/WgWCg6Y8VuB582avGjQDdeoJDa1+RBKCyXo700sAMSGjM/bVakOlFqvCsVFNBysx1CH731CDb2DR1a0bsmYmDQ9d0ZHX+AOohVDIx9mc7bkDQZoEFpe9NqFsu95Y9yktpl1JKPmKyLOFgufGJYYvQyoOM=";
    string expStr = "AQAB";
    string PStr = "/JydNn89lSWjgWOG1XRJm1qTWDekzzoLfTQU+GK+h8DGQ6gkUbgqGosLGo+eAxbO/ETZV3ibbBuIdvL4UxC5Qw==";
    string QStr = "zAh23Gc8Oqz/Uh2wh+yt8DqUesVLwMn2koc9CbyF9/Z5Qe8OIR4yygJtuYruRC1x/KYj85l6DGzstUZOtYmv4Q==";
    string DPStr ="+1INj1SUPjjOLUKJuQAS4z7/7PqfO5RyLcSNQHltOb5vAozcZXkmWnYPPAO6nzQoBg+xdDcH2kyiPkWJDYtL5Q==";
    string DQStr = "cbYh8HJEufrijTRox0hcJG+xgr7kmjy1BDMFDKEaFPkz2VBPEpwO+FDkMC1C35JoXcOGc+RMhhJK1jip8zkaYQ==";
    string InverseQStr = "3PAXzlAXgvLVrbOEygjA2zhJEYALBEi6VTKqfDKlnv8/D9QUkC39bEDIRLG0wMFFxN8NlLx5zTiiVswxnMy8Mw==";
    string DStr = "KKBSyKkyID+bowyxcWUAuJlRgv19YPNbL0RYTWZ+5UalqmfoT/uDk+pjndrYxcmulFkl5ZC1SYgmBl+zrXoLc/Ei86BtNiuwfcqHlUDp0fdP+fyYN45wh/251HQ3UM1zBpMP8XeYB6zjpCU/s3/wCBE6WpJWN9fKcG0W5PLq8eE=";

    //FROM STRINGS TO BYTE VECTORS!
    vector<BYTE> modBinMSB = base64_decode(modStr);
    vector<BYTE> expBinMSB = base64_decode(expStr);
    vector<BYTE> PBinMSB = base64_decode(PStr);
    vector<BYTE> QBinMSB = base64_decode(QStr);
    vector<BYTE> DPBinMSB = base64_decode(DPStr);
    vector<BYTE> DQBinMSB = base64_decode(DQStr);
    vector<BYTE> InverseQBinMSB = base64_decode(InverseQStr);
    vector<BYTE> DBinMSB = base64_decode(DStr);

    //TURN MSB TO LSB

    DWORD offset = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY); // to keep track of things
    const DWORD modulusLengthInBytes = 128;
    DWORD keyBlobLength = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + (modulusLengthInBytes * 4) + (modulusLengthInBytes / 2);
    BYTE* keyBlob = (PBYTE)malloc(keyBlobLength);
    BLOBHEADER* blobheader = (BLOBHEADER*)keyBlob;
    blobheader->bType = PRIVATEKEYBLOB;
    blobheader->bVersion = CUR_BLOB_VERSION;
    blobheader->reserved = 0;
    blobheader->aiKeyAlg = CALG_RSA_KEYX;
    RSAPUBKEY* rsapubkey = (RSAPUBKEY*)(keyBlob + sizeof(BLOBHEADER));
    rsapubkey->magic = 0x31415352;
    rsapubkey->bitlen = modulusLengthInBytes * 8 *4 + modulusLengthInBytes*4;
    rsapubkey->pubexp = MSBByteVectorToDword(expBinMSB);

    BYTE* modulus = keyBlob + offset;
    copyReversed(modBinMSB, modulus);
    offset += modulusLengthInBytes;
    BYTE* prime1 = keyBlob + offset ;
    copyReversed(PBinMSB, prime1);
    offset += modulusLengthInBytes / 2;
    BYTE* prime2 = keyBlob + offset;
    copyReversed(QBinMSB, prime2);
    offset += (modulusLengthInBytes / 2);
    BYTE* exponent1 = keyBlob + offset;
    copyReversed(DPBinMSB, exponent1);
    offset += (modulusLengthInBytes / 2);
    BYTE* exponent2 = keyBlob + offset;
    copyReversed(DQBinMSB, exponent2);
    offset += (modulusLengthInBytes / 2);
    BYTE* coefficient = keyBlob + offset;
    copyReversed(InverseQBinMSB, coefficient);
    offset += modulusLengthInBytes / 2;
    BYTE* privateExponent = keyBlob + offset;
    copyReversed(DBinMSB, privateExponent);

    old_RSA_decrypt(keyBlob, keyBlobLength);
}

Solution

  • It's certainly possible to do so. You mentioned both Windows crypto stacks, and there are some differences:

    • Encoding:
      • CAPI: all of the variable-length fields are little-endian.
      • CNG: all of the variable-length fields are big-endian.
    • Rigidity:
      • CAPI: the modulus and D must have the same length. Also, P, Q, DP, DQ, InverseQ all have the same length (which must be half (round up) the length of the modulus).
      • CNG: private keys ask only for n, e, p, and q... and you specify the length of each field separately.

    I see one obvious error in your code:

    rsapubkey->bitlen = modulusLengthInBytes * 8 *4 + modulusLengthInBytes*4;
    

    Per the documentation:

    bitlen

    Number of bits in the modulus. In practice, this must always be a multiple of eight.

    So just

    rsapubkey->bitlen = modulusLengthInBytes * 8;
    

    Your setting of the dwMagic value seems to be incorrect, too.

    rsapubkey->magic = 0x31415352;
    

    0x31415352 is RSA_PUB_MAGIC, so you are calling yourself a public key. You want RSA_PRIV_MAGIC (and to use the constant).

    rsapubkey->magic = RSA_PRIV_MAGIC;
    

    Compare to http://source.dot.net/#System.Security.Cryptography.Csp/System/Security/Cryptography/CapiHelper.Shared.cs,b7bc764e6deb34f5, which is a working blob writer in C#.