将PEM编码的X.509证书加载到Windows CryptoAPI

15
我需要将一个PEM编码的X.509证书加载到Windows Crypto API上下文中,用于C++。它们是那些具有-----BEGIN RSA XXX KEY----------END RSA XXX KEY-----的证书。我找到了Python和.NET的示例,但它们使用与纯Windows Crypto API不相关的特定函数。
我了解到一旦获得HCRYPTKEY就可以进行加密/解密。但是,我不知道如何导入.PEM文件中的Base64 blob并获得可以使用的HCRYPTKEY
我有一种奇怪的感觉,似乎不仅仅是调用CryptDecodeObject()
能指点我吗?我已经试错式地编程两天了,却没有任何进展。
3个回答

22

KJKHyperion在他的答案中提到:

我发现了一组导入PEM格式RSA公钥的"神奇"调用序列。如下所示:

  1. 使用CryptStringToBinary将密钥解码为二进制数据块;在dwFlags参数中传递CRYPT_STRING_BASE64HEADER
  2. 使用CryptDecodeObjectEx将二进制密钥块解码为CERT_PUBLIC_KEY_INFO对象;在dwCertEncodingType参数中传递X509_ASN_ENCODING,lpszStructType参数中传递X509_PUBLIC_KEY_INFO
  3. 使用CryptDecodeObjectEx将CERT_PUBLIC_KEY_INFO对象中的PublicKey块解码为RSA密钥块;在dwCertEncodingType参数中传递X509_ASN_ENCODING,lpszStructType参数中传递RSA_CSP_PUBLICKEYBLOB
  4. 使用CryptImportKey导入RSA密钥块

这个步骤确实有助于我理解正在进行的操作,但它并不能直接用于我的情况。第二次调用CryptDecodeObjectEx时出现了错误:"ASN.1 bad tag value met"。在多次尝试理解Microsoft文档后,我终于意识到第一次解码的输出不能再次被ASN解码,并且它实际上已经准备好进行导入。有了这个理解,我在以下链接中找到了答案:

http://www.ms-news.net/f2748/problem-importing-public-key-4052577.html

下面是我的程序,它将从.pem文件中导入公钥到CryptApi上下文:

int main()
{
    char           pemPubKey[2048];
    int            readLen;
    char           derPubKey[2048];
    size_t         derPubKeyLen = 2048;
    CERT_PUBLIC_KEY_INFO *publicKeyInfo;
    int            publicKeyInfoLen;
    HANDLE         hFile;
    HCRYPTPROV     hProv = 0;
    HCRYPTKEY      hKey = 0;

    /*
     * Read the public key cert from the file
     */
    hFile = CreateFileA( "c:\\pub.pem", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    if ( hFile == INVALID_HANDLE_VALUE )
    {
        fprintf( stderr, "Failed to open file. error: %d\n", GetLastError() );
    }

    if ( !ReadFile( hFile, pemPubKey, 2048, &readLen, NULL ) )
    {
        fprintf( stderr, "Failed to read file. error: %d\n", GetLastError() );
    }

    /*
     * Convert from PEM format to DER format - removes header and footer and decodes from base64
     */
    if ( !CryptStringToBinaryA( pemPubKey, 0, CRYPT_STRING_BASE64HEADER, derPubKey, &derPubKeyLen, NULL, NULL ) )
    {
        fprintf( stderr, "CryptStringToBinary failed. Err: %d\n", GetLastError() );
    }

    /*
     * Decode from DER format to CERT_PUBLIC_KEY_INFO
     */
    if ( !CryptDecodeObjectEx( X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, derPubKey, derPubKeyLen, 
                               CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo, &publicKeyInfoLen ) )
    {
        fprintf( stderr, "CryptDecodeObjectEx 1 failed. Err: %p\n", GetLastError() );
        return -1;
    }

    /*
     * Acquire context 
     */
    if( !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) )
    {
        {
            printf( "CryptAcquireContext failed - err=0x%x.\n", GetLastError() );
            return -1;
        }
    }

    /*
     * Import the public key using the context
     */
    if ( !CryptImportPublicKeyInfo( hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey ) )
    {
        fprintf( stderr, "CryptImportPublicKeyInfo failed. error: %d\n", GetLastError() );
        return -1;
    }
    LocalFree( publicKeyInfo );

    /*
     * Now use hKey to encrypt whatever you need.
     */

    return 0;
}

你能提供有效的公钥吗?你的程序对我来说还是无法工作..但我的密钥确实是有效的。 - socketpair
1
根据实现情况,@socketpair 数字签名在验证之前可能需要完全反转字节。 - moala

12

我发现了将PEM格式的RSA公钥导入的“神奇”调用序列。请看下面:

  1. 使用CryptStringToBinary将密钥解码为二进制块; 在dwFlags中传递CRYPT_STRING_BASE64HEADER
  2. 使用CryptDecodeObjectEx将二进制密钥块解码为CERT_PUBLIC_KEY_INFO; 在dwCertEncodingType中传递X509_ASN_ENCODING,在lpszStructType中传递X509_PUBLIC_KEY_INFO
  3. 从CERT_PUBLIC_KEY_INFO中的PublicKey blob中使用CryptDecodeObjectEx将其解码为RSA key blob; 在dwCertEncodingType中传递X509_ASN_ENCODING,在lpszStructType中传递RSA_CSP_PUBLICKEYBLOB
  4. 使用CryptImportKey导入RSA密钥blob

2

我目前也面临同样的困境。虽然我还没有编写出解决方案,但据我所知,您需要剥离----- BEGIN等----- END等标签并解码Base64。

这将使您得到一个DER编码字符串,您需要解析它以获取模数和公共指数。从那些数据中,您可以填充PUBLICKEYSTRUC和RSAPUBKEY结构体。祝你好运 ;-)


请查看使用X509_ASN_ENCODING和RSA_CSP_PUBLICKEYBLOB选项的CryptDecodeObjectEx。似乎已正确解码并填充结构,但您可能仍需要交换某些部分的字节顺序。 - jarmond

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接