71

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

1 回答 1

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 对用户不是很友好。但是,您在资源有限的情况下做得很好。您要添加的是加密枚举提供程序,以帮助缩小密钥范围。确保您拥有 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通过 using绕过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当有多个连接时,肯定会显示给新用户,尤其是那些具有高延迟的连接。超过 300ms 只会导致 aNTE_BAD_KEYSET_PARAM或类似的调用,因为超时甚至没有收到适当的错误。(传输问题,有人和我在一起吗?)

除非您担心多个 DLL(由于NTE_PROVIDER_DLL_FAIL错误不支持),否则获取 crypt 服务客户端的基本设置如下(直接从 Microsoft 的示例中复制):

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

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

名叫 John Howard 的人编写了一个不错的 Hyper-V 远程管理配置实用程序 (HVRemote),它是这里讨论的技术的大型汇编。除了使用基本的密码和密钥对之外,它们还可以用于允许ANONYMOUS LOGON远程DCOM访问(cscript hvremote.wsf具体来说是 )。您可以在他的博客上看到他最新的 crypts 中的许多功能和技术(您必须缩小查询范围):

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

如果您在基础知识方面需要更多帮助,请发表评论或请求私人聊天。

结论

尽管一旦您了解了用于散列的基本服务器端方法以及客户端如何获取“crypts”,这将非常简单,但您会质疑为什么您甚至在传输过程中尝试加密。然而,如果没有加密客户端,加密肯定是传输已经散列的内容的唯一安全方式。

尽管您可能会争辩说数据包可以被解密并从盐中散列,但请考虑必须以重新散列客户端所需的正确时间和顺序来处理和存储传入传出的数据。

于 2014-10-27T23:23:40.770 回答