Microsoft Crypto API禁用使用RSAES-OAEP密钥传输algorithm
我正在使用CryptEncryptMessage
来生成一个PKCS#7
封装的消息。 我使用szOID_NIST_AES256_CBC
作为encryptionalgorithm。
生成的消息似乎是有效的,但密钥传输algorithm的RSAES-OAEP
在野外的支持有限(Thunderbird,OpenSSL SMIME模块之间的许多人不支持它)。
我想让CAPI恢复到旧的RSAencryption
encryption密钥传输。
有没有办法做到这一点,我可以恢复到低级消息传递函数,如果有一种方法,而不是使用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
来绕过CryptEncryptMessage
, CryptReleaseContext
直接释放由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。