Search code examples
c++keyprivatebcryptcrypt

BCrypt how to turn bytes to a Key Handle


I have an array of bytes that is supposed to be the Private Key that I use to decode a message encoded with a public key.

BCryptDecrypt uses a BCRYPT_KEY_HANDLE type as the key used to decrypt the message. How do I convert the private key to a key handle?


Solution

  • I had a similiar problem. You need the key (as byte stream), but you also need to know additional information (such as the used algorithm and so forth).

    If we assume that the key as been exported using BCryptExportKey (which exports the key AND a CNG specific header), then you could load the key like this:

    BCRYPT_KEY_HANDLE ImportKey(BYTE* pbKey, ULONG uLength)
    {
        if(uLength < sizeof(BCRYPT_KEY_BLOB))
            return nullptr;
    
        const wchar_t*  wszAlgorithm;
        bool bPublic;
        switch(reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic)
        {
        case BCRYPT_RSAPUBLIC_MAGIC:
        case BCRYPT_RSAPRIVATE_MAGIC:
        case BCRYPT_RSAFULLPRIVATE_MAGIC:
            if(uLength < sizeof(BCRYPT_RSAKEY_BLOB))
                return nullptr;
            wszAlgorithm = BCRYPT_RSA_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_RSAPUBLIC_MAGIC);
            break;
        case BCRYPT_ECDH_PUBLIC_P256_MAGIC:
        case BCRYPT_ECDH_PRIVATE_P256_MAGIC:
            wszAlgorithm = BCRYPT_ECDH_P256_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDH_PUBLIC_P256_MAGIC);
            break;
        case BCRYPT_ECDH_PUBLIC_P384_MAGIC:
        case BCRYPT_ECDH_PRIVATE_P384_MAGIC:
            wszAlgorithm = BCRYPT_ECDH_P384_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDH_PUBLIC_P384_MAGIC);
            break;
        case BCRYPT_ECDH_PUBLIC_P521_MAGIC:
        case BCRYPT_ECDH_PRIVATE_P521_MAGIC:
            wszAlgorithm = BCRYPT_ECDH_P521_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDH_PUBLIC_P521_MAGIC);
            break;
        case BCRYPT_ECDSA_PUBLIC_P256_MAGIC:
        case BCRYPT_ECDSA_PRIVATE_P256_MAGIC:
            wszAlgorithm = BCRYPT_ECDSA_P256_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDSA_PUBLIC_P256_MAGIC);
            break;
        case BCRYPT_ECDSA_PUBLIC_P384_MAGIC:
        case BCRYPT_ECDSA_PRIVATE_P384_MAGIC:
            wszAlgorithm = BCRYPT_ECDSA_P384_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDSA_PUBLIC_P384_MAGIC);
            break;
        case BCRYPT_ECDSA_PUBLIC_P521_MAGIC:
        case BCRYPT_ECDSA_PRIVATE_P521_MAGIC:
            wszAlgorithm = BCRYPT_ECDSA_P521_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_ECDSA_PUBLIC_P521_MAGIC);
            break;
        case BCRYPT_DH_PUBLIC_MAGIC:
        case BCRYPT_DH_PRIVATE_MAGIC:
            if(uLength < sizeof(BCRYPT_DH_KEY_BLOB))
                return nullptr;
            wszAlgorithm = BCRYPT_DH_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_DH_PUBLIC_MAGIC);
            break;
        case BCRYPT_DSA_PUBLIC_MAGIC:
        case BCRYPT_DSA_PRIVATE_MAGIC:
        case BCRYPT_DSA_PUBLIC_MAGIC_V2:
        case BCRYPT_DSA_PRIVATE_MAGIC_V2:
            if(uLength < sizeof(BCRYPT_DSA_KEY_BLOB))
                return nullptr;
            wszAlgorithm = BCRYPT_DSA_ALGORITHM;
            bPublic = (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_DSA_PUBLIC_MAGIC) || (reinterpret_cast<BCRYPT_KEY_BLOB*>(pbKey)->Magic == BCRYPT_DSA_PUBLIC_MAGIC_V2);
            break;
        default:
            return nullptr;
        }
    
        // Query provider (I recommend to use RAII here for the case of exceptions; I omit that in this example here)
        BCRYPT_ALG_HANDLE hAlgorithm;
        NTSTATUS ntResult = BCryptOpenAlgorithmProvider(&hAlgorithm, wszAlgorithm, MS_PRIMITIVE_PROVIDER, 0);
        if(!BCRYPT_SUCCESS(ntResult))
            return nullptr;
    
        // Import key
        BCRYPT_KEY_HANDLE hKey;
        ntResult = BCryptImportKeyPair(hAlgorithm, nullptr, bPublic ? BCRYPT_PUBLIC_KEY_BLOB : BCRYPT_PRIVATE_KEY_BLOB, &hKey, pbKey, uLength, 0);
        BCryptCloseAlgorithmProvider(hAlgorithm, 0);        // Close in each case
        if(!BCRYPT_SUCCESS(ntResult))
            return nullptr;
    
        return hKey;
    }
    

    As you can see, this example function expects that the byte stream has the CNG header. If your key byte stream has this header, then you load the key using this function. Otherwise you need to create such a header.