【问题标题】:Java ECC encoded Key too largeJava ECC 编码的密钥太大
【发布时间】:2018-07-15 10:32:41
【问题描述】:

我是 EC 加密的新手,对此有些挣扎。 我正在使用 Java 8 和 BouncyCatle 提供程序。 我现在的问题是: 当我使用以下代码生成 EC-KeyPair 时:

    ECGenParameterSpec spec = new ECGenParameterSpec("secp521r1");
    KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
    kpg.initialize(spec, new SecureRandom());
    return kpg.generateKeyPair();

并尝试获取公钥的字节数组以将其发送给另一个人,编码后的密钥长度为 158 字节,格式为 X.509。但我期望 X9.62 格式和 65 到 66 字节之间的密钥大小。 为什么公钥这么大,如何使用预期的密钥大小对其进行编码? (我希望密钥大小,因为我希望密钥长度为 521 位)

【问题讨论】:

    标签: java cryptography bouncycastle ecdsa


    【解决方案1】:

    ECC 公钥在语义上是曲线上的一个点;如果您命名的曲线是隐含的,则 X9.62 格式的点在压缩时为 67 个八位字节(Java 字节)或在未压缩时为 133 个八位字节,绝不是任何其他长度。

    如果您的意思是java.security.PublicKey.getEncoded(),它始终采用 Java 所称的“X.509”编码,它实际上是在 X.509 中定义的 ASN.1 结构 SubjectPublicKeyInfo (SPKI),在 rfc5280 sec 4.1 中更方便地使用,编码为DER。这种格式的曲线上的 ECC 公钥是 90 或 158 个八位字节,准确地说,对于未压缩或压缩,Java 提供程序(至少目前)生成未压缩形式(尽管它们可以解析压缩) .

    听起来您可能想要 X9.62 压缩格式,正如我所说,它是 67 字节(不是 65 或 66)。如果是这样,您无法在标准 Java API 中控制点压缩,但 BouncyCastle 实现类确实支持它,因为您拥有由 BC 提供者创建的关键对象。 首先将keypair.getPublicKey() 转换为 (corr) org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey(1.47 之前是org.bouncycastle.jce.provider.JCEECPublicKey)然后getQ() 返回一个org.bouncycastle.math.ec.ECPoint,它有一个(重载)getEncoded(boolean compressed)显然想要。


    对于您的其他但不是(尚未?)官方问题,要从编码点(压缩与否)重新创建 PublicKey 对象,您有两个或三个选项,具体取决于您的计数方式:

    • 为此曲线和点构造一个 ASN.1/DER 编码的 SubjectPublicKeyInfo 结构(Java 将其称为“X.509”格式),将其放入X509EncodedKeySpec,并通过适当的KeyFactory 运行它。可以使用标准 SunEC 提供程序(假设是 j7+,而不是 RedHat 残缺版本)或 BC 提供程序。手动构建像 SPKI 这样的 ASN.1 编码通常很困难,但在这种特定情况下还不错;或者如果你有 BC,你可以使用它的 ASN.1 功能

    • 直接调用 BC 例程来执行 EC KeyFactory对上述输入执行的操作

    创建点然后以三种方式使用它的示例代码:

    // as needed in addition to standard java.security and javax.xml 
    import org.bouncycastle.asn1.ASN1EncodableVector;
    import org.bouncycastle.asn1.DERBitString;
    import org.bouncycastle.asn1.DERSequence;
    import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    import org.bouncycastle.asn1.x9.X962Parameters;
    import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
    import org.bouncycastle.crypto.params.ECPublicKeyParameters;
    import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
    import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
    import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
    import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.math.ec.ECCurve;
    import org.bouncycastle.math.ec.ECPoint;
    
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
        kpg.initialize(new ECGenParameterSpec("secp521r1"));
        org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey ku = 
                (org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey)kpg.generateKeyPair().getPublic();
        byte[] encodedpoint = ku.getQ().getEncoded(true/*compressed*/);
        
        { // construct SPKI by hand, this curve only
            byte[] hdr = DatatypeConverter.parseHexBinary("3058301006072a8648ce3d020106052b81040023034400");
            // could also write out byte[] hdr = {0x30,0x58,0x30,0x10... but items with 0x80 set need casts
            if( 0x44 /*hdr[0x15]*/ -1 != encodedpoint.length ) throw new Exception ("BAD COMPRESSED POINT FOR secp521r1!");
            byte[] spki = Arrays.copyOf(hdr,90); System.arraycopy(encodedpoint,0, spki,0x17, 0x43);
            PublicKey k2 = KeyFactory.getInstance("EC" /*,provider?*/).generatePublic(new X509EncodedKeySpec(spki));
            Signature.getInstance("ECDSA").initVerify(k2); // sanity check
        }
        { // construct SPKI with BC
            AlgorithmIdentifier algid = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey,SECObjectIdentifiers.secp521r1);
            ASN1EncodableVector vec = new ASN1EncodableVector();
            vec.add(algid); vec.add(new DERBitString(encodedpoint));
            byte[] spki = new DERSequence(vec).getEncoded();
            PublicKey k2 = KeyFactory.getInstance("EC" /*,provider*/).generatePublic(new X509EncodedKeySpec(spki));
            Signature.getInstance("ECDSA").initVerify(k2); // sanity check
        }
        { // call BC directly
            ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION;
            X962Parameters params = X962Parameters.getInstance(org.bouncycastle.asn1.sec.SECObjectIdentifiers.secp521r1);
            ECCurve curve = EC5Util.getCurve(configuration, params);
            /*ECParameterSpec ecSpec = EC5Util.convertToSpec(params, curve);*/
            ECPoint point = curve.decodePoint(encodedpoint).normalize();
            ECPublicKeyParameters kparams = new ECPublicKeyParameters(point, ECUtil.getDomainParameters(configuration, params));
            PublicKey k2 = new BCECPublicKey ("EC"/* or "ECDH" etc*/, kparams, configuration);
            Signature.getInstance("ECDSA").initVerify(k2); // sanity check
        }
    

    相关Loading raw 64-byte long ECDSA public key in Java,用于未压缩的P256。

    【讨论】:

    • 非常感谢!我只需要将其更改为 BCECPublicKey,因为我无法投射它。但是现在我有一个新问题,我如何从 Q byte[] 到 PublicKey?
    • @Pilikio:哇!当我重新测试时,我发现了同样的情况,并最终追查到我最初是在一个 JVM 上测试的,但我忘记了我前段时间用旧版本的 Bouncy 设置了一些东西——而 Bouncy 在版本之间改变了这一点;编辑。关于您的新问题,您应该将其添加到您的问题中,因为堆栈政策是问题应该在问题中,答案应该在答案中,并且 cmets 不用于讨论,可以随时删除。但是还是看编辑:)
    • 没关系,苦恼了一天半终于找到了解决办法,BouncyCastle和Java的类名一样但是互不兼容……
    • X9.62 格式的点大小是否为 67 个八位字节,是否带有“压缩”位?因为我有一个压缩过的密钥,但它的长度为 68 个八位字节,并且在尝试从中创建给定曲线上的 ECPoint 时出现错误。
    • @michalk:仅适用于 P-521 aka secp521r1 压缩为 1+66=67,未压缩为 1+2x66=133。 (还有hybrid,没有人用,和未压缩的一样。)从第一个八位字节你就可以分辨出它是哪个:02或03是压缩的,04是未压缩的。
    【解决方案2】:

    下面的代码(从 BouncyCastle 修改)可以使用任何公钥(不仅是 secp521r1)

    
    import org.bouncycastle.asn1.ASN1ObjectIdentifier;
    import org.bouncycastle.asn1.DERNull;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    import org.bouncycastle.asn1.x9.X962Parameters;
    import org.bouncycastle.asn1.x9.X9ECParameters;
    import org.bouncycastle.asn1.x9.X9ECPoint;
    import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
    import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
    import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
    import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
    import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.jce.spec.ECNamedCurveSpec;
    import org.bouncycastle.math.ec.ECCurve;
    
    import java.math.BigInteger;
    import java.security.KeyFactory;
    import java.security.Security;
    import java.security.spec.ECParameterSpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    
    public class TestCompressionEncoded {
    
        static X962Parameters getDomainParametersFromName(ECParameterSpec ecSpec, boolean compress) {
            X962Parameters x962Param;
            if (ecSpec instanceof ECNamedCurveSpec) {
                ASN1ObjectIdentifier var3 = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName());
                if (var3 == null) {
                    var3 = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName());
                }
    
                x962Param = new X962Parameters(var3);
            } else if (ecSpec == null) {
                x962Param = new X962Parameters(DERNull.INSTANCE);
            } else {
                ECCurve var5 = EC5Util.convertCurve(ecSpec.getCurve());
                X9ECParameters var4 = new X9ECParameters(var5, new X9ECPoint(EC5Util.convertPoint(var5, ecSpec.getGenerator()), compress), ecSpec.getOrder(), BigInteger.valueOf((long)ecSpec.getCofactor()), ecSpec.getCurve().getSeed());
                x962Param = new X962Parameters(var4);
            }
            return x962Param;
        }
    
        static byte[] encodeKeyWithCompression(BCECPublicKey x) throws Exception {
            AlgorithmIdentifier var1 = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, getDomainParametersFromName(x.getParams(), true));
            byte[] var2 = x.getQ().getEncoded(true);
            return KeyUtil.getEncodedSubjectPublicKeyInfo(var1, var2);
        }
    
        public static void main(String...args) throws Exception {
            Security.addProvider(new BouncyCastleProvider());
            String publicKey = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagGFnfckwVFpKg10+S2ttJYVUB4q+kPpnJg/YHV5xMnSLA==";
            KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
            BCECPublicKey bcePubKey = (BCECPublicKey) fact.generatePublic(new X509EncodedKeySpec( Base64.getDecoder().decode(publicKey)));
    
            System.out.println("Uncompressed encoded value: " + publicKey);
            System.out.println("Compressed encoded value: " + Base64.getEncoder().encodeToString(encodeKeyWithCompression(bcePubKey)));
        }
    }
    
    

    输出(对于 prime256v1)

    Uncompressed encoded value: MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAELPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagGFnfckwVFpKg10+S2ttJYVUB4q+kPpnJg/YHV5xMnSLA==
    Compressed encoded value: MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACLPqrW2JAXKTbjfh9M3X3b85Uje7T0r2gu7qKPmmyagE=
    
    

    【讨论】:

    • 使用编码不是公认的关键规格:从字节未能构建体序列[]:在流中检测到额外的数据传递公开密钥:043d076b9e470cb23148f12eebd5c5d00c3cb1a2a19c688f0f10176a96fddef2e833449c88b9f87d7a15d1afc42eda3bd3dc91567cb195ea401e4e86d115aee3f1 跨度>
    猜你喜欢
    • 2013-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    • 1970-01-01
    • 2020-03-15
    • 2017-03-12
    • 2015-06-13
    相关资源
    最近更新 更多