Nodejs加密ECDH公钥十六进制为X.509

时间:2018-09-08 07:22:28

标签: java node.js cryptography ecdsa

我正在使用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'

Usign jsrsasign

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

返回

'04f36e41189420db05dd8a73e3cb310b0c55809190bdedd89bf19769ac8df3cd06c1380f646e9e65e31c24affff79e43516b37e0186c3753cfdfd29894c2becc84'

在Java中将PublicKey十六进制转换为PublicKey对象

我想使用这些publicKeys并将其转换为PublicKey中的java对象。使用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.

但是,我能够使用KeyPair生成java并使用通过nodejs获得的publicKey十六进制来执行签名verify。从Java生成的示例十六进制公钥如下:

3059301306072a8648ce3d020106082a8648ce3d0301070342000425a321d5a1a74e6c04a6e3cab030401f3dbc04d5242f9bc629175c3d3988799175eb80cd96d7e76ea924630a8d86b93c54dec7cb965b58de31705eb3343846a1

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

编辑:

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

谢谢。

1 个答案:

答案 0 :(得分:1)

  

但是有人可以解释为什么吗?

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

您的“前缀”是ASN.1外部SEQUENCE的DER编码,AlgorithmIdentifier以及标记长度和填充数,它们开始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?(公开:我的)

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