java - 将 Java DH 公钥生成器转换为 Node JS
问题描述
我有这样的功能:
public String generatePublicKeyEncoded() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
Serializable serializable = new SecureRandom();
BigInteger bigInteger = BigInteger.probablePrime(1024, (Random) serializable);
serializable = BigInteger.probablePrime(1024, (Random) serializable);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
keyPairGenerator.initialize(new DHParameterSpec(bigInteger, (BigInteger)serializable));
this.keyPair = keyPairGenerator.generateKeyPair();
return HexEncoder.encode(this.keyPair.getPublic().getEncoded());
}
我正在尝试转换为节点。我的 node.js 代码是:
const prime_length = 1024;
const diffHell = crypto.createDiffieHellman(prime_length);
diffHell.generateKeys('base64');
const hexPublicKey = diffHell.getPublicKey('hex')
但我的 Java 生成的公钥是
308201A33082011706092A864886F70D0103013082010802818100DE4779E7F4523CA143FFE102853E671CAAAB96203B1FC3C42D0EA1CB6878FCA889C79C709DDB1190DF9073050B1AD410D34A48A6E5A1D2C1854C471528DB3C4FE48A237FC86BAA777AAB8A17750DBA7948F258BD55E480BA3FFD87076BC4B0429CE731E31A8320DC594F9BD5022CD203C95D73F5B3E91C930A0AF2FA7AEE160502818100D719835971E8A91980141201FF765392A0049841142A3C203862AF8FFBC719528F142706639BD0C614EBA72660876F5A7011B5FC08224824577324FCF847648F24A600F408BED17770AAF958CC75076164DAA5E6179BFC573F40E2B086FC18A48B67A10F7B9B7C037A7BEEEDF554764CC8653C09AA3D330CC3C30F89616D810703818500028181008EE027B916FC87BE2627CFB53F4DA76693A06EECAC8DA2A6B9155C66D60BCD9977A811B3732F72880BDE1AA259731FE37AD4284909481777444F7A3C5BCF7F287AD5F05BE45F4553CC06D599E7E3BAD6736D6BCA59EAD8B0F6C0FE980F471304AC2600A677A70CE46F2835FA6797D18FAA8A237573916E604AF40CA456CCEE1E
节点生成的密钥是:
1b6254629d00a18333ec701558ef34b0df9b86569985799106c4d71d1fabd3c41ef25c7bf4a522498a92c983ca09e3435ebd09b51220d6ffccb296803f1718bf8cf7e0f72432b65b60d8a49d6d80fec6e708a88d2b00e2829e74534fa86a94d96a743725c6eb2076d5ac03edd909491639a359467a67fc64b9dc2fb420d822a2
错误在哪里?
HexEncoder 类:
public class HexEncoder {
private static final byte[] a = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 };
public static String encode(byte[] paramArrayOfByte)
{
return encode(paramArrayOfByte, false, 0);
}
public static String encode(byte[] paramArrayOfByte, boolean paramBoolean, int paramInt)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < paramArrayOfByte.length; i++)
{
if ((paramBoolean) && (i > 0) && (i % paramInt == 0)) {
stringBuilder.append("\r\n");
}
int j = paramArrayOfByte[i];
int k = j;
if (j < 0) {
k = j + 256;
}
j = k / 16;
stringBuilder.append((char)a[j]);
stringBuilder.append((char)a[(k % 16)]);
}
return stringBuilder.toString();
}
}
解决方案
首先,您的 Java 生成的 DH 参数是非常错误的。DH 的模数 (p) 和生成器 (g)不仅仅是大的随机素数。p 必须是一个大素数,并且不需要是随机的,但必须有一个乘法群 (Z p *) 对于“非平滑”阶,即阶的素因数分解不能完全由小因子组成,或者更具体地说,阶必须有一个大素因数。因为素数 p 的乘法群的阶是 p-1,这通常通过选择安全素数 (p=2q+1) 或 Schnorr 素数 (p=kq+1) 来完成,其中 q 是一个足够大的素数。OTOH g 不必很大(并且不得大于或等于 p,这可能在您的代码中发生)并且不必是随机的,但必须生成足够大小的子组(通常为 q)。像你一样选择参数有时会彻底失败(密钥协议不起作用),但大多数时候只是不安全——本应保密的共享值很容易被对手确定并用于暴露和/或更改您所谓的安全数据。
但这对于 SO 来说是题外话。请参阅wikipedia和许多关于 security.SX 和 crypto.SX 的 Q 和 As。对于您的实际问题:
就是编码。对于(所有)JavaPublicKey
对象,getEncoded()
返回基于ASN.1和DER的 X.509 中定义的“SubjectPublicKeyInfo”格式(更方便地在 RFC5280 aka PKIX 中重复,链接在那里) 。特别是它包含一个将算法标识为 DH 的 OID,一个包含 p 和 g 的参数结构,以及包装在 BITSTRING 中的实际公钥值(通常表示为 y);对于您发布的值:
$ openssl asn1parse -i -dump -inform d <64790186.bin
0:d=0 hl=4 l= 419 cons: SEQUENCE
4:d=1 hl=4 l= 279 cons: SEQUENCE
8:d=2 hl=2 l= 9 prim: OBJECT :dhKeyAgreement
19:d=2 hl=4 l= 264 cons: SEQUENCE
23:d=3 hl=3 l= 129 prim: INTEGER :DE4779E7F4523CA143FFE102853E671CAAAB96203B1FC3C42D0EA1CB6878FCA889C79C709DDB1190DF9073050B1AD410D34A48A6E5A1D2C1854C471528DB3C4FE48A237FC86BAA777AAB8A17750DBA7948F258BD55E480BA3FFD87076BC4B0429CE731E31A8320DC594F9BD5022CD203C95D73F5B3E91C930A0AF2FA7AEE1605
155:d=3 hl=3 l= 129 prim: INTEGER :D719835971E8A91980141201FF765392A0049841142A3C203862AF8FFBC719528F142706639BD0C614EBA72660876F5A7011B5FC08224824577324FCF847648F24A600F408BED17770AAF958CC75076164DAA5E6179BFC573F40E2B086FC18A48B67A10F7B9B7C037A7BEEEDF554764CC8653C09AA3D330CC3C30F89616D8107
287:d=1 hl=3 l= 133 prim: BIT STRING
0000 - 00 02 81 81 00 8e e0 27-b9 16 fc 87 be 26 27 cf .......'.....&'.
0010 - b5 3f 4d a7 66 93 a0 6e-ec ac 8d a2 a6 b9 15 5c .?M.f..n.......\
0020 - 66 d6 0b cd 99 77 a8 11-b3 73 2f 72 88 0b de 1a f....w...s/r....
0030 - a2 59 73 1f e3 7a d4 28-49 09 48 17 77 44 4f 7a .Ys..z.(I.H.wDOz
0040 - 3c 5b cf 7f 28 7a d5 f0-5b e4 5f 45 53 cc 06 d5 <[..(z..[._ES...
0050 - 99 e7 e3 ba d6 73 6d 6b-ca 59 ea d8 b0 f6 c0 fe .....smk.Y......
0060 - 98 0f 47 13 04 ac 26 00-a6 77 a7 0c e4 6f 28 35 ..G...&..w...o(5
0070 - fa 67 97 d1 8f aa 8a 23-75 73 91 6e 60 4a f4 0c .g.....#us.n`J..
0080 - a4 56 cc ee 1e .V...
# if you know DER you can see the value in the BITSTRING is
# the encoding of a 128-octet INTEGER, like the two in the parameters structure
nodejscrypto
虽然它实际上在内部使用了支持 X.509/PKIX SPKI 格式的 OpenSSL,但不使用该格式。它只返回实际的公钥值 (y)。要创建与 Java 相同的标准格式,请执行以下操作:
const crypto = require('crypto');
function der(tag,val){ // for basic tags and up to 64kB, which are enough here
var len = val.length;
var enc = Buffer.alloc(4); enc[0]=tag;
if( len < 128 ){ enc[1]=len; enc = enc.slice(0,2); }
else if(len < 256 ){ enc[1]=0x81; enc[2]=len; enc = enc.slice(0,3); }
else{ enc[1]=0x82; enc[2]=len>>8; enc[3]=len&0xFF; }
return Buffer.concat([enc,val]);
}
function derpint(x){ return der(0x02, x[0]<128? x: Buffer.concat([onezero,x])); }
const onezero = Buffer.alloc(1,0);
function derseq(x){ return der(0x30, Buffer.concat(x)); }
const oidpkcs3 = Buffer.from('06092a864886f70d010301','hex');
var dh = crypto.createDiffieHellman(1024);
var pub = dh.generateKeys();
var p = dh.getPrime(), g = dh.getGenerator();
var algid = derseq([oidpkcs3,derseq([derpint(p),derpint(g)])]);
var spki = derseq([algid,der(0x03,Buffer.concat([onezero,derpint(pub)]))]);
console.log(spki.toString('hex'));
(补充)虽然它不在问题中,但堆栈政策是问题应该在问题中,而不是在评论中进行长时间的讨论,对其他人有用:
对于私钥, Java 类似地使用通用格式,但在同一个 Javadoc 页面上声明的不同Key
通用格式,即来自 PKCS8 的 PrivateKeyInfo更方便地用作 RFC5208。这有一个版本号,与 SPKI 相同的 algid(OID+参数),以及包装在 OCTETSTRING 而不是 BITSTRING 中的私有值(标记为 x),因此替换或添加到上面的最后两行:
var prv = dh.getPrivateKey();
var pkcs8 = derseq([derpint(onezero),algid,der(0x04,derpint(prv))]);
console.log(pkcs8.toString('hex'));
推荐阅读
- python - 递归查找 Linux 目录中的所有 Windows 可执行文件
- javascript - 为什么有两个模态的网页不起作用,第一个我点击 make frizze
- amazon-web-services - 在本地调用函数时,AWS SAM CLI 无法访问 Dynamo DB
- c# - 无论如何我可以测量表面上的勒克斯水平(照度水平)吗?
- go - 回声框架上的 {"message":"Method Not Allowed"}
- java - 在 Java 中创建字符串路径以读取文本文件
- javascript - 如何在另一个位置的对象数组中添加对象?
- python - 如何将来自用户的许多输入存储在集合中
- c - 使用 MinGW 为 Windows 编译 C
- java - 如何修复此 Date 类,以使日期在创建新 Date 实例时启动后不能更改?