C# 从 EC 公钥字节生成 PublicKey/IPublicKey 对象?

标签 c# xamarin.android cryptography mono bouncycastle

在将一段代码从 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/

相关文章:

c# - 使用接口(interface)作为构造函数参数

c# - 如果重写时未调用 base.OnCreateView(.....) 会怎样?

c++ - openssl 与 Windows capi

c# - 发件人未收到 UDP 广播

c# - 是否有一种优雅的方式可以每天、每晚或按计划重新启动 Topshelf 服务?

android - 有没有办法在 Xamarin Native android 中禁用 Android 上的选项卡式页面之间的滑动?

xamarin - 在CarouselPage中如何将ContentPage分成不同的文件?

c# - DPAPI for WP7使用什么加密算法?

android - 关闭 CipherInputStream 时出现 RuntimeException

c# - 在 C# 中传递函数指针