Javascript node.js:加密需要解密的数据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6953286/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
node.js: encrypting data that needs to be decrypted?
提问by fancy
We are using bcrypt for passwords and data that never needs to be decrypted.
我们将 bcrypt 用于永远不需要解密的密码和数据。
What should do to protect other user information that does. For this example lets say that we didn't want a users real name to be in plain text in case someone was to obtain the db.
应该怎么做才能保护其他用户信息呢。对于这个例子,假设我们不希望用户的真实姓名为纯文本,以防有人获得数据库。
This is somewhat sensitive data but also needs to be called from time to time and displayed in plain text. Is there a simple way to do this?
这是有些敏感的数据,但也需要不时调用并以纯文本形式显示。有没有一种简单的方法可以做到这一点?
回答by mak
You can use the cryptomodule:
您可以使用加密模块:
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);
回答by Saptarshi Basu
Update on 12-DEC-2019
2019 年 12 月 12 日更新
Unlike some other modes like CBC, GCM mode does not require the IV to be unpredictable. The only requirement is that the IV has to be unique for each invocation with a given key. If it repeats once for a given key, security can be compromised. An easy way to achieve this is to use a random IV from a strong pseudo random number generator as shown below.
与 CBC 等其他一些模式不同,GCM 模式不需要 IV 不可预测。唯一的要求是对于使用给定键的每次调用,IV 必须是唯一的。如果对给定的密钥重复一次,则安全性可能会受到影响。实现此目的的一种简单方法是使用来自强伪随机数生成器的随机 IV,如下所示。
Using a sequence or timestamp as IV is also possible, but it may not be as trivial as it may sound. For example, if the system does not correctly keep track of the sequences already used as IV in a persistent store, an invocation may repeat an IV after a system reboot. Likewise, there is no perfect clock. Computer clocks readjusts etc.
使用序列或时间戳作为 IV 也是可能的,但它可能不像听起来那么简单。例如,如果系统没有正确跟踪已在持久存储中用作 IV 的序列,则在系统重新启动后调用可能会重复 IV。同样,没有完美的时钟。电脑时钟重新调整等。
Also, the key should be rotated after every 2^32 invocations. For further details on the IV requirement, refer to this answerand the NIST recommendations.
此外,应在每 2^32 次调用后轮换密钥。有关 IV 要求的更多详细信息,请参阅此答案和NIST 建议。
Update on 30-JUL-2019
2019 年 7 月 30 日更新
As the answer is getting more views and votes, I think it is worth mentioning that the code below has used a *Sync method - crypto.scryptSync
. Now that is fine if the encryption or decryption is done during application initialization. Otherwise, consider using the asynchronous version of the function to avoid blocking the event loop. (A promise library like bluebird
is useful).
由于答案获得了更多的观点和投票,我认为值得一提的是,下面的代码使用了 *Sync 方法 - crypto.scryptSync
。现在,如果在应用程序初始化期间完成加密或解密就可以了。否则,请考虑使用函数的异步版本以避免阻塞事件循环。(像这样的承诺库bluebird
很有用)。
Update on 23-JAN-2019
2019 年 1 月 23 日更新
The bug in decryption logic has been fixed. Thanks @AlexisWilke for rightly pointing it out.
解密逻辑中的错误已得到修复。感谢@AlexisWilke 正确指出。
The accepted answer is 7 years old and doesn't look secured today. Hence, I'm answering it:
接受的答案是 7 岁,今天看起来并不安全。因此,我是这样回答的:
Encryption Algorithm: Block cipher AES with 256 bits key is considered secure enough. To encrypt a complete message, a mode needs to be selected. Authenticated encryption (which provides both confidentiality and integrity) is recommended. GCM, CCM and EAX are most commonly used authenticated encryption modes. GCM is usually preferred and it performs well in Intel architectures which provide dedicated instructions for GCM. All these three modes are CTR-based (counter-based) modes and therefore they do not need padding. As a result they are not vulnerable to padding related attacks
An initialization Vector (IV) is required for GCM. The IV is not a secret. The only requirement being it has to be random or unpredictable. In NodeJs,
crypto.randomBytes()
is meant to produce cryptographically strong pseudo random numbers.NIST recommends 96 bit IV for GCM to promote interoperability, efficiency, and simplicity of design
The recipient needs to know the IV to be able to decrypt the cipher text. Therefore the IV needs to be transferred along with the cipher text. Some implementations send the IV as AD (Associated Data) which means that the authentication tag will be calculated on both the cipher text and the IV. However, that is not required. The IV can be simply pre-pended with the cipher text because if the IV is changed during transmission due to a deliberate attack or network/file system error, the authentication tag validation will fail anyway
Strings should not be used to hold the clear text message, password or the key as Strings are immutable which means we cannot clear the strings after use and they will linger in the memory. Thus a memory dump can reveal the sensitive information. For the same reason, the client calling these encryption or decryption methods should clear all the
Buffer
holding the message, key or the password after they are no longer needed usingbufferVal.fill(0)
.Finally for transmission over network or storage, the cipher text should be encoded using Base64 encoding.
buffer.toString('base64');
can be used to convert theBuffer
into Base64 encoded string.Note that the key derivation scrypt (
crypto.scryptSync()
) has been used to derive a key from a password. However, this function is available only in Node 10.* and later versions
加密算法:具有 256 位密钥的分组密码 AES 被认为足够安全。要加密完整的消息,需要选择一种模式。建议使用经过身份验证的加密(提供机密性和完整性)。GCM、CCM 和 EAX 是最常用的认证加密模式。GCM 通常是首选,它在为 GCM 提供专用指令的 Intel 架构中表现良好。所有这三种模式都是基于 CTR(基于计数器)的模式,因此它们不需要填充。因此,它们不易受到与填充相关的攻击
GCM 需要初始化向量 (IV)。IV 不是秘密。唯一的要求是它必须是随机的或不可预测的。在 NodeJs 中,
crypto.randomBytes()
旨在产生加密强的伪随机数。NIST 建议 GCM 使用 96 位 IV,以提高设计的互操作性、效率和简单性
接收者需要知道 IV 才能解密密文。因此,IV 需要与密文一起传输。一些实现将 IV 作为 AD(关联数据)发送,这意味着将在密文和 IV 上计算身份验证标签。然而,这不是必需的。可以简单地在 IV 前面加上密文,因为如果在传输过程中由于故意攻击或网络/文件系统错误而更改了 IV,则身份验证标签验证无论如何都会失败
字符串不应该用于保存明文消息、密码或密钥,因为字符串是不可变的,这意味着我们无法在使用后清除字符串,它们会留在内存中。因此,内存转储可以揭示敏感信息。出于同样的原因,调用这些加密或解密方法的客户端应
Buffer
在不再需要使用bufferVal.fill(0)
.最后,为了通过网络或存储进行传输,密文应使用 Base64 编码进行编码。
buffer.toString('base64');
可用于将 转换Buffer
为 Base64 编码的字符串。请注意,密钥派生 scrypt (
crypto.scryptSync()
) 已用于从密码派生密钥。但是,此功能仅在 Node 10.* 及更高版本中可用
The code goes here:
代码在这里:
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;
}
And the unit tests are also provided below:
下面还提供了单元测试:
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);
});
});
});
回答by Bhumij Gupta
An update to @mak answer, crypto.createCipher
and crypto.createDecipher
has been deprecated. Latest working code would be:
更新到@mak答案,crypto.createCipher
并crypto.createDecipher
已被弃用。最新的工作代码是:
var crypto = require("crypto");
var algorithm = "aes-192-cbc"; //algorithm to use
var password = "Hello darkness";
const key = crypto.scryptSync(password, 'salt', 24); //create key
var text= "this is the text to be encrypted"; //text to be encrypted
const iv = Buffer.alloc(16, 0);
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);
回答by Shashwat Gupta
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);