在 .NET 中加载椭圆曲线 (EC) 密钥
本文将讨论在.NET 中解析和加载EC 密钥的不同方式。在本文中,您将了解 EC 密钥的构成,然后了解如何以四种不同的方式使用这些知识ECDsa
在 .NET 中加载或创建对象。到本文结束时,您应该能够加载 EC 密钥,无论格式如何。
本文针对 .NET Core 3.1 及更高版本。
椭圆曲线 (EC) 密钥的参数
加载 EC 密钥时,您将需要了解三件事:
1.私钥(可选)2.公钥3.您需要使用的曲线
私钥d是一个随机整数,其长度取决于您使用的曲线。如果你有私钥,你就可以对数据进行签名。
公钥是椭圆曲线上的一个点,使用坐标x&y。因此,公钥特定于该曲线,并使用私钥乘以曲线上的生成点G生成。使用公钥,您将能够验证签名。
这表明您需要了解正在使用的椭圆曲线(例如,NIST 的 P-256[1]或secp256k1[2])才能使用椭圆曲线加密 (ECC)。如果不知道曲线,您将不知道私钥应该有多长、提供的安全级别或公钥是否有效(如果它们是曲线上的点)。
如果您想了解有关椭圆曲线密码学工作原理的更多信息,我发现开发人员实用密码学[3]是一本有用的读物。
.NET 的ECDsa对象
.NET 对 ECDSA 的抽象是ECDsa
在System.Security.Cryptography
. 这继承AsymmetricAlgorithm
并遵循RSA
您之前可能使用过的类的类似模式。 ECDsa
是一个抽象类,具有使用不同实现的各种平台(例如,Windows 上的 CNG 和 Linux 上的 OpenSSL)。
此类的主要用途是调用SignData
和VerifyData
方法。否则,您可能会将其传递到SecurityKey 实现[4]或 SignedXml。
本文的其余部分将重点介绍如何创建ECDsa
实现。
使用ECDsa和命名曲线生成 EC 密钥
创建新 EC 密钥的最简单方法是使用 .NETCreate
上的方法让 .NET 为您完成ECDsa
。这将在您选择的曲线上创建一个私钥。您通常会使用已知的命名曲线,例如 NIST 的 P-256,又名 secp256r1。您可以在ECCurve.NamedCurve
课程中找到 .NET 支持的开箱即用曲线。
ECDsa key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
这种方法非常适合在测试期间即时生成密钥,但是一旦对象被处置,您就会丢失密钥。幸运的是,您可以使用各种导出方法ECDsa
;否则,您可以使用该ExportParameters
方法直接访问 EC 参数。这些导出方法使您可以访问ECParameters
包含关键点曲线、D 和 Q ( x & y ) 点的对象,您将在接下来的操作中看到这些点。
创建ECDSA使用为ECParameters
假设您已经收到曲线的原始点,并且您需要以这种方式加载密钥。这在使用 JSON Web Keys (JWK) 和OpenID Connect[5]时很常见。
这是来自 RFC 7517 的 JWK 示例:
{
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid": "my EC key"
}
要加载密钥,您需要知道密钥使用什么曲线以及公钥参数(x和y坐标)。也可以使用d参数将私钥包含在 JWK 中;然而,这种情况很少见。
您可以再次使用ECDsa
的Create
方法加载这些坐标,但这次您将传入一些ECParameters
.
// parse curve from JOSE format
// https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve
var curve = crv switch
{
"P-256" => ECCurve.NamedCurves.nistP256,
"P-384" => ECCurve.NamedCurves.nistP384,
"P-521" => ECCurve.NamedCurves.nistP521,
_ => throw new NotSupportedException()
};
ECDsa key = ECDsa.Create(new ECParameters
{
Curve = curve,
D = Base64UrlEncoder.DecodeBytes(d), // optional
Q = new ECPoint
{
X = Base64UrlEncoder.DecodeBytes(x),
Y = Base64UrlEncoder.DecodeBytes(y)
}
});
在这里,您正在解析 JOSE 名称中的曲线,并使用 中找到的编码器对其他参数进行 base64url 解码Microsoft.IdentityModel.Tokens
。
此解析特定于 JWK 使用的格式,但它演示了如何在知道其参数的情况下加载 EC 密钥。
有一种Validate
方法ECParameters
可以检查关键和坐标大小并检查曲线是否有效。但是,Create
on的方法ECDsa
会为您调用它。
如果您确实有 JWK 并打算使用它来验证 JWT,我建议将 JWK 直接传递到构造函数中Microsoft.IdentityModel.Tokens.JsonWebKey
并跳过我在上面演示的手动解析。
从X509Certificate2加载ECDsa
从 an 加载 EC 密钥X509Certificate2
是简单使用GetECDsaPrivateKey
和GetECDsaPublicKey
方法的一种情况。
ECDsa key = cert.HasPrivateKey ? cert.GetECDsaPrivateKey() : cert.GetECDsaPublicKey();
为了首先加载X509Certificate2
,您可以从 PEM 文件加载它[6]。
X509Certificate2.PrivateKey与GetRSAPrivateKey和GetECDsaPrivateKey
过去,您可能在 上使用了PrivateKey
和PublicKey
属性X509Certificate2
。但是,如果您尝试在包含 EC 密钥的证书上使用这些属性,您将获得 null forPrivateKey
和以下异常PublicKey
:
System.NotSupportedException: 不支持证书密钥算法。
在 System.Security.Cryptography.X509Certificates.PublicKey.get_Key()
要使用来自 X.509 的 EC 密钥,您需要使用GetECDsaPrivateKey
和GetECDsaPublicKey
方法。
从 .NET 6 开始,PrivateKey
和PublicKey
属性被标记为过时。因此,您必须使用RSA
和ECDsa
特定方法来全面加载密钥。
从十六进制字符串加载ECDsa
有时,您会看到 EC 密钥作为十六进制字符串共享。这在加密社区中很常见,我的意思是密码学和加密货币。我不能说它在密码学中是否有很大用处。这些键看起来像这样:
Private: c711e5080f2b58260fe19741a7913e8301c1128ec8e80b8009406e5047e6e1ef
Public: 04e33993f0210a4973a94c26667007d1b56fe886e8b3c2afdd66aa9e4937478ad20acfbdc666e3cec3510ce85d40365fc2045e5adb7e675198cf57c6638efa1bdb
这些键起初可能看起来有点奇怪,但它们实际上映射回您已经使用的参数。私钥只是d参数的十六进制表示,公钥是连接在一起的x &y坐标(第一个字节是标签)。
因此,您可以再次使用ECParameters
来创建您的ECDsa
对象:
public static ECDsa LoadFromHex()
{
var privateKeyBytes = FromHexString("c711e5080f2b58260fe19741a7913e8301c1128ec8e80b8009406e5047e6e1ef");
var publicKeyBytes = FromHexString("04e33993f0210a4973a94c26667007d1b56fe886e8b3c2afdd66aa9e4937478ad20acfbdc666e3cec3510ce85d40365fc2045e5adb7e675198cf57c6638efa1bdb");
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256, // you'd need to know the curve before hand
D = privateKeyBytes,
Q = new ECPoint
{
X = publicKeyBytes.Skip(1).Take(32).ToArray(),
Y = publicKeyBytes.Skip(33).ToArray()
}
});
}
private static byte[] FromHexString(string hex) {
var numberChars = hex.Length;
var hexAsBytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
hexAsBytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return hexAsBytes;
}
源代码
您可以在我的GitHub 示例存储库中[7]找到演示上述方法的可执行代码。此代码是多目标 .NET Core 3.1 及以上版本,可在 Windows 和 Linux 上运行。
查看原文:https://www.scottbrady91.com/c-sharp/ecdsa-key-loading
References
[1]
NIST 的 P-256: https://www.scottbrady91.com/C-Sharp/JWT-Signing-using-ECDSA-in-dotnet-Core[2]
secp256k1: https://www.scottbrady91.com/C-Sharp/Supporting-Custom-JWT-Signing-Algorithms-in-dotnet-Core[3]
开发人员实用密码学: https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc[4]
SecurityKey 实现: https://www.scottbrady91.com/C-Sharp/JWT-Signing-using-ECDSA-in-dotnet-Core[5]
OpenID Connect: https://www.scottbrady91.com/OpenID-Connect/OpenID-Connect-Overview[6]
从 PEM 文件加载它: https://www.scottbrady91.com/C-Sharp/PEM-Loading-in-dotnet-core-and-dotnet[7]
GitHub 示例存储库中: https://github.com/scottbrady91/Blog-Example-Classes/tree/master/EcdsaKeyLoading