为什么 nodejs 加密签名功能只接受 privateKey pem 格式?

Joh*_*ohn 2 javascript openssl cryptography elliptic-curve node.js

首先,我们使用椭圆加密包。符号函数如下所示:

signByPriv = function (privKeyData, text) {
    let msgHash = getmsgHash(text, "SHA-384");
    let key = ec.keyFromPrivate(Buffer.from(privKeyData,'base64').toString('hex'), 'hex')
    let signature = key.sign(msgHash);
    return signature
}
Run Code Online (Sandbox Code Playgroud)

然后我们想把它改成 nodejs 版本,因为 nodejs 在引擎盖下使用 openssl?所以它会更快

首先我的符号功能如下:

signByPriv = function (privKeyData, text) {
    const sign1 = crypto.createSign('SHA384');  //hash do inside
    sign1.write(text);
    sign1.end();
    const signature = sign1.sign(privKeyData, 'hex');
    return signature;
}
Run Code Online (Sandbox Code Playgroud)

它会抱怨错误:

internal/crypto/sig.js:86 const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,

错误:错误:0909006C:PEM 例程:get_name:无起始行

所以我检查了nodejs docs,发现它需要以pem格式传递privKey?

signByPriv = function (privKeyData, text) {
    let key = turnBase64PrivToPemKey(privKeyData) //base64 => pem
    const sign1 = crypto.createSign('SHA384');  //hash do inside
    sign1.write(text);
    sign1.end();
    const signature = sign1.sign(privKeyData, 'hex');
    return signature;
}
turnBase64PrivToPemKey = function (base64Priv) {
    var key_hex = Buffer.from(base64Priv, 'base64').toString('hex');
    ecdh.setPrivateKey(key_hex, 'hex')
    var pubKey_hex = ecdh.getPublicKey().toString('hex');
    //pem???????????????????????==????????????
    var mykey = '308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420' + key_hex + 'a144034200' + pubKey_hex;
    privKey = '-----BEGIN PRIVATE KEY-----\n' + Buffer.from(mykey, 'hex').toString('base64') + '\n-----END PRIVATE KEY-----';
    pubKey = crypto.createPublicKey(privKey); //???????
    let Key = {
        privKey,
        pubKey
    }
    return Key;
}
Run Code Online (Sandbox Code Playgroud)

太好了,签名和验证功能都完美无缺?

但后端可能会做同样愚蠢的事情......

我们选择的曲线是 prime256v1

const ecdh = crypto.createECDH('prime256v1')

所以,我想知道为什么 nodejs sign func 不能只接受 base64 priv?

因为pem格式只由私钥、公钥等固定字符串组成。

Top*_*aco 8

Sign并且Verify只是支持PEM格式,而且还DER格式(以两者中描述的答案@Maarten Bodewes的)。此外,Pkcs8-(RFC 5208此处)和 Sec1-EC-keys(SECG SEC1,C.4 节此处)都可以使用。但是,不直接支持原始EC 密钥。因此,如果密钥仅可用作原始密钥,则始终需要进行转换。但是这种转换比发布的代码更容易实现,因此(在我看来)没有显着的额外工作,例如签名:

var buf1 = Buffer.from('308141020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420', 'hex'); // specific byte-sequence for curve prime256v1
var buf2 = Buffer.from('<Raw private key as hex string>', 'hex'); // raw private key (32 bytes)
var privateKeyPkcs8Der = Buffer.concat([buf1, buf2], buf1.length + buf2.length);
var sign = crypto.createSign('SHA384');
sign.write(<data to sign>);
sign.end();
var signature = sign.sign({ key: privateKeyPkcs8Der, format: 'der', type: 'pkcs8' }); // specify format and type
Run Code Online (Sandbox Code Playgroud)

用于签名privateKeyPkcs8Der的密钥是 DER 格式的 Pkcs8 密钥(没有原始公钥)。

与发布的代码相反:

  • 使用 DER 格式而不是 PEM 格式。
  • 只有原始私钥嵌入到 Pkcs8 容器中,而不是原始公钥。
  • buf1包含属于 prime256v1 (secp256r1) 的字节序列和没有原始公钥的 Pkcs8 容器。 注意:字节序列与发布代码中使用的字节序列略有不同。这是因为字节序列还包含长度信息,这取决于是否嵌入了原始公钥。

这同样适用于验证:

var buf1 = Buffer.from('3059301306072a8648ce3d020106082a8648ce3d030107034200', 'hex'); // specific byte-sequence for curve prime256v1
var buf2 = Buffer.from('<Raw public key as hex string>', 'hex'); // raw public key (uncompressed, 65 bytes, startting with 04)
var publicKeyX509Der = Buffer.concat([buf1, buf2], buf1.length + buf2.length);
var verify = crypto.createVerify('SHA384');    
verify.write(<data to sign>);
verify.end();
var verified = verify.verify({ key: publicKeyX509Der, format: 'der', type: 'spki' }, signature); // specify format and type
Run Code Online (Sandbox Code Playgroud)

用于验证的密钥publicKeyX509Der是DER 格式的 X.509-SubjectPublicKeyInfo 密钥(SECG SEC1,C.3 节)。

与签名一样:

  • 使用 DER 格式而不是 PEM 格式。
  • buf1 包含属于 prime256v1 的字节序列。

在发布的代码中,ECDH-class的方法用于从原始私钥派生原始公钥。相反,createPublicKey- 和export- 方法可用于从 Pkcs8 密钥派生 X.509-SubjectPublicKeyInfo 密钥:

var publicKey = crypto.createPublicKey({ key: privKeyPkcs8DER, type: 'pkcs8', format: 'der' });
var publicKeyX509Der = publicKey.export({type: 'spki', format: 'der'})
Run Code Online (Sandbox Code Playgroud)

privateKeyPkcs8Der是一个 Pkcs8 密钥(带有带有原始公钥)和publicKeyX509Der一个 X.509-SubjectPublicKeyInfo 密钥,均为 DER 格式。


注意:也可以使用 Sec1 容器代替 Pkcs8 容器。但是,密钥的结构和字节序列必须相应地进行调整。此处描述了 Sec1 容器的使用,但对于不同的曲线 (secp256 k 1),因此不能简单地复制字节序列。