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

why nodejs crypto sign function only accept privateKey pem format?

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

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
}

然后我们想把它改成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;
}

会报错:

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

Error: error:0909006C:PEM routines:get_name:no start line

于是查看了nodejs文档,发现需要传递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;
}

太棒了,签名和验证功能都很完美。

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

the curve we chose is prime256v1

const ecdh = crypto.createECDH('prime256v1')

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

因为pem格式只由私钥,public密钥和其他固定字符串组成。

PEM 是由 OpenSSL 处理的标准。它定义了 header 行、一个页脚行和一种特定类型的 base 64 以及处理数据的方式。 header 行和页脚行也清楚地标识了 base 64 编码的数据类型。它最初主要用于邮件; PEM 表示隐私增强邮件。

所以这些都是优于base 64的优势。你可以清楚地找到PEM的开始和结束,PEM中包含的object的类型等。还要注意PEM早于任何官方据我所知,base 64 的标准。它当然在那之前就存在了,但是每个需要编码/解码的标准都只是在内部 re-defined 它。

是的,这就是 OpenSSL 开始使用 PEM 的方式,而您提到的 NodeJS 使用它。


请注意,许多库还包含 base 64 解码器。 base 64 中通常有一种称为 DER 的二进制格式,其中包含由 ASN.1 数据描述语言描述的数据。如果您的库可以处理 DER,那么您可以自己简单地解码 base 64 并在您的密码库中使用 ASN.1 功能。 OpenSSL 命令行可以通过 base64 命令和 -inform DER 开关来做到这一点。

Sign and Verify don't only support the PEM-format, but also the DER-format (both described in the of @Maarten Bodewes). Furthermore, both Pkcs8- (RFC 5208 and ) and Sec1-EC-keys (SECG SEC1, section C.4 and here)即可。但是,不直接支持 raw EC 密钥。因此,如果密钥仅可用作原始密钥,则始终需要进行转换。但是这种转换比发布的代码更容易实现,所以(在我看来)没有 significant 额外的努力,例如签名:

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

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

与发布的代码对比:

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

同样适用于验证:

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

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

与签名一样:

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

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

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

这里 privateKeyPkcs8Der 是一个 Pkcs8 密钥(with or without raw public key)和publicKeyX509Der 是一个 X.509-SubjectPublicKeyInfo-key,都是 DER 格式。


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