Microsoft Crypto API禁用使用RSAES-OAEP密钥传输algorithm

我正在使用CryptEncryptMessage来生成一个PKCS#7封装的消息。 我使用szOID_NIST_AES256_CBC作为encryptionalgorithm。

生成的消息似乎是有效的,但密钥传输algorithm的RSAES-OAEP在野外的支持有限(Thunderbird,OpenSSL SMIME模块之间的许多人不支持它)。

我想让CAPI恢复到旧的RSAencryptionencryption密钥传输。

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

码:

 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); 

密钥传输algorithm处理起来有点棘手,可能无法达到目的(我看到你注意到,你希望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); 

很基本,不是吗? 尽pipe高效,但并没有真正解决问题。 如果你看看这个:

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

你会看到它是预定义的,但只用于retval的定义。 但是,我可以肯定地认为这是一个微型优化,如果我们要重写代码,那么这个优化并不是很有用。 不过,我已经概述了在不重新编码的情况下集成这个基本步骤(所以你可以继续使用相同的参数):

第2步 – 编辑参数

正如@owlstead在他的评论中提到的,Crypto API不是非常用户友好的。 然而,你用有限的资源做了很棒的工作。 你想添加的是一个encryption枚举提供商来帮助缩小密钥。 确保您有Microsoft Base Cryptographic Provider版本1.0或Microsoft Enhanced Cryptographic Provider版本1.0以有效地使用这些。 否则,你需要添加如下function:

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

这主要用于防止NTE_BAD_FLAGS错误,尽pipe从技术上讲,您可以通过更低级的声明来避免这种错误。 如果你想的话,你也可以创build一个全新的散列(尽pipe只有在上述实现不能缩放到时间/速度的必要因素时才需要)。

 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"); } 

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

顺便说一句,你第一次运行它,你将不得不创build一个新的集合,因为没有默认值。 下面是一个很好的单行程序,可以popup一个if

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

请记住,同步服务器资源的效率不如我在第一步中所build议的那样重新工作。 这是我将在下面解释:

第3步 – 重新编码和重新启动

作为一名程序员,重新编码似乎是浪费时间,但从长远来看,它肯定会帮助你。 请记住,在编码/同步时,您仍然必须在自定义参数中编码; 我不会像婴儿一样亲手喂你所有的代码。 向您展示基本的轮廓应该足够了。

我肯定假设你正在尝试处理特定CSP中当前用户的密钥容器; 否则,我真的看不到这个用法。 如果不是的话,你可以做一些基本的编辑来适应你的需求。

请记住,我们将通过使用CryptReleaseContext来绕过CryptEncryptMessageCryptReleaseContext直接释放由CryptAcquireContext函数获取的句柄。 微软在CAC上的标准如下:

 BOOL WINAPI CryptAcquireContext( _Out_ HCRYPTPROV *phProv, _In_ LPCTSTR pszContainer, _In_ LPCTSTR pszProvider, _In_ DWORD dwProvType, _In_ DWORD dwFlags ); 

请注意,如果您使用的是用户界面,微软会斥责你:

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

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

除非你担心多个DLL(由于NTE_PROVIDER_DLL_FAIL错误,这不支持这个),所以设置为获取crypt服务的基本设置如下(直接从微软的例子中复制):

 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); } 

然而,看起来很简单,你肯定不希望把这个问题传递给密钥交换algorithm(或者其他任何你需要处理的)。 除非您使用对称会话密钥(Diffie-Hellman / KEA),否则交换密钥对可用于encryption会话密钥,以便可以安全地存储和交换其他用户。

有一个名叫John Howard的人写了一个很好的Hyper-V远程pipe理configuration实用程序(HVRemote),这是这里讨论的大量技术汇编。 除了使用基本的密码和密钥对,它们还可以用来允许ANONYMOUS LOGON远程DCOM访问( cscript hvremote.wsf ,具体)。 你可以在他的博客中看到他最新的地下室中的许多function和技巧(你将不得不缩小查询范围):

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

如果您需要更多帮助,请留下评论或要求私下聊天。

结论

尽pipe一旦您意识到散列的基本服务器端方法以及客户端如何获取“隐藏”,但这很简单,您会质疑为什么您甚至在传输过程中尝试encryption。 但是,如果没有客户端的encryption,encryption一定是传输已经散列的唯一安全的方式。

虽然你可能会争辩说,数据包可以被解密和散列盐,考虑到,这两个内部将不得不被处理和存储在正确的时间顺序必要重新哈希clientside。