在将一段代码从 Java 移植到 C# 时,我遇到了一个特定的函数,我正在努力寻找解决方案。基本上在解码时,需要将来自 EC PublicKey 的字节数组转换为 PublicKey 对象,而我在互联网上找到的所有内容似乎都没有帮助。
我正在使用 Java.Security 库和 Mono 6.12.0 上的 BouncyCaSTLe 在 Xamarin.Android 上开发这个。
这是我在 Java 中使用的代码:
static PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyFactory kf = KeyFactory.getInstance("EC", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
return (ECPublicKey) kf.generatePublic(pubKeySpec);
}
这是我能想到的最好的解决方案,它不会在 VS 中引发任何错误。可悲的是,它抛出一个异常并告诉我规范是错误的:
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
ECPoint point = curve.Curve.DecodePoint(pubKey);
ECPublicKeyParameters pubKeySpec = new ECPublicKeyParameters(point, domain);
// Get the encoded representation of the public key
byte[] encodedKey = pubKeySpec.Q.GetEncoded();
// Create a KeyFactory object for EC keys
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
// Generate a PublicKey object from the encoded key data
var pbKey = keyFactory.GeneratePublic(new X509EncodedKeySpec(encodedKey));
我之前以类似的方式创建了一个 PrivateKey,我生成了一个 PrivateKey,然后以 PKCS#8 格式导出 key ,然后从该格式生成对象。但是,我无法通过已经设置的字节数组让它工作。
最佳答案
纯 Xamarin 类可以导入原始公共(public) EC key (例如 secp256r1),不需要 BouncyCaSTLe。生成 KeyAgreement
时可以直接使用返回的 key :
using Java.Security.Spec;
using Java.Security;
using Java.Math;
using Java.Lang;
...
private IPublicKey GetPublicKeyFromBytes(byte[] rawXY) // assuming a valid raw key
{
int size = rawXY.Length / 2;
ECPoint q = new ECPoint(new BigInteger(1, rawXY[0..size]), new BigInteger(1, rawXY[size..]));
AlgorithmParameters algParams = AlgorithmParameters.GetInstance("EC");
algParams.Init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec ecParamSpec = (ECParameterSpec)algParams.GetParameterSpec(Class.FromType(typeof(ECParameterSpec)));
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
return keyFactory.GeneratePublic(new ECPublicKeySpec(q, ecParamSpec));
}
在上面的示例中,rawXY
是公钥的 x 坐标和 y 坐标的串联。对于 secp256r1,两个坐标均为 32 字节,因此原始 key 总计为 64 字节。
但是,Java 引用代码不导入原始 key ,而是导入未压缩或压缩的 EC key 。未压缩的 key 对应于 x 和 y 坐标(即原始 key )加上额外的前导 0x04 字节的串联,压缩 key 由 x 坐标加上前导 0x02(对于偶数 y)或 0x03(对于奇数 y)字节组成.
对于 secp256r1,未压缩 key 为 65 字节,压缩 key 为 33 字节。可以使用 BouncyCaSTLe 将压缩 key 转换为未压缩 key 。通过删除前导 0x04 字节将未压缩的 key 转换为原始 key 。
要在未压缩或压缩 key 的情况下应用上述导入,有必要将其转换为原始 key ,这可以使用 BouncyCaSTLe 完成,例如如下:
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.EC;
...
private byte[] ConvertToRaw(byte[] data) // assuming a valid uncompressed (leading 0x04) or compressed (leading 0x02 or 0x03) key
{
if (data[0] != 4)
{
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
Org.BouncyCastle.Math.EC.ECPoint point = curve.Curve.DecodePoint(data).Normalize();
data = point.GetEncoded(false);
}
return data[1..];
}
测试:导入压缩 key :
using Java.Util;
using Hex = Org.BouncyCastle.Utilities.Encoders.Hex;
...
byte[] compressed = Hex.Decode("023291D3F8734A33BCE3871D236431F2CD09646CB574C64D07FD3168EA07D3DB78");
pubKey = GetPublicKeyFromBytes(ConvertToRaw(compressed));
Console.WriteLine(Base64.GetEncoder().EncodeToString(pubKey.GetEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
因为可以使用 ASN.1 解析器(例如 https://lapo.it/asn1js/)轻松验证,导出的 X.509/SPKI key MFkw...
包含原始 key ,即压缩 key 是正确导入。
关于C# 从 EC 公钥字节生成 PublicKey/IPublicKey 对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74809292/