如何在 windows 上的 .net 框架中使用来自 ECC X509 证书的 public 密钥加密数据?
How can I encrypt data using a public key from ECC X509 certificate in .net framework on windows?
我正在使用:
- Windows10(版本 1709,OS 内部版本 17025.1000)
- .net 框架 4.7
- VS 2017(版本:15.3.5)
这是我所做的:
使用 OpenSSL 和 https://gist.github.com/sidshetye/4759690 处的脚本中概述的步骤获得了自签名 ECC 证书并进行了修改:
a) 在 256 位素数域上使用 NIST/P-256 曲线
b) 使用 SHA-256
从文件(在上一步中生成)将证书加载到 X509Certificate2 对象中
将 PFX 文件导入 windows 信任库(用于测试)。这样就成功了。
- 检查导入的证书显示 Public 关键字段为 'ECC (256 Bits)' 和 Public 关键参数为 'ECDSA_P256'.
- 接下来想弄清楚如何用这个证书加密。
我卡在了最后一步,因为所有使用 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 密钥来加密数据。
###背景
非对称算法具有三种不同的目的(据我所知)
- 加密
- RSA 是唯一可以直接执行此操作的“标准”算法。
- 签名
- RSA
- 每日动态广告
- ECDSA
- ElGamal 签名
- 密钥约定
- 迪菲-赫尔曼 (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节(椭圆曲线综合加密方案)
###设置
- 选择 ANSI-X9.63-KDF 和 SHA-2-256 作为哈希函数。
- 选择HMAC–SHA-256–256.
- 在CBC模式下选择AES–256。
- 选择椭圆曲线 Diffie-Hellman 原语。
- 您已经选择了secp256r1。
- 硬编码。完成。
- 点压缩很烦人,选择不用。
- 我省略了 SharedInfo。这可能使我成为一个坏人。
- 不使用 XOR,N/A。
###加密
在右曲线上创建一个临时密钥。
ECDiffieHellman ephem = ECDiffieHellman.Create(curve);
我们决定不。
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 和 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 });
加密内容。
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);
}
MAC这
byte[] d;
using (HMAC hmac = new HMACSHA256(mk))
{
d = hmac.ComputeHash(em);
}
完成
// 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
实例可用于创建 ECDsaCng
或 ECDiffieHellmanCng
实例:
var sa = new ECDsaCng(cngKey);
var sa = new ECDiffieHellmanCng(cngKey);
我正在使用:
- Windows10(版本 1709,OS 内部版本 17025.1000)
- .net 框架 4.7
- VS 2017(版本:15.3.5)
这是我所做的:
使用 OpenSSL 和 https://gist.github.com/sidshetye/4759690 处的脚本中概述的步骤获得了自签名 ECC 证书并进行了修改:
a) 在 256 位素数域上使用 NIST/P-256 曲线
b) 使用 SHA-256
从文件(在上一步中生成)将证书加载到 X509Certificate2 对象中
将 PFX 文件导入 windows 信任库(用于测试)。这样就成功了。
- 检查导入的证书显示 Public 关键字段为 'ECC (256 Bits)' 和 Public 关键参数为 'ECDSA_P256'.
- 接下来想弄清楚如何用这个证书加密。
我卡在了最后一步,因为所有使用 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 密钥来加密数据。
###背景
非对称算法具有三种不同的目的(据我所知)
- 加密
- RSA 是唯一可以直接执行此操作的“标准”算法。
- 签名
- RSA
- 每日动态广告
- ECDSA
- ElGamal 签名
- 密钥约定
- 迪菲-赫尔曼 (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节(椭圆曲线综合加密方案)
###设置
- 选择 ANSI-X9.63-KDF 和 SHA-2-256 作为哈希函数。
- 选择HMAC–SHA-256–256.
- 在CBC模式下选择AES–256。
- 选择椭圆曲线 Diffie-Hellman 原语。
- 您已经选择了secp256r1。
- 硬编码。完成。
- 点压缩很烦人,选择不用。
- 我省略了 SharedInfo。这可能使我成为一个坏人。
- 不使用 XOR,N/A。
###加密
在右曲线上创建一个临时密钥。
ECDiffieHellman ephem = ECDiffieHellman.Create(curve);
我们决定不。
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 和 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 });
加密内容。
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); }
MAC这
byte[] d; using (HMAC hmac = new HMACSHA256(mk)) { d = hmac.ComputeHash(em); }
完成
// 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
实例;returnECDiffieHellmanCng
没有扩展方法。) cngKey
实例可用于创建ECDsaCng
或ECDiffieHellmanCng
实例:var sa = new ECDsaCng(cngKey); var sa = new ECDiffieHellmanCng(cngKey);