如何在 windows 上的 .net 框架中使用来自 ECC X509 证书的 public 密钥加密数据?

How can I encrypt data using a public key from ECC X509 certificate in .net framework on windows?

我正在使用:

这是我所做的:

  1. 使用 OpenSSL 和 https://gist.github.com/sidshetye/4759690 处的脚本中概述的步骤获得了自签名 ECC 证书并进行了修改:

    a) 在 256 位素数域上使用 NIST/P-256 曲线

    b) 使用 SHA-256

  2. 从文件(在上一步中生成)将证书加载到 X509Certificate2 对象中

  3. 将 PFX 文件导入 windows 信任库(用于测试)。这样就成功了。

  4. 检查导入的证书显示 Public 关键字段为 'ECC (256 Bits)' 和 Public 关键参数为 'ECDSA_P256'.
  5. 接下来想弄清楚如何用这个证书加密。

我卡在了最后一步,因为所有使用 X509Certificate2 对象的示例主要只使用 RSA,而我使用的是 ECC 证书。对于 RSA 证书,在 X509Certificate2 上有一个 GetRSAPublicKey 扩展方法,RSA class 有 Encrypt 方法。但是ECC证书没有这样的方法。

接下来,我偶然发现了这个 post (Load a Certificate Using X509Certificate2 with ECC Public Key) 并尝试了以下操作(尽管它看起来很奇怪为什么 ECC 证书 public 密钥被强制转换为 RSA 类型) :

RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key

我遇到以下异常:不支持证书密钥算法。

接下来我偶然发现了这个 post (Importing ECC-based certificate from the Windows Certificate Store into CngKey),它基本上试图创建 CNGKey 类型并用它实例化 ECDsaCng。然而,即使我可以用 ECDiffieHellmanCng 做到这一点,也没有加密方法。

所以我不太确定如何才能进一步使用 ECC X509 证书的 public 密钥来加密数据。

###背景

非对称算法具有三种不同的目的(据我所知)

  1. 加密
  • RSA 是唯一可以直接执行此操作的“标准”算法。
  1. 签名
  • RSA
  • 每日动态广告
  • ECDSA
  • ElGamal 签名
  1. 密钥约定
  • 迪菲-赫尔曼 (DH)
  • ECDH
  • ElGamal 加密(非对称启动阶段)
  • MQV
  • ECMQV

由于 RSA 加密是 space 有限的,并且对于 90 年代的计算机来说很难,RSA 加密的主要用途是在“密钥传输”中,也就是说“加密消息”只是DES/3DES 的对称加密密钥(AES 尚未发明)- https://www.rfc-editor.org/rfc/rfc2313#section-8.

密钥协议(或传输)方案始终必须与 protocol/scheme 结合才能进行加密操作。此类方案包括

  • TLS(也就是 SSL)
  • CMS 或 S/MIME 加密数据
  • IES(综合加密方案)
  • ECIES(椭圆曲线综合加密方案)
  • ElGamal 加密(整体)
  • PGP 加密

所以您可能想要的是 ECIES。

ECIES.Net

目前(.NET Framework 4.7.1、.NET Core 2.0)不支持从 .NET 中的证书获取 ECDiffieHellman 对象。

游戏结束了吧?好吧,可能不是。除非带有 ECDH 密钥的证书明确使用 id-ecDH 算法标识符(相对于更标准的 id-ecc 标识符),否则它可以作为 ECDSA 打开。然后,您可以将该对象强制转换为 ECDH:

using (ECDsa ecdsa = cert.GetECDsaPublicKey())
{
    return ECDiffieHellman.Create(ecdsa.ExportParameters(false));
}

(私钥可以做类似的事情,如果密钥是可导出的,否则需要复杂的东西,但你不应该需要它)

让我们继续分割接收者public对象:

ECDiffieHellmanPublicKey recipientPublic = GetECDHFromCertificate(cert).PublicKey;
ECCurve curve = recipientPublic.ExportParameters().Curve;

所以现在我们转到http://www.secg.org/sec1-v2.pdf第5.1节(椭圆曲线综合加密方案)

###设置

  1. 选择 ANSI-X9.63-KDF 和 SHA-2-256 作为哈希函数。
  2. 选择HMAC–SHA-256–256.
  3. 在CBC模式下选择AES–256。
  4. 选择椭圆曲线 Diffie-Hellman 原语。
  5. 您已经选择了secp256r1。
  6. 硬编码。完成。
  7. 点压缩很烦人,选择不用。
  8. 我省略了 SharedInfo。这可能使我成为一个坏人。
  9. 不使用 XOR,N/A。

###加密

  1. 在右曲线上创建一个临时密钥。

     ECDiffieHellman ephem = ECDiffieHellman.Create(curve);
    
  2. 我们决定不。

     ECParameters ephemPublicParams = ephem.ExportParameters(false);
     int pointLen = ephemPublicParams.Q.X.Length;
     byte[] rBar = new byte[pointLen * 2 + 1];
     rBar[0] = 0x04;
     Buffer.BlockCopy(ephemPublicParams.Q.X, 0, rBar, 1, pointLen);
     Buffer.BlockCopy(ephemPublicParams.Q.Y, 0, rBar, 1 + pointLen, pointLen);
    
  3. 无法直接执行此操作,继续。

  4. 无法直接执行此操作,继续。

  5. 既然我们控制了这里,我们就把 3、4、5 和 6 作为一件事来做。

  6. KDF时间

     // This is why we picked AES 256, HMAC-SHA-2-256(-256) and SHA-2-256,
     // the KDF is dead simple.
     byte[] ek = ephem.DeriveKeyFromHash(
         recipientPublic,
         HashAlgorithmName.SHA256,
         null,
         new byte[] { 0, 0, 0, 1 });
    
     byte[] mk = ephem.DeriveKeyFromHash(
         recipientPublic,
         HashAlgorithmName.SHA256,
         null,
         new byte[] { 0, 0, 0, 2 });
    
  7. 加密内容。

     byte[] em;
    
     // ECIES uses AES with the all zero IV. Since the key is never reused,
     // there's not risk in that.
     using (Aes aes = Aes.Create())
     using (ICryptoTransform encryptor = aes.CreateEncryptor(ek, new byte[16]))
     {
         if (!encryptor.CanTransformMultipleBlocks)
         {
             throw new InvalidOperationException();
         }
    
         em = encryptor.TransformFinalBlock(message, 0, message.Length);
     }
    
  8. MAC这

     byte[] d;
    
     using (HMAC hmac = new HMACSHA256(mk))
     {
         d = hmac.ComputeHash(em);
     }
    
  9. 完成

     // Either
     return Tuple.Create(rBar, em, d);
     // Or
     return rBar.Concat(em).Concat(d).ToArray();
    

###解密 作为练习留给 reader.

要从证书中获取 ECDiffieHellman 私钥,请使用以下方法:

  • 安装 NuGet 包 Security.Cryptography (CLR Security). (The package is under MIT license。)
  • 使用以下扩展方法获取 CngKey 实例: CngKey cngKey = certificate.GetCngPrivateKey(); (注意:扩展方法 certificate.GetECDsaPrivateKey(),在 .NET 中原生支持,return 是一个 ECDsaCng 实例;return ECDiffieHellmanCng 没有扩展方法。)
  • cngKey 实例可用于创建 ECDsaCngECDiffieHellmanCng 实例: var sa = new ECDsaCng(cngKey); var sa = new ECDiffieHellmanCng(cngKey);