在Node.js中使用公钥加密数据

57
我需要使用公钥(.pem文件)加密一个字符串,然后使用私钥(同样是.pem文件)对其进行签名。
我已经成功加载了.pem文件:
publicCert = fs.readFileSync(publicCertFile).toString();

但是经过数小时的在Google上搜索后,我似乎找不到一种使用公钥加密数据的方法。在PHP中,我只需要调用 openssl_public_encrypt() 函数,但是我在Node.js或任何模块中都没有看到相应的函数。

6个回答

155

不需要使用库。输入crypto

以下是一个有点瑕疵的小模块,您可以使用RSA密钥加密/解密字符串:

var crypto = require("crypto");
var path = require("path");
var fs = require("fs");

var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
    var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
    var publicKey = fs.readFileSync(absolutePath, "utf8");
    var buffer = Buffer.from(toEncrypt);
    var encrypted = crypto.publicEncrypt(publicKey, buffer);
    return encrypted.toString("base64");
};

var decryptStringWithRsaPrivateKey = function(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
    var absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey);
    var privateKey = fs.readFileSync(absolutePath, "utf8");
    var buffer = Buffer.from(toDecrypt, "base64");
    var decrypted = crypto.privateDecrypt(privateKey, buffer);
    return decrypted.toString("utf8");
};

module.exports = {
    encryptStringWithRsaPublicKey: encryptStringWithRsaPublicKey,
    decryptStringWithRsaPrivateKey: decryptStringWithRsaPrivateKey
}

我建议尽可能不使用同步的fs方法,可以使用promises来改进,但对于简单的用例,这是我见过的可行的方法。


10
值得一提的是,你只能使用公钥加密小量(最多245字节)的数据,对于更大的数据,你应该使用AES256算法,并使用公私配对加密的方式来保护私钥。请参考此回答:https://security.stackexchange.com/questions/33434/rsa-maximum-bytes-to-encrypt-comparison-to-aes-in-terms-of-security - jmc
1
太棒了 :D 只有一件事,密钥文件应该是 .pem 格式的:openssl rsa -in ~/.ssh/id_rsa -outform pem > id_rsa.pem - Bodhi Hu
1
如果privateKey是使用密码編碼和加密的,那該怎麼辦呢?我找不到crypto.privateDecrypt()的正確方法調用。 - maroodb
@maroodb 使用openssl密钥生成工具(https://sourceforge.net/p/gnuwin32/)生成您的公钥和私钥,并运行这些命令(https://rietta.com/blog/2012/01/27/openssl-generating-rsa-key-from-command/)创建一对public.pem和private.pem文件,并将这些文件传递给crypto.privateDecrypt()函数。 - Prabhat Mishra
@Prabhat Mishra,我不是在问如何生成密钥 ;) - maroodb
显示剩余7条评论

34

我在Node.js 10中测试了这个,你可以使用加密/解密函数(在Jacob的回答上进行小修改):

const crypto = require('crypto')
const path = require('path')
const fs = require('fs')

function encrypt(toEncrypt, relativeOrAbsolutePathToPublicKey) {
  const absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey)
  const publicKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toEncrypt, 'utf8')
  const encrypted = crypto.publicEncrypt(publicKey, buffer)
  return encrypted.toString('base64')
}

function decrypt(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
  const absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey)
  const privateKey = fs.readFileSync(absolutePath, 'utf8')
  const buffer = Buffer.from(toDecrypt, 'base64')
  const decrypted = crypto.privateDecrypt(
    {
      key: privateKey.toString(),
      passphrase: '',
    },
    buffer,
  )
  return decrypted.toString('utf8')
}

const enc = encrypt('hello', `public.pem`)
console.log('enc', enc)

const dec = decrypt(enc, `private.pem`)
console.log('dec', dec)

您可以使用以下方法生成密钥:

const { writeFileSync } = require('fs')
const { generateKeyPairSync } = require('crypto')

function generateKeys() {
  const { privateKey, publicKey } = generateKeyPairSync('rsa', {
    modulusLength: 4096,
    publicKeyEncoding: {
      type: 'pkcs1',
      format: 'pem',
    },
    privateKeyEncoding: {
      type: 'pkcs1',
      format: 'pem',
      cipher: 'aes-256-cbc',
      passphrase: '',
    },
  })

  writeFileSync('private.pem', privateKey)
  writeFileSync('public.pem', publicKey)
}

我花了好几天的时间,最终找到了一个解释为什么这在俄语中不起作用的方法,所以我想在这里添加一下:http://qaru.site/questions/16043509/nodejs-cryptopublicencrypt-error-error0906d06cpem-routinespemreadbiono-start-line。它指出publicEncrypt使用openssl,只支持RSA,因此不能使用crypto.createECDH secp384r1,否则会在publicEncrypt中引发“unhandledRejection Error: error:0906D06C:PEM routines:PEM_read_bio:no start line”的错误。有没有建议如何使用更高位数的椭圆曲线密钥?还注意到Chrome也不支持EC 512。这是截至nodejs 11.6的情况,详情请参见nodejs.org/api/crypto.html。 - Master James
同时,当尝试从Node将某些内容加密到浏览器时,需要在浏览器中生成密钥,并将公钥导入到Node中。 - Master James
谢谢您的回答,这让我学会了如何使用JSON对象将“passphrase”传递给privateDecrypt函数。 - kaushalop

8
更新后的公共/私有解密和加密模块是 URSA。node-rsa模块已过时。
此Node模块为OpenSSL的RSA公共/私有密钥加密功能提供了一个相当完整的封装。
npm install ursa

4
Ursa已经有一段时间没有维护了。这些更新的实现可能会有所帮助:https://github.com/tracker1/cryptico-js和https://github.com/rzcoder/node-rsa。 - Andrew Eddie

7

1
也许我需要更熟悉RSA加密。我已经阅读了密码学文档十几遍,试图找到我所需的内容,但是没有找到任何东西。你说createCipheriv()会做我需要的事情,但我甚至不知道"iv"是什么。我猜这是因为在PHP和其他语言中更抽象。我会尝试使用该函数并查看是否可以使其正常工作。 - Clint
1
阅读更多有关createCipheriv的内容后,我发现它不是非对称加密(公钥/私钥加密)。我认为它不能满足我的需求。Crypto确实可以使用私钥对加密字符串进行签名,这让我想知道为什么我不能使用公钥进行加密。这似乎很奇怪,否则我可能完全错过了什么。 - Clint
iv是一个初始化向量。http://zh.wikipedia.org/wiki/Initialization_vector - Peter Lyons
我没有看到你编辑了你的答案。那看起来实际上可能会起作用!我会测试一下看看。 - Clint

5
TL;DR: URSA是您的最佳选择。很奇怪这不是Node.js的标准crypto

我发现其他所有解决方案都无法在Windows上运行,或者实际上并不是加密库。Louie推荐的URSA看起来是最好的选择。如果您不关心Windows,那么您就更加完美。

关于Ursa的注意事项:为了使npm安装正常工作,我必须安装OpenSSL以及称为“Visual C++ 2008 Redistributables”的东西。在此获取该垃圾:http://slproweb.com/products/Win32OpenSSL.html

分解:

非加密库

这就是我能找到的全部内容。


1
node-rsa不再依赖于node-waf。它兼容浏览器。 - 131

3

是时候更新了吗? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接