Microsoft Crypto API禁用RSAES-OAEP密钥传输算法

时间:2012-10-26 15:48:18

标签: c windows encryption cryptography mscapi

我正在使用CryptEncryptMessage生成PKCS#7封套邮件。我使用szOID_NIST_AES256_CBC作为加密算法。

生成的消息似乎是有效的,但是密钥传输算法的RSAES-OAEP在野外具有有限的支持(Thunderbird,OpenSSL SMIME模块以及许多其他人不支持它)。

我希望CAPI恢复到较旧的RSAencryption进行密钥传输。

有没有办法做到这一点,如果有办法而不是使用CryptEncryptMessage,我可以恢复到低级别的消息传递功能但是我找不到办法做到这一点,即使使用低等级功能。

代码:

CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);

EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;

EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;

EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;

BOOL retval =  CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);

1 个答案:

答案 0 :(得分:2)

密钥传输算法有点难以处理,它可能无法实现其目的(我看到你注意到你希望CAPI支持RSAencryption;相信我,我也会这样做)。看起来您已经检测到了大部分问题 - 生成的消息显示有效,但您的方法需要使用CryptEncryptMessage,这样做不会很好/在从长远来看。

第1步 - 检查代码

CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);

EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;

EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;

EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;

BOOL retval =  CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);

非常基本,不是吗?虽然效率很高,但并没有真正解决问题。如果你看一下:

EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

您会看到它是预先定义的,但仅用于retval的定义。但是,我绝对可以将其视为微优化,如果我们要重新编写代码,那么这并不是很有用。但是,我已经概述了在没有完全重新编写代码的情况下集成它的基本步骤(因此您可以继续使用相同的参数):

第2步 - 编辑参数

正如@owlstead在评论中提到的,Crypto API不是非常用户友好。但是,你在资源有限的情况下做得很好。您要添加的内容是Cryptographic Enumeration Provider,以帮助缩小密钥范围。确保您具有Microsoft Base Cryptographic Provider 1.0版或Microsoft Enhanced Cryptographic Provider 1.0版以有效地使用它们。否则,您需要像这样添加函数:

DWORD cbName;
DWORD dwType;
DWORD dwIndex;
CHAR *pszName = NULL;
(regular crypt calls here)

这主要用于防止NTE_BAD_FLAGS错误,但从技术上讲,您可以使用更低级别的声明来避免此错误。如果您愿意,您还可以创建一个全新的哈希值(尽管只有在上述实现不能缩放到必要的时间/速度因子时才需要这样做):

DWORD dwBufferLen = strlen((char *)pbBuffer)+1*(0+5);
HCRYPTHASH hHash;
HCRYPTKEY hKey;
HCRYPTKEY hPubKey;
BYTE *pbKeyBlob;
BYTE *pbSignature;
DWORD dwSigLen;
DWORD dwBlobLen;
(use hash as normal w/ crypt calls and the pbKeyBlobs/Signatures)

请确保在继续之前对此代码段进行虚拟化处理。您可以这样轻松地完成这样做:

if(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
     printf("CSP context acquired.\n");
}

如果您正在记录或发布,可能需要添加void MyHandleError(char *s)来捕获错误,以便编辑但失败的人可以快速捕获它。

顺便说一句,第一次运行它时,你必须创建一个新的集合,因为它没有默认值。可以弹出if的漂亮单行如下:

CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)

请记住,同步服务器资源与我在第一步中建议的重新工作一样高效。这就是我将在下面解释的内容:

第3步 - 重新编码并重新启动

作为程序员,重新编码似乎是浪费时间,但从长远来看,它绝对可以帮助你。请记住,在编码/同步时,您仍然需要在自定义参数中进行编码;我不会像宝宝一样亲自给你喂食所有的代码。它应该足以向您展示基本的轮廓。

我绝对假设您正在尝试处理当前用户在特定CSP中的密钥容器;否则,我真的没有看到这个用途。如果没有,您可以进行一些基本编辑以满足您的需求。

请注意,我们将使用CryptEncryptMessage绕过CryptReleaseContextCryptAcquireContext直接释放BOOL WINAPI CryptAcquireContext( _Out_ HCRYPTPROV *phProv, _In_ LPCTSTR pszContainer, _In_ LPCTSTR pszProvider, _In_ DWORD dwProvType, _In_ DWORD dwFlags ); 函数获取的句柄。微软在CAC上的标准如下:

ERROR_BUSY

请注意,如果您使用的是用户界面,Microsoft会责骂您:

  

如果CSP必须显示要操作的UI,则调用失败并将NTE_SILENT_CONTEXT错误代码设置为最后一个错误。此外,如果使用带有CRYPT_SILENT标志获取的上下文的CRYPT_USER_PROTECTED标志对CryptGenKey进行调用,则调用将失败,CSP将设置NTE_SILENT_CONTEXT。

这主要是服务器代码,当有多个连接时,NTE_BAD_KEYSET_PARAM肯定会显示给新用户,特别是那些具有高延迟的连接。超过300毫秒将导致NTE_PROVIDER_DLL_FAIL或类似的被调用,因为超时甚至没有收到正确的错误。 (传输问题,有人陪我吗?)

除非您担心多个DLL(由于if (GetLastError() == NTE_BAD_KEYSET) { if(CryptAcquireContext( &hCryptProv, UserName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) { printf("A new key container has been created.\n"); } else { printf("Could not create a new key container.\n"); exit(1); } } else { printf("A cryptographic service handle could not be " "acquired.\n"); exit(1); } 错误而无法支持),否则抓取加密服务客户端的基本设置如下(直接从Microsoft的示例中复制:

ANONYMOUS LOGON

无论这看起来多么简单,你肯定不想把它传递给密钥交换算法(或者你处理的其他任何事情)。除非您使用对称会话密钥(Diffie-Hellman / KEA),否则交换密钥对可用于加密会话密钥,以便可以安全地存储和与其他用户交换。

名为John Howard的人编写了一个不错的Hyper-V远程管理配置实用程序(HVRemote),它是对此处讨论的技术的大量汇编。除了使用基本隐窝和密钥对之外,它们还可用于允许DCOM远程cscript hvremote.wsf访问({{1}},具体而言)。你可以在他的博客上看到他最新的密码中的许多功能和技巧(你必须缩小查询范围):

http://blogs.technet.com/b/jhoward/

如果您需要更多基本帮助,只需发表评论或请求私聊。

结论

虽然一旦你意识到用于哈希的基本服务器端方法以及客户端如何抓取“密码”这一点非常简单,你就会质疑为什么你甚至尝试加密在传输过程中但是,如果没有加密客户端,加密肯定是传输已经散列的唯一安全方式。

虽然您可能会认为数据包可以被解密并从盐中删除,但请考虑必须处理输入内容并将其存储在重新哈希所需的正确时间顺序中客户机侧。