Maarten covers 我想说的大部分要点,所以这只是一些详细说明和 Node 中的一个示例。
您的代码的更改是:
- 以 Base64 而非 Hex 编码。这更节省空间。最好只使用 Buffers 而根本不创建字符串;那么我们可以有一个 9 字节的 nonce 而不是 5 字节的 nonce。
- 去掉 IV/nonce 和密文之间的分隔符。我们知道 IV/nonce 有多长;我们不需要分隔符。
- 使用 CTR 模式而不是 CBC 模式。这使得输出长度等于输入长度。
- 使用随机数而不是 IV。从 2^40 的空间中随机选择随机数。 (随机选择 CTR 随机数通常是非常危险的。请参阅下文了解为什么它可能在您的用例中是可以接受的;仍然不建议这样做。)
- 通过添加 PBKDF2 修复密码生成(您也可以只使用 randomBytes)。您的密码非常不安全。它是一个 ASCII 字符串,这意味着它代表 AES-256 密钥空间的一小部分。大约为密钥空间的 0.000000000002%。这比 AES-256 的安全性要低得多。
正如 Maarten 所说,CTR 模式复制 Key+Nonce 对是非常危险的。如果有人这样做,他们可以学习两个原始消息的异或。这样,他们就有很大的机会解密这两条消息。例如,如果您在两条消息上复制了您的密钥+随机数,并且攻击者使用它来发现它们的 XOR 为 3,并且知道加密的文本是大写字母,那么他们就会知道这两条消息必须是以下消息之一:
[('A', 'B'), ('D', 'G'), ('E', 'F'), ('H', 'K'), ('I', 'J'), ('L', 'O'),
('M', 'N'), ('P', 'S'), ('Q', 'R'), ('T', 'W'), ('U', 'V')]
这类信息对于人类语言或计算机协议等结构化数据来说是毁灭性的。它可以非常快速地用于解密整个消息。 Key+nonce 重用就是WEP was broken。 (当您手动执行此操作时,它基本上与解决您在报纸上找到的密码谜题相同。)加密数据越随机,它提供的上下文越少,它的功能就越小。
使用随机的 5 字节 nonce,在大约 130 万次加密后发生冲突的可能性为 50%。使用随机的 8 字节 nonce,在大约 5.3B 加密后发生冲突的可能性为 50%。 sqrt(pi/2 * 2^bits)
在密码学术语中,这是一个完全被破坏的。它可能足以满足您的目的,也可能不够。正如 Maarten 指出的那样,要正确地做到这一点(我在下面没有这样做),您应该跟踪您的计数器并为每次加密增加它,而不是使用随机的。在 2^40 次加密 (~1T) 之后,您更改了您的密钥。
假设每百万分之一消息的泄漏信息是可以接受的,这就是您将如何实现的。
const crypto = require('crypto');
const ENCRYPTION_KEY = 'Must256bytes(32characters)secret';
const SALT = 'somethingrandom';
const IV_LENGTH = 16;
const NONCE_LENGTH = 5; // Gives us 8-character Base64 output. The higher this number, the better
function encrypt(key, text) {
let nonce = crypto.randomBytes(NONCE_LENGTH);
let iv = Buffer.alloc(IV_LENGTH)
nonce.copy(iv)
let cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
let encrypted = cipher.update(text.toString());
message = Buffer.concat([nonce, encrypted, cipher.final()]);
return message.toString('base64')
}
function decrypt(key, text) {
let message = Buffer.from(text, 'base64')
let iv = Buffer.alloc(IV_LENGTH)
message.copy(iv, 0, 0, NONCE_LENGTH)
let encryptedText = message.slice(NONCE_LENGTH)
let decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
let decrypted = decipher.update(encryptedText);
try{
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}catch(Err){
return 'NULL';
}
}
// You could do this one time and record the result. Or you could just
// generate a random 32-byte key and record that. But you should never
// pass an ASCII string to the encryption function.
let key = crypto.pbkdf2Sync(ENCRYPTION_KEY, SALT, 10000, 32, 'sha512')
let encrypted = encrypt(key, "X")
console.log(encrypted + " : " + encrypted.length)
let decrypted = decrypt(key, encrypted)
console.log(decrypted)