例如,假设我们不想以明文形式存储用户的真实姓名,以防万一有人获取了数据库访问权限。虽然这是一些敏感数据,但有时也需要显示为明文。那么,有没有一种简单的方法来解决这个问题呢?
var crypto = require('crypto');
var assert = require('assert');
var algorithm = 'aes256'; // or any other algorithm supported by OpenSSL
var key = 'password';
var text = 'I love kittens';
var cipher = crypto.createCipher(algorithm, key);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
var decipher = crypto.createDecipher(algorithm, key);
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
assert.equal(decrypted, text);
编辑
现在,createCipher和createDecipher已被弃用,改用createCipheriv和createDecipheriv。
crypto
来实现? - FizzixcreateCipher
和createDecipher
被弃用,这个解决方案就无法工作。它们需要更多的参数,如IV
。 - Willian2019年12月12日更新:
GCM模式不像其他模式(如CBC),不要求IV值是不可预测的。唯一要求的是,使用给定密钥时,每次调用都必须使用唯一的IV。如果为给定密钥重复使用IV,则会导致安全性被破坏。下面展示了一种简单实现这一要求的方法,即使用一个强伪随机数生成器生成随机IV。
使用序列或时间戳作为IV也是可以的,但实现起来可能并非像听起来那么简单。例如,如果系统没有正确跟踪在持久存储中已使用的IV序列,则在系统重新启动后,会有调用重复使用IV的风险。同样,也不存在完美的时钟,计算机时钟会重新调整等等。
另外,应该在每2^32次调用后轮换密钥。关于IV要求的更多细节,请参考此答案和NIST建议。
2019年7月30日更新:
鉴于本答案的点击量和投票数不断增加,我认为有必要提到下面的代码使用了一个同步方法——crypto.scryptSync
。如果加密或解密是在应用程序初始化期间完成,则没有问题。否则,请考虑使用该函数的异步版本以避免阻塞事件循环(如使用bluebird
等Promise库)。
2019年1月23日更新:
修复了解密逻辑中的错误。感谢@AlexisWilke正确指出这一点。
接受的答案已经有7年历史,在今天看来不再安全。因此,我来回答:
加密算法:使用256位密钥的块密码AES被视为足够安全。要加密完整的消息,需要选择一种模式。建议使用认证加密(同时提供机密性和完整性)。GCM、CCM和EAX是最常用的认证加密模式。GCM通常被优先选择,并且它在提供专门用于GCM的指令的Intel架构中表现良好。所有这三种模式都基于CTR(计数器)模式,因此它们不需要填充。因此,它们不易受到与填充相关的攻击。
需要使用IV来加密GCM。IV不是秘密。唯一的要求是它必须是随机或不可预测的。在NodeJs中,crypto.randomBytes()
用于生成具有密码学强度的伪随机数。
NIST建议使用96位IV以促进互操作性、效率和设计的简单性。
接收者需要知道IV才能解密密文。因此,IV需要与密文一起传输。一些实现将IV作为AD(相关数据)发送,这意味着认证标记将计算密文和IV的内容。但是,这并非必需。IV可以简单地添加到密文前面,因为如果在传输过程中由于恶意攻击或网络/文件系统错误更改了IV,则认证标记验证
为避免敏感信息被泄露,不应使用字符串来保存明文消息、密码或密钥,因为字符串是不可变的,这意味着我们无法在使用后清除字符串,它们将在内存中残留。因此,内存转储可以揭示敏感信息。出于同样的原因,调用这些加密或解密方法的客户端应该使用bufferVal.fill(0)
清除所有保存消息、密钥或密码的Buffer
,一旦不再需要。
最后,在网络传输或存储时,密文应使用Base64编码。可以使用buffer.toString('base64');
将Buffer
转换为Base64编码的字符串。
请注意,从密码派生密钥的关键导出函数scrypt (crypto.scryptSync()
) 仅适用于Node 10.*及更高版本。
代码如下:
const crypto = require('crypto');
var exports = module.exports = {};
const ALGORITHM = {
/**
* GCM is an authenticated encryption mode that
* not only provides confidentiality but also
* provides integrity in a secured way
* */
BLOCK_CIPHER: 'aes-256-gcm',
/**
* 128 bit auth tag is recommended for GCM
*/
AUTH_TAG_BYTE_LEN: 16,
/**
* NIST recommends 96 bits or 12 bytes IV for GCM
* to promote interoperability, efficiency, and
* simplicity of design
*/
IV_BYTE_LEN: 12,
/**
* Note: 256 (in algorithm name) is key size.
* Block size for AES is always 128
*/
KEY_BYTE_LEN: 32,
/**
* To prevent rainbow table attacks
* */
SALT_BYTE_LEN: 16
}
const getIV = () => crypto.randomBytes(ALGORITHM.IV_BYTE_LEN);
exports.getRandomKey = getRandomKey = () => crypto.randomBytes(ALGORITHM.KEY_BYTE_LEN);
/**
* To prevent rainbow table attacks
* */
exports.getSalt = getSalt = () => crypto.randomBytes(ALGORITHM.SALT_BYTE_LEN);
/**
*
* @param {Buffer} password - The password to be used for generating key
*
* To be used when key needs to be generated based on password.
* The caller of this function has the responsibility to clear
* the Buffer after the key generation to prevent the password
* from lingering in the memory
*/
exports.getKeyFromPassword = getKeyFromPassword = (password, salt) => {
return crypto.scryptSync(password, salt, ALGORITHM.KEY_BYTE_LEN);
}
/**
*
* @param {Buffer} messagetext - The clear text message to be encrypted
* @param {Buffer} key - The key to be used for encryption
*
* The caller of this function has the responsibility to clear
* the Buffer after the encryption to prevent the message text
* and the key from lingering in the memory
*/
exports.encrypt = encrypt = (messagetext, key) => {
const iv = getIV();
const cipher = crypto.createCipheriv(
ALGORITHM.BLOCK_CIPHER, key, iv,
{ 'authTagLength': ALGORITHM.AUTH_TAG_BYTE_LEN });
let encryptedMessage = cipher.update(messagetext);
encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()]);
return Buffer.concat([iv, encryptedMessage, cipher.getAuthTag()]);
}
/**
*
* @param {Buffer} ciphertext - Cipher text
* @param {Buffer} key - The key to be used for decryption
*
* The caller of this function has the responsibility to clear
* the Buffer after the decryption to prevent the message text
* and the key from lingering in the memory
*/
exports.decrypt = decrypt = (ciphertext, key) => {
const authTag = ciphertext.slice(-16);
const iv = ciphertext.slice(0, 12);
const encryptedMessage = ciphertext.slice(12, -16);
const decipher = crypto.createDecipheriv(
ALGORITHM.BLOCK_CIPHER, key, iv,
{ 'authTagLength': ALGORITHM.AUTH_TAG_BYTE_LEN });
decipher.setAuthTag(authTag);
let messagetext = decipher.update(encryptedMessage);
messagetext = Buffer.concat([messagetext, decipher.final()]);
return messagetext;
}
以下还提供了单元测试:
const assert = require('assert');
const cryptoUtils = require('../lib/crypto_utils');
describe('CryptoUtils', function() {
describe('decrypt()', function() {
it('should return the same mesage text after decryption of text encrypted with a '
+ 'randomly generated key', function() {
let plaintext = 'my message text';
let key = cryptoUtils.getRandomKey();
let ciphertext = cryptoUtils.encrypt(plaintext, key);
let decryptOutput = cryptoUtils.decrypt(ciphertext, key);
assert.equal(decryptOutput.toString('utf8'), plaintext);
});
it('should return the same mesage text after decryption of text excrypted with a '
+ 'key generated from a password', function() {
let plaintext = 'my message text';
/**
* Ideally the password would be read from a file and will be in a Buffer
*/
let key = cryptoUtils.getKeyFromPassword(
Buffer.from('mysecretpassword'), cryptoUtils.getSalt());
let ciphertext = cryptoUtils.encrypt(plaintext, key);
let decryptOutput = cryptoUtils.decrypt(ciphertext, key);
assert.equal(decryptOutput.toString('utf8'), plaintext);
});
});
});
decrypt()
函数有一个 bug,因为你没有对 decipher.final()
的输出做任何处理。应该将其连接起来,对吗? - Alexis WilkeBLOCK_CIPHER:'aes-256-gcm' as crypto.CipherCCMTypes,
这应该可以解决您的问题 :) - pa1nd对@mak答案的更新,crypto.createCipher
和crypto.createDecipher
已被弃用。最新可用的代码如下:
var crypto = require("crypto");
var algorithm = "aes-192-cbc"; //algorithm to use
var secret = "your-secret-key";
const key = crypto.scryptSync(secret, 'salt', 24); //create key
var text= "this is the text to be encrypted"; //text to be encrypted
const iv = crypto.randomBytes(16); // generate different ciphertext everytime
const cipher = crypto.createCipheriv(algorithm, key, iv);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex'); // encrypted text
const decipher = crypto.createDecipheriv(algorithm, key, iv);
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8'); //deciphered text
console.log(decrypted);
key
生成的。 - Bhumij Guptaiv = Buffer.alloc(...)
可以改为 iv = crypto.randomBytes(16)
,每次都会生成不同的哈希值,从而防御任何彩虹表攻击。 - roshnet加密
和iv
以供以后解密使用,那么您还需要存储authTag
。通过authTag = cipher.getAuthTag()
获取它,并通过decipher.setAuthTag(authTag)
应用它。如果没有这个步骤,我在decipher.final('utf8')
上会失败。请参见:https://github.com/nodejs/help/issues/1034 - WGriffing虽然已经有正确的答案了,但是一个好的模式是在类包装器中使用加密库,这个模式我已经复制/粘贴多年,用于各种项目。
const crypto = require("crypto");
class Encrypter {
constructor(encryptionKey) {
this.algorithm = "aes-192-cbc";
this.key = crypto.scryptSync(encryptionKey, "salt", 24);
}
encrypt(clearText) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
const encrypted = cipher.update(clearText, "utf8", "hex");
return [
encrypted + cipher.final("hex"),
Buffer.from(iv).toString("hex"),
].join("|");
}
dencrypt(encryptedText) {
const [encrypted, iv] = encryptedText.split("|");
if (!iv) throw new Error("IV not found");
const decipher = crypto.createDecipheriv(
this.algorithm,
this.key,
Buffer.from(iv, "hex")
);
return decipher.update(encrypted, "hex", "utf8") + decipher.final("utf8");
}
}
// Usage
const encrypter = new Encrypter("secret");
const clearText = "adventure time";
const encrypted = encrypter.encrypt(clearText);
const dencrypted = encrypter.dencrypt(encrypted);
console.log({ worked: clearText === dencrypted });
crypto.createCipher/2
(在顶部答案中使用)。 - expelledboydecrypt/1
函数中打印变量 encrypted
和 iv
进行调试。 - expelledboyaes256
,当我切换到aes-192-cbc
时它正常工作。根据我在文档中阅读的内容,我可以使用任何OpenSSL支持的方法。我需要为aes256
添加更多代码吗? - TheWhiteFangaes256
,您可能需要将 scryptSync 中指定的密钥长度更改为 32。 - CookieEater接受的答案是正确的,但有一些更改,因为 createCipher
和 createDecipher
已被弃用。
在新的方法中,createCipheriv
和 createDecipheriv
要求提供 iv 值,并且 iv 值长度必须为128位,key 必须为256位。
代码示例
const crypto = require('crypto');
const assert = require('assert');
let algorithm = 'aes256'; // or any other algorithm supported by OpenSSL
let key = 'ExchangePasswordPasswordExchange'; // or any key from .env
let text = 'I love kittens';
let iv = crypto.randomBytes(8).toString('hex'); // or you can add static value from .env
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
let decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
assert.equal(decrypted, text);
buffer
模块显式导入Buffer
let
变量转换为const
变量(或完全省略)module.exports
转换为单个对象exports.x = x = (...)
声明移动到module.exports
对象ALGORITHM
对象的文档const crypto = require("crypto");
const { Buffer } = require("buffer");
const ALGORITHM = {
// GCM is an authenticated encryption mode that not only provides confidentiality but also provides integrity in a secured way
BLOCK_CIPHER: "aes-256-gcm",
// 128 bit auth tag is recommended for GCM
AUTH_TAG_BYTE_LEN: 16,
// NIST recommends 96 bits or 12 bytes IV for GCM to promote interoperability, efficiency, and simplicity of design
IV_BYTE_LEN: 12,
// NOTE: 256 (in algorithm name) is key size (block size for AES is always 128)
KEY_BYTE_LEN: 32,
// to prevent rainbow table attacks
SALT_BYTE_LEN: 16
};
module.exports = {
getRandomKey() {
return crypto.randomBytes(ALGORITHM.KEY_BYTE_LEN);
},
// to prevent rainbow table attacks
getSalt() {
return crypto.randomBytes(ALGORITHM.SALT_BYTE_LEN);
},
/**
*
* @param {Buffer} password - The password to be used for generating key
*
* To be used when key needs to be generated based on password.
* The caller of this function has the responsibility to clear
* the Buffer after the key generation to prevent the password
* from lingering in the memory
*/
getKeyFromPassword(password, salt) {
return crypto.scryptSync(password, salt, ALGORITHM.KEY_BYTE_LEN);
},
/**
*
* @param {Buffer} messagetext - The clear text message to be encrypted
* @param {Buffer} key - The key to be used for encryption
*
* The caller of this function has the responsibility to clear
* the Buffer after the encryption to prevent the message text
* and the key from lingering in the memory
*/
encrypt(messagetext, key) {
const iv = crypto.randomBytes(ALGORITHM.IV_BYTE_LEN);
const cipher = crypto.createCipheriv(ALGORITHM.BLOCK_CIPHER, key, iv, {
authTagLength: ALGORITHM.AUTH_TAG_BYTE_LEN
});
let encryptedMessage = cipher.update(messagetext);
encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()]);
return Buffer.concat([iv, encryptedMessage, cipher.getAuthTag()]);
},
/**
*
* @param {Buffer} ciphertext - Cipher text
* @param {Buffer} key - The key to be used for decryption
*
* The caller of this function has the responsibility to clear
* the Buffer after the decryption to prevent the message text
* and the key from lingering in the memory
*/
decrypt(ciphertext, key) {
const authTag = ciphertext.slice(-16);
const iv = ciphertext.slice(0, 12);
const encryptedMessage = ciphertext.slice(12, -16);
const decipher = crypto.createDecipheriv(ALGORITHM.BLOCK_CIPHER, key, iv, {
authTagLength: ALGORITHM.AUTH_TAG_BYTE_LEN
});
decipher.setAuthTag(authTag);
const messagetext = decipher.update(encryptedMessage);
return Buffer.concat([messagetext, decipher.final()]);
}
};
最简单的方法是使用一个名为 cryptr 的包。
可以非常快速地完成,例如:
// npm install cryptr
const Cryptr = require('cryptr');
const cryptr = new Cryptr('myTotallySecretKey');
const encryptedString = cryptr.encrypt('bacon');
const decryptedString = cryptr.decrypt(encryptedString);
console.log(encryptedString); // 2a3260f5ac4754b8ee3021ad413ddbc11f04138d01fe0c5889a0dd7b4a97e342a4f43bb43f3c83033626a76f7ace2479705ec7579e4c151f2e2196455be09b29bfc9055f82cdc92a1fe735825af1f75cfb9c94ad765c06a8abe9668fca5c42d45a7ec233f0
console.log(decryptedString); // bacon
感谢 Maurice Butler 创建了这个库。
对于同一页中的多个值加密,我们需要创建单独的Cipheriv,如下所示,使用静态iv:
const iv = 'xxxx'; /* replace with your iv */
const cipher1 = crypto.createCipheriv(algorithm, key, iv);
var encrypted1 = cipher1.update(val1, 'utf8', 'hex') + cipher1.final('hex'); // encrypted text
const cipher2 = crypto.createCipheriv(algorithm, key, iv);
var encrypted2 = cipher2.update(val2, 'utf8', 'hex') + cipher2.final('hex'); // encrypted text
const cipher3 = crypto.createCipheriv(algorithm, key, iv);
var encrypted3 = cipher3.update(val3, 'utf8', 'hex') + cipher3.final('hex'); // encrypted text
Encrypt.ts
import * as crypto from "crypto";
export class Encrypter {
static algorithm = "aes256";
static key = crypto.scryptSync("<Your-Secret-Key>", "salt", 32);
static encrypt(clearText) {
const iv = crypto.randomBytes(16);
try {
const cipher = crypto.createCipheriv(
Encrypter.algorithm,
Encrypter.key,
iv
);
const encrypted = cipher.update(clearText, "utf8", "hex");
return [
encrypted + cipher.final("hex"),
Buffer.from(iv).toString("hex"),
].join("|");
} catch (error) {
return error;
}
}
static decrypt(encryptedText) {
try {
const [encrypted, iv] = encryptedText.split("|");
if (!iv) throw new Error("IV not found");
const decipher = crypto.createDecipheriv(
Encrypter.algorithm,
Encrypter.key,
Buffer.from(iv, "hex")
);
return decipher.update(encrypted, "hex", "utf8") + decipher.final("utf8");
} catch (error) {
return error;
}
}
}
使用方法:
//Encrypt
const encryptedPassword = Encrypter.encrypt("Password");
//Decrypt, Note: Here you need to provide encrypted value, to decrypt it
const decryptedPassword = Encrypter.decrypt(encryptedPassword);
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'RJ23edrf';
//Here "aes-256-cbc" is the advance encryption standard we are using for encryption.
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
var salt = uuid.v4()
var e = encrypt();
console.log(e);
var d = decrypt(e);
console.log(d);