繁体   English   中英

Java Bouncy Castle:点编码无效 0x45

[英]Java Bouncy Castle : Invalid point encoding 0x45

我正在使用bouncy castle库在java中加载公钥,但总是收到错误Invalid point encoding 0x45

公钥是在客户端使用 C# CNG API 生成的。

Java方法一:

public PublicKey loadPublicKey(String encodedPublicKey)
            throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
        
        byte[] keybytes = java.util.Base64.getDecoder().decode(encodedPublicKey);
        
        Security.addProvider(new BouncyCastleProvider());
        
        ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
        
        ECPublicKeySpec keySpec = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
        
        return new BCECPublicKey("ECDH", keySpec, BouncyCastleProvider.CONFIGURATION);

    }

方法二

public PublicKey loadPublicKey(String pKey) throws Exception {
        byte[] keybytes = java.util.Base64.getDecoder().decode(pKey);
        Security.addProvider(new BouncyCastleProvider());
        ECParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
        ECPublicKeySpec pubKey = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
        KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
        return kf.generatePublic(pubKey);
    }

例外

java.lang.IllegalArgumentException: Invalid point encoding 0x45
    at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:443)

下面的方法来创建公钥

public static (byte[] publicKey, byte[] privateKey) CreateKeyPair()
        {
            using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(
                // need to do this to be able to export private key
                CngKey.Create(
                    CngAlgorithm.ECDiffieHellmanP256,
                    null,
                    new CngKeyCreationParameters
                    { ExportPolicy = CngExportPolicies.AllowPlaintextExport })))
            {
                cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                cng.HashAlgorithm = CngAlgorithm.Sha256;
                // export both private and public keys and return
                var pr = cng.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
                var pub = cng.PublicKey.ToByteArray();
                return (pub, pr);
            }
        }

生成RUNLMSAAAAHddHI6TOEDG/Ka7naBbLQH0u/DSFfbKJI2w0WSoxrmFkwKm1tktz4wD0rqnwkZp8FwdHJ+8OVrTcpDMmxrwvS6

我在 java 收到的密钥是72 bytes 但我认为bouncy castle java支持64 bytes的密钥。

我也在调查这个但没有得到任何帮助

C# 代码将公钥导出为 Base64 编码的EccPublicBlob ,其格式在问题中给出的链接中描述:
前 4 个字节 0x45434B31 以小端顺序表示曲线 P-256 的公共 ECDH 密钥,接下来的 4 个字节以小端顺序表示密钥长度(以字节为单位)(0x20000000 = 32),其余是曲线 P-256 的 x 和 y 坐标EC点即公钥,每个32字节。
令人惊讶的是,在您发布的密钥中,第二个 4 个字节是 0x20000001,但 x 和 y 坐标各为 32 个字节。 这里可能存在复制/粘贴错误。 无论如何,使用发布的 C# 代码,我无法在第二个 4 字节中重现值不是 0x20000000 的键。

Java/BC 不直接支持导入EccPublicBlob (这是 MS 专有的),但它确实支持导入未压缩的公钥。 这会在 x 和 y 坐标连接并且 0x04 用作前缀时产生。 然后可以使用 Java/BC 进行导入,如下所示:

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
...
public static PublicKey getPubKeyFromCurve(byte[] uncompRawPubKey, String curveName) throws Exception {
      ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
      ECNamedCurveSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN());
      ECPoint point = ECPointUtil.decodePoint(params.getCurve(), uncompRawPubKey);
      ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
      KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
      ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(pubKeySpec);
      return pubKey;
}

测试(假设EccPublicBlob是 Base64 编码的,就像发布的一样):

import java.util.Base64;
...
String publicKeyBlob = "RUNLMSAAAAAFzw4IGY4N8PKVt0MGF38SAKU5ixJhptVUdrWzuPhFDOcj/2k4SlGRN1RpRMbar9Iu7Uvcx7Vtm8Wa0HSzWJdE";
byte[] rawPublic = new byte[65];
rawPublic[0] = 0x04;
System.arraycopy(Base64.getDecoder().decode(publicKeyBlob), 8, rawPublic, 1, 64);
PublicKey pub = getPubKeyFromCurve(rawPublic, "P-256");
System.out.println(Base64.getEncoder().encodeToString(pub.getEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBc8OCBmODfDylbdDBhd/EgClOYsSYabVVHa1s7j4RQznI/9pOEpRkTdUaUTG2q/SLu1L3Me1bZvFmtB0s1iXRA==

该测试导入EccPublicBlob并将其导出为 X.509/SPKI 格式的 Base64 编码的 DER 密钥。 这可以使用 ASN.1 解析器读取,例如https://lapo.it/asn1js/ ,从而得到验证。


请注意,C# 还支持其他格式的导出。 但是,这取决于版本。 例如,从 .NET Core 3.0 开始,有方法ExportSubjectPublicKeyInfo()以 X.509/SPKI 格式导出公钥,DER 编码。 这种格式和编码可以使用X509EncodedKeySpec直接导入 Java(即使没有 BouncyCastle)。
在其他版本的 C# 中,可以使用 BouncyCastle for C# 进行导出,它也支持 X.509/SPKI 格式。
由于您没有发布您的 .NET 版本,因此尚不清楚您有哪些具体的替代方案。


请记住,P-256 的 ECDH 密钥也可以通过以下方式更简单地创建:

ECDiffieHellmanCng cng = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256)

或跨平台

ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM