Crypt32 - RSA Keys

Source

Generating RSA keypair

C
BOOL GenerateRSAKeyPairCryptoAPI(BYTE **publicKey, DWORD *publicKeyLength, BYTE **privateKey, DWORD *privateKeyLength) {
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hKey = 0;
    BOOL result = FALSE;

    // Acquire cryptographic context
    CryptAcquireContextA(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);

    // Generate RSA key pair (4096-bit)
    CryptGenKey(hProv, AT_KEYEXCHANGE, (4096 << 16) | CRYPT_EXPORTABLE, &hKey);

    // Export public key blob
    CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, NULL, publicKeyLength);

    *publicKey = (BYTE*)malloc(*publicKeyLength);
    CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, *publicKey, publicKeyLength);

    // Export private key blob
    CryptExportKey(hKey, 0, PRIVATEKEYBLOB, 0, NULL, privateKeyLength);

    *privateKey = (BYTE*)malloc(*privateKeyLength);
    CryptExportKey(hKey, 0, PRIVATEKEYBLOB, 0, *privateKey, privateKeyLength);

    result = TRUE;

    cleanup:
    if (hKey) CryptDestroyKey(hKey);
    if (hProv) CryptReleaseContext(hProv, 0);
    return result;
}

Bonus, converting to the x509 format

C
BYTE* ConvertPublicKeyBlobToX509(BYTE *keyBlob, DWORD blobLen, DWORD *x509Length) {
    CERT_PUBLIC_KEY_INFO keyInfo = {0};
    BYTE *x509Data = NULL;

    // Parse the Windows PUBLICKEYBLOB structure
    PUBLICKEYSTRUC *pubKeyStruc = (PUBLICKEYSTRUC*)keyBlob;
    RSAPUBKEY *rsaPubKey = (RSAPUBKEY*)(keyBlob + sizeof(PUBLICKEYSTRUC));


    // Set up algorithm identifier for RSA encryption
    keyInfo.Algorithm.pszObjId = "1.2.840.113549.1.1.1"; // rsaEncryption OID
    keyInfo.Algorithm.Parameters.cbData = 2;

    // ASN.1 NULL parameters (0x05 0x00)
    static BYTE nullParams[] = {0x05, 0x00};
    keyInfo.Algorithm.Parameters.pbData = nullParams;

    // The public key data - we need to convert Windows format to ASN.1
    DWORD keySize = rsaPubKey->bitlen / 8;
    BYTE *modulus = keyBlob + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY);

    // Windows stores RSA modulus in little-endian, need to reverse to big-endian
    BYTE *reversedModulus = (BYTE*)malloc(keySize);
    for (DWORD i = 0; i < keySize; i++) {
        reversedModulus[i] = modulus[keySize - 1 - i];
    }

    // Convert exponent to big-endian bytes
    DWORD exponent = rsaPubKey->pubexp;
    BYTE expBytes[4];
    expBytes[0] = (exponent >> 24) & 0xFF;
    expBytes[1] = (exponent >> 16) & 0xFF;
    expBytes[2] = (exponent >> 8) & 0xFF;
    expBytes[3] = exponent & 0xFF;

    // Find actual exponent length (remove leading zeros)
    int expLen = 4;
    while (expLen > 1 && expBytes[4 - expLen] == 0) {
        expLen--;
    }

    // Build ASN.1 RSAPublicKey structure manually
    // SEQUENCE { modulus INTEGER, exponent INTEGER }
    BYTE rsaPublicKey[4096];
    BYTE *p = rsaPublicKey;

    *p++ = 0x30; // SEQUENCE tag

    // Calculate content length first
    DWORD modIntegerLen = keySize + (reversedModulus[0] & 0x80 ? 1 : 0) + 3; // tag + len + padding + data
    DWORD expIntegerLen = expLen + (expBytes[4-expLen] & 0x80 ? 1 : 0) + 3; // tag + len + padding + data
    DWORD contentLen = modIntegerLen + expIntegerLen;

    if (contentLen > 127) {
        *p++ = 0x82; // long form, 2 bytes
        *p++ = (contentLen >> 8) & 0xFF;
        *p++ = contentLen & 0xFF;
    } else {
        *p++ = (BYTE)contentLen;
    }

    // Modulus INTEGER
    *p++ = 0x02; // INTEGER tag
    DWORD modLen = keySize + (reversedModulus[0] & 0x80 ? 1 : 0);
    if (modLen > 127) {
        *p++ = 0x82;
        *p++ = (modLen >> 8) & 0xFF;
        *p++ = modLen & 0xFF;
    } else {
        *p++ = (BYTE)modLen;
    }
    if (reversedModulus[0] & 0x80) {
        *p++ = 0x00; // padding byte
    }
    memcpy(p, reversedModulus, keySize);
    p += keySize;

    // Exponent INTEGER
    *p++ = 0x02; // INTEGER tag
    DWORD actualExpLen = expLen + (expBytes[4-expLen] & 0x80 ? 1 : 0);
    *p++ = (BYTE)actualExpLen;
    if (expBytes[4-expLen] & 0x80) {
        *p++ = 0x00; // padding byte
    }
    memcpy(p, &expBytes[4-expLen], expLen);
    p += expLen;

    DWORD rsaPublicKeyLen = p - rsaPublicKey;

    // Now use the manually built RSA public key
    keyInfo.PublicKey.cbData = rsaPublicKeyLen;
    keyInfo.PublicKey.pbData = rsaPublicKey;
    keyInfo.PublicKey.cUnusedBits = 0;

    CryptEncodeObject(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, &keyInfo, NULL, x509Length);

    x509Data = (BYTE*)malloc(*x509Length);
    CryptEncodeObject(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, &keyInfo, x509Data, x509Length);

    free(reversedModulus);
    return x509Data;
}