Nodejs Crypto ECDH PublicKey Hex as X.509

Posted

技术标签:

【中文标题】Nodejs Crypto ECDH PublicKey Hex as X.509【英文标题】: 【发布时间】:2018-09-08 07:22:27 【问题描述】:

我正在使用prime256v1 曲线生成密钥对并使用nodejs 和默认crypto 模块进行签名。

使用crypto

let crypto = require('crypto');
let e = crypto.createECDH('prime256v1');
e.generateKeys();
privateKey = e.getPrivateKey();
privateKeyHex = privateKey.toString('hex');
publicKey = e.getPublicKey();
publicKeyHex = publicKey.toString('hex');

我获得了一个类似于以下十六进制字符串的公钥:

'049a6b0ac242afe41128cf59736412686ca83c9e902ee3fa0f13810b9d59ebfe5e49204427c23b630be12ae33815b0bda6ed8d0603386c6ea5f1906cdb0e731286'

签名jsrsasign

let jsrsa = require('jsrsasign');
let KEYUTIL = jsrsa.KEYUTIL;
let kp = KEYUTIL.generateKeypair("EC", "prime256v1");
let pkHex = kp.pubKeyObj.pubKeyHex

返回

'04f36e41189420db05dd8a73e3cb310b0c55809190bdedd89bf19769ac8df3cd06c1380f646e9e65e31c24affff79e43516b37e0186c3753cfdfd29894c2becc84'

在 Java 中将 PublicKey Hex 转换为 PublicKey 对象

我想使用这些 publicKeys 并将其转换为 java 中的 PublicKey 对象。使用EC KeyFactory,我将十六进制转换为byte[],并尝试在Java 中构造PublicKey 对象,该对象需要X.509 格式编码。

public PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException 
    EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
    KeyFactory kf = KeyFactory.getInstance("EC");
    PublicKey pub = kf.generatePublic(publicKeySpec);
    return pub;

要将十六进制字符串转换为 byte[],我使用以下代码:

public byte[] hexStringToByteArray(String hexString) 
    byte[] bytes = new byte[hexString.length() / 2];

    for(int i = 0; i < hexString.length(); i += 2) 
        String sub = hexString.substring(i, i + 2);
        Integer intVal = Integer.parseInt(sub, 16);
        bytes[i / 2] = intVal.byteValue();
        String hex = "".format("0x%x", bytes[i / 2]);
    
    return bytes;

尝试使用以下测试用例执行相同操作会导致InvalidKeySpecException

@Test
public void pkConversionTest() throws NoSuchAlgorithmException, InvalidKeySpecException 
    ECDSA.setDebug(true);
    byte[] pk = hexStringToByteArray("049a6b0ac242afe41128cf59736412686ca83c9e902ee3fa0f13810b9d59ebfe5e49204427c23b630be12ae33815b0bda6ed8d0603386c6ea5f1906cdb0e731286");
    PublicKey pub = ECDSA.getPublicKey(pk);
    System.out.println(pub);

返回

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=26, too big.

但是,我可以使用java 生成KeyPair,并使用通过nodejs 获得的publicKey hex 来执行签名verify。从 java 生成的示例 publickey hex 如下所示:

3059301306072a8648ce3d020106082a8648ce3d0301070342000425a321d5a1a74e6c04a6e3cab030401f3dbc04d5242f9bc629175c3d3988799175eb80cd96d7e76ea924630a8d86b93c54dec7cb965b58de31705eb3343846a1

如何将 nodejs 生成的 publicKey 格式化为 X.509 格式,以便在 java 端使用?

编辑:

3059301306072a8648ce3d020106082a8648ce3d030107034200 似乎是使用java 生成的公钥十六进制的通用前缀。通过Prefix 将其转换为使用nodejs 获得的PublicKey 的十六进制值,因为长度更小似乎可以解决问题。但是有人可以解释为什么吗?

谢谢。

【问题讨论】:

【参考方案1】:

但有人能解释一下原因吗?

Java 将公钥编码为“X.509”格式或更确切地说是 X.509/PKIX 定义的 SubjectPublicKeyInfo 结构 (SPKI);请参阅 rfc5280、rfc3279 和 ECC,特别是 rfc5480。这就是您传递给密钥工厂的数据位于名为X509EncodedKeySpec 的类中的原因。此 ASN.1 结构包含一个AlgorithmIdentifier,它标识所使用的算法及其参数(对于 ECC,它是所使用的曲线/组,在您的情况下是标识 prime256 aka P-256 aka secp256r1 的 OID)加上一个 BIT STRING 类型,其中包含实际编码的公钥值(ECC 是 X9.62 格式的点,它有几个变体;这里你使用的是未压缩的;根据the docnodejs.crypto 也支持压缩)。

您的“前缀”是 ASN.1 外部 SEQUENCE、AlgorithmIdentifier 和标记长度和填充计数的 DER 编码,它们开始 BIT STRING 以包含公钥点。

基本上是骗人的: * How can I get a PublicKey object from EC public key bytes? * Loading raw 64-byte long ECDSA public key in Java(Maarten 的回答实际上就是你所做的) * How can I generate a valid ECDSA EC key pair?(披露:我的)

仅供参考:实际上同样的问题也发生在 RSA 上,并且有更多的问题。 与特定于算法的格式相比,通用 PKCS8 格式的私钥也存在类似问题,但由于公钥通常与其他系统和/或程序交换,而私钥通常不是私钥编码的互操作性,因此问题较少。

【讨论】:

以上是关于Nodejs Crypto ECDH PublicKey Hex as X.509的主要内容,如果未能解决你的问题,请参考以下文章

升级到OTP 18打破了public_key库的使用

nodeJS之crypto加密

nodeJS之crypto加密

nodeJS之crypto模块md5和Hmac加密

nodejs -- crypto MD5签名

nodeJS之crypto模块公钥加密及解密