god*_*god 3 java elliptic-curve public-key
我正在尝试学习一些加密编码,并生成了当前保存在字节数组( byte[] privatekey )中的 32 字节私钥。我知道公钥是使用secp256k1命名的椭圆曲线参数和公式生成的,其中publickey = G * privatekey,其中 G 是椭圆曲线上的某个点(ECPoint?),但我无法将该命名参数规范和公式转换为实际编码公钥。java.security.*我知道自 java 7 以来,和包中包含一些类java.security.spec.*可以用简短的代码来执行此操作,但我找不到一个很好的示例来说明如何在不使用第三方库的情况下执行此操作。
这个比特币 stackexchange 链接包含所有理论答案以及出色的 python 和 C# 代码,但没有 Java 代码。
编辑/更新:我尝试使用以下代码获取我需要的内容:
String secp256k1_G_uncompressed_string = "0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8";
byte[] secp256k1_G_uncompressed_bytes = DatatypeConverter.parseHexBinary(secp256k1_G_uncompressed_string);
String privatekeystring = "1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD";
byte[] privatekeybytes = DatatypeConverter.parseHexBinary(privatekeystring);
BigInteger secp256k1_G_num = new BigInteger(1, secp256k1_G_uncompressed_bytes);
BigInteger privatekey_num = new BigInteger(1, privatekeybytes);
BigInteger curvepoint = secp256k1_G_num.multiply(privatekey_num);
byte[] publickeybytes = curvepoint.toByteArray();
System.out.println(DatatypeConverter.printHexBinary(privatekeybytes));
System.out.println(DatatypeConverter.printHexBinary(publickeybytes));
Run Code Online (Sandbox Code Playgroud)
应该使用正确编码生成的公钥是这样的:
04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6 fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495
但正在生成的公钥是这样的:
4E6801418BB6EF9F462F69830F82EB51BB9224219B9D89C8C34FB746297F59779D8B986194181BD7AB99DC7E3086914EA13C4B37E05716CADCA0AE391CE81C4B85E0F09E8 628F0F81692B5D08D0D8B9E20615A5D23DE0F591D02C650554BB1D8
椭圆曲线点不是整数。将点 (G) 的编码表示放入 a 中BigInteger并尝试将其用作整数是完全不正确的。椭圆曲线点乘不是整数乘法,并且远没有 那样简单BigInteger.multiply。并且它的左边写有标量,例如kG 而不是Gk。
对于任何 Java 程序员来说,将比特币 Q 中给出的标准(或至少是传统的)算法翻译成 Java 确实应该是一个相当简单的练习。
椭圆曲线上点的标量乘法包含(在答案中)P192 又名 secp192r1 的正确实现;它可以通过将 p 和 a 替换为规范中的值(来自https://www.secg.org的 SEC2或 X9.62,如果有的话)或任何现有实现(包括 Java(见下文))来转换为 secp256k1 ——并丢弃 P192 特定的测试数据。实际上你主要需要改变 p; Koblitz 曲线选择为 a=0。椭圆曲线乘法函数包含一个不太正确的实现,据称该实现适用于 secp256k1,但实际上并不包含任何曲线的常量。
从 java 7 开始,java.security.* 和 java.security.spec.* 包中包含一些类,可以用短代码执行此操作
并不真地。java.security首先,Java 加密将您在javax.crypto实现代码中看到的类隔离开来,这些类位于一个或多个“提供者”中完全不同的类中(大部分(仍然)在sun.*和下com.sun.*),这些提供者是单独的 jars,并且在技术上是可选的;尽管大多数人不这样做,但可以在不更改代码中的调用的情况下删除、添加或更改提供程序。EC 加密的 JCA“外观”类自 java 5(称为 1.5)以来就存在,但标准构建中不包含实现 EC 算法的提供程序;要使用它们,您必须添加第三方提供商。从 java 7 开始,包含了标准 SunEC 提供程序。然而,JCA(对于所有算法,不仅仅是 EC)在生成后将私钥和公钥严格分开,特别是它无法提供访问 EC 内部存在的私钥到公钥的派生逻辑的方法。
它确实包含多个标准曲线的参数,包括 secp256k1,您可以使用它来避免从规范中复制它们的工作。似乎没有直接的方法来访问此数据,但您可以通过生成随机数密钥并丢弃它来间接访问此数据。或者,由于您已经拥有私钥,因此您可以创建 Java 使用的编码 (PKCS8) 并将其读入,从而生成相同的曲线参数和可用的密钥。一般来说,构建像 PKCS8 这样的 ASN.1 DER 编码相当复杂,但对于 EC 来说,它被简化了,因为 (1) 每个人都使用“命名”形式,将曲线编码为单个 OID,并且 (2) 标准指定了给定曲线长度固定的私有值;因此,给定 EC 曲线的 PKCS8 编码由固定前缀后跟私钥值组成。示例片段:
/* this is not needed
KeyPairGenerator kg = KeyPairGenerator.getInstance ("EC");
kg.initialize (new ECGenParameterSpec ("secp256k1"));
ECParameterSpec p = ((ECPublicKey) kg.generateKeyPair().getPublic()).getParams();
instead do the following: */
AlgorithmParameters a = AlgorithmParameters.getInstance("EC");
a.init(new ECGenParameterSpec("secp256k1"));
ECParameterSpec p = a.getParameterSpec(ECParameterSpec.class);
System.out.println ("p=(dec)" + ((ECFieldFp) p.getCurve().getField()).getP() );
java.security.spec.ECPoint G = p.getGenerator();
System.out.format ("Gx=(hex)%032x%n", G.getAffineX());
System.out.format ("Gy=(hex)%032x%n", G.getAffineY());
//
byte[] privatekey_enc = DatatypeConverter.parseHexBinary(
"303E020100301006072A8648CE3D020106052B8104000A042730250201010420"+
"1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD");
// note fixed prefix for PKCS8-EC-secp256k1 plus your private value
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey k1 = kf.generatePrivate(new PKCS8EncodedKeySpec(privatekey_enc));
ECParameterSpec p2 = ((ECPrivateKey) k1).getParams();
System.out.println ("again p=(dec)" + ((ECFieldFp) p2.getCurve().getField()).getP() );
Run Code Online (Sandbox Code Playgroud)
产生输出:
p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663
Gx=(hex)79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Gy=(hex)483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
again p=(dec)115792089237316195423570985008687907853269984665640564039457584007908834671663
Run Code Online (Sandbox Code Playgroud)
请注意,基点 G 的这些坐标符合您的预期。我混合显示了十进制和十六进制只是为了展示可能性;这不会影响计算机中的实际数字。
针对评论添加:
变量 p 和 p2 是ECParameterSpec包含 EC 曲线参数的对象(基础场、曲线系数、基点又名生成器、阶数和辅因子;以及内部“名称”,尽管 API 未公开它)。我打印的标记为“p”的值是调用的结果getP,该调用从曲线参数中返回一项,即基础素数域的模数,因此您需要在链接帖子中显示的计算中使用该值mod(p)和。modInverse(p) modPow(,p)由于这个 p(或 P)是曲线的参数,因此该曲线上的所有关键点都是相同的;请注意,我打印的两个值是相同的,即使它们来自不同的键。实际上有两种针对密码学标准化的椭圆曲线:素数域上的曲线,表示为 Fp,以及特征二的扩展域上的曲线,表示为 F2m。secp256k1 是第一种,这就是为什么ECFieldFp在调用之前强制转换为getP().
是的,我的固定前缀包含将私钥 (PKCS8) 编码标识为 EC 和 secp256k1 的标头和字段,并且该前缀对于所有 EC secp256k1 私钥都是相同的。p 值如上所述,而不是私钥或公钥。是的,如果你有公共点,你可以将它与 组合成ECParameterSpecanECPublicKeySpec并将其转换并使用它 - 或者你可以将点编码附加到类似但不同的固定前缀以获取X509EncodedKeySpecJava 用于公钥和转换的编码不需要提前ECParameterSpec- 但据我了解,你的整个问题是你还没有公共点并且想要导出它,这需要链接帖子中显示的点乘法计算。
| 归档时间: |
|
| 查看次数: |
9488 次 |
| 最近记录: |