Windows XP 与 Vista/7 上的 MS Crypto API 行为

Posted

技术标签:

【中文标题】Windows XP 与 Vista/7 上的 MS Crypto API 行为【英文标题】:MS Crypto API behavior on Windows XP vs Vista/7 【发布时间】:2011-05-28 13:42:48 【问题描述】:

我正在尝试了解如何跨 XP、Vista 和 Windows 7 获取从 PEM 格式(示例包含在下面的代码中)导入的公钥。示例代码将在 XP 和 Windows Vista/7 上导入密钥,但方式不同。

在 Windows XP 上,字符串 "(Prototype)" 是加密提供程序名称中必需的,它允许对 CryptImportPublicKeyInfo 的调用通过。

在 Windows 7 上,"(Prototype)" 提供程序显然存在,但不支持对 CryptImportPublicKeyInfo 的调用,这很令人困惑。

这些操作系统之间的正确实现应该是什么样的?是否需要检测 XP 并使用 "(Prototype)" 请求名称,而对于其他操作系统则不需要它?是否有可能在某些 XP 系统上仍然会失败?

或者,有没有办法检测这种令人困惑的行为并选择支持必要调用的加密提供程序?

Windows 7 上的输出:

ANALYZING CRYPTOGRAPHIC SUPPORT FOR:
     "Microsoft Enhanced RSA and AES Cryptographic Provider"
     CryptAcquireContext success.
     CryptAcquireContext.1 success.
     CryptStringToBinary.2 success.
     CryptDecodeObjectEx success.
     CryptImportPublicKeyInfo success.
     SUCCESS.
ANALYZING CRYPTOGRAPHIC SUPPORT FOR:
     "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
     CryptAcquireContext success.
     CryptAcquireContext.1 success.
     CryptStringToBinary.2 success.
     CryptDecodeObjectEx success.
     CryptImportPublicKeyInfo FAILED****.

Windows XP 上的输出:

ANALYZING CRYPTOGRAPHIC SUPPORT FOR:
     "Microsoft Enhanced RSA and AES Cryptographic Provider"
     CryptAcquireContext success.
     CryptAcquireContext.1 success.
     CryptStringToBinary.2 success.
     CryptDecodeObjectEx success.
     CryptImportPublicKeyInfo FAILED****.
ANALYZING CRYPTOGRAPHIC SUPPORT FOR:
     "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
     CryptAcquireContext success.
     CryptAcquireContext.1 success.
     CryptStringToBinary.2 success.
     CryptDecodeObjectEx success.
     CryptImportPublicKeyInfo success.
     SUCCESS.

产生该输出的 C++ 源代码:(需要 crypt32.lib)

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <wincrypt.h>

bool windowsAcquireProviderContext(HCRYPTPROV *pHandleProv, LPCTSTR pProviderName);
bool analyzeCryptographicSupport(LPCTSTR pProviderName);

int _tmain(int argc, _TCHAR* argv[])

    analyzeCryptographicSupport(MS_ENH_RSA_AES_PROV);
    analyzeCryptographicSupport(L"Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)");
    return 0;


bool windowsAcquireProviderContext(HCRYPTPROV *pHandleProv, LPCTSTR pProviderName) 
    WCHAR *pContainerName = L"blah blah blah";
    if(!CryptAcquireContext(pHandleProv, pContainerName, pProviderName, PROV_RSA_AES, CRYPT_SILENT)) 
        if(GetLastError() == NTE_BAD_KEYSET) 
            if(CryptAcquireContext(pHandleProv, pContainerName, pProviderName, PROV_RSA_AES, CRYPT_NEWKEYSET|CRYPT_SILENT)) 
                return true;
            
         
    
    return true;


LPCWSTR pwszPemPublicKey = 
    L"-----BEGIN PUBLIC KEY-----\r\n"
    L"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GUVcbn92bahlwOskKi8XkG9q\r\n"
    L"Vq863+C4cOWC6HzJojc011pJFFIBu8/pG1EI8FZJdBmTrFaJTriYw1/SpbOH0QqE\r\n"
    L"eHanT8qWn+S5m9xgDJoWTBJKcnu3OHOvJJU3c8jOHQQnRWLfghJH4vnwStdiwUUY\r\n"
    L"SMWpwuHObsNelGBgEQIDAQAB\r\n"
    L"-----END PUBLIC KEY-----\r\n";
int pemPublicKeySize = wcslen(pwszPemPublicKey);

bool analyzeCryptographicSupport(LPCTSTR pProviderName) 

    printf("ANALYZING CRYPTOGRAPHIC SUPPORT FOR:\r\n");
    wprintf(L"\t \"%s\"\r\n", pProviderName);

    HCRYPTPROV hProv;
    if(!windowsAcquireProviderContext(&hProv, pProviderName)) 
        wprintf(L"\t CryptAcquireContext FAILED.\r\n");
        return false;
    
    wprintf(L"\t CryptAcquireContext success.\r\n");

    DWORD blobSize;

    if(!CryptStringToBinary(pwszPemPublicKey, pemPublicKeySize, CRYPT_STRING_BASE64_ANY,  NULL, &blobSize, NULL, NULL)) 
        CryptReleaseContext(hProv, 0);
        wprintf(L"\t CryptStringToBinary.1 FAILED****.\r\n");
        return false;
    
    wprintf(L"\t CryptAcquireContext.1 success.\r\n");

    BYTE *pBlob = (BYTE *)malloc(blobSize);

    if(!CryptStringToBinary(pwszPemPublicKey, pemPublicKeySize, CRYPT_STRING_BASE64_ANY,  pBlob, &blobSize, NULL, NULL)) 
        free(pBlob);
        CryptReleaseContext(hProv, 0);
        wprintf(L"\t CryptStringToBinary.2 FAILED****.\r\n");
        return false;
    
    wprintf(L"\t CryptStringToBinary.2 success.\r\n");

    CERT_PUBLIC_KEY_INFO *publicKeyInfo;
    DWORD publicKeyInfoLen;
    HCRYPTKEY hPublicKey;

    if(!CryptDecodeObjectEx(X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pBlob, blobSize, CRYPT_DECODE_ALLOC_FLAG, NULL, &publicKeyInfo, &publicKeyInfoLen)) 
        free(pBlob);
        CryptReleaseContext(hProv, 0);
        wprintf(L"\t CryptDecodeObjectEx FAILED****.\r\n");
        return false;
    
    wprintf(L"\t CryptDecodeObjectEx success.\r\n");

    if(!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, publicKeyInfo, &hPublicKey)) 
        LocalFree(publicKeyInfo);
        free(pBlob);
        CryptReleaseContext(hProv, 0);
        wprintf(L"\t CryptImportPublicKeyInfo FAILED****.\r\n");
        return false;
    
    wprintf(L"\t CryptImportPublicKeyInfo success.\r\n");

    CryptDestroyKey(hPublicKey);
    LocalFree(publicKeyInfo);
    free(pBlob);
    CryptReleaseContext(hProv, 0);

    wprintf(L"\t SUCCESS.\r\n");
    return true;

【问题讨论】:

【参考方案1】:

您描述的问题的原因很简单:Microsoft 将 AES Cryptographic Provider 重命名为

"Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" 在 Windows XP 中为 "Microsoft Enhanced RSA and AES Cryptographic Provider" 在以后的操作系统版本中。

WinCrypt.h 中定义了相应的常量MS_ENH_RSA_AES_PROVMS_ENH_RSA_AES_PROV_XP,您可以使用它们。

如果您不想测试操作系统的版本,您可以使用CryptAcquireContext 和NULL 作为pszProvider(并继续使用PROV_RSA_AES 作为dwProvType)。在您的代码中,您可以包含analyzeCryptographicSupport(NULL);

您还可以检查注册表项的“名称”值

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider Types\Type 024

查看默认PROV_RSA_AES 提供程序的名称。

【讨论】:

看起来我的案例的简短答案是检测 XP 并使用适当的提供程序字符串...不幸的是,但不是什么大不了的事。 @g01d,效果很好,但更“正确”的方法不是检测操作系统版本,而是检测注册表中的加密名称并从那里处理。分头,我知道…… @Prof.Falken:当然可以检测到操作系统。我写道,可以交替使用pszProvider=NULLdwProvType=PROV_RSA_AES。我在回答中尝试解释,问题的真正根源是使用MS_ENH_RSA_AES_PROV 常量,它只是注册表中的字符串。我想微软只是在 Windows XP 中犯了一个错误,在注册表中使用了带有不需要的“(原型)”后缀的错误文本。因此,通过引入MS_ENH_RSA_AES_PROV_XP 来“修复”错误。如果人们了解问题的原因,则可以选择最佳解决方案。【参考方案2】:

我想我记得在某处读到微软在名称上搞砸了,它要求“(原型)”出现在 XP 上,而在 Vista 及更高版本上不存在。我认为您必须在运行时检测平台并使用适当的字符串。

【讨论】:

以上是关于Windows XP 与 Vista/7 上的 MS Crypto API 行为的主要内容,如果未能解决你的问题,请参考以下文章

我如何在 Windows(xp、vista、7)欢迎屏幕或锁定屏幕(如 VNC 或 Dame Ware)中进行交互

如何在 Windows 中从我的 VS2008/C++ 应用程序中执行一个程序来替换调用者并在 xp/vista/7 上运行?

需要 dpiAware 可执行文件才能在 Windows XP/2003 中工作

Windows Vista/7 上的 SDL_Mixer MIDI 音量问题

通过Windows Vista / 7上的Inno Setup将文件安装到原始用户的My Docs文件夹

Windows Vista/7:如何对输出音频混合进行采样?