我开始在我的 NodeJS 项目中使用加密模块。 我的代码很简单如下:
const { createCipheriv, randomBytes, createDecipheriv } = require('crypto');
const key = randomBytes(32);
const iv = randomBytes(16);
const cipher = createCipheriv('aes256', key, iv);
const decipher = createDecipheriv('aes256', key, iv);
const cipherTest = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage.toString('utf-8')
}
cipherTest 函数返回与输入相同的结果。正确!
但是,如果我如下分别创建密码和解密函数,则无法解密加密消息。
const encryptMessage = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
return encryptedMessage;
}
const decryptMessage = (encryptedMessage) => {
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage;
}
有人可以看一下吗?我对此深表感谢。
最佳答案
密码是算法,但它们包含状态。此外,它们通常需要 IV 作为输入,需要对本例中使用的 CBC 进行随机化。假设 key 保持不变,则 IV 需要针对每个密文进行更改。
因此,您通常应该在伪代码中遵循以下结构:
MessageEncryptor {
Key key
constructor(Key key) {
if (key = bad format) {
throw error // fail fast
this.key = key
}
String encryptMessage(String message) {
Cipher cipher = Cipher.Create(key)
Bytes iv = Rng.createBytes(cipher.blockSize)
cipher.setIV(iv)
Bytes encodedMessage = UTF8.encode(message)
// lower level runtimes may require padding for CBC mode
Bytes ciphertext = cipher.encrypt(encodedMessage)
ciphertext = Bytes.concat(ciphertext, cipher.final())
Bytes ciphertextMessage = Bytes.concat(iv, ciphertext)
// use HMAC here to append an authentication tag
String encodedCiphertextMessage = Base64.encode(ciphertextMessage)
return encodedCiphertextMessage
}
String decryptMessage(String encodedCiphertextMessage) {
Cipher cipher = Cipher.Create(key)
Bytes ciphertextMessage = Base64.decode(encodedCiphertextMessage)
// use HMAC here to verify an authentication tag
Bytes iv = Bytes.sub(0, cipher.blockSizeBytes)
cipher.setIV(iv)
Bytes encodedMessage = cipher.decrypt(ciphertextMessage, start = iv.Size)
encodedMessage = Bytes.concat(encodedMessage, cipher.final())
// lower level runtimes may require unpadding here
String message = UTF8.decode(encodedMessage)
return message
}
}
如您所见,您必须:
- 仅在方法体内使用 Cipher 构造;这些对象携带状态,并且通常不是线程安全的,因为它们是可变的;
- 依赖
key
作为字段,以便消息加密器始终使用相同的 key ; - 创建一个新的
MessageEncryptor
实例,以防您想使用其他 key ; - 在加密期间为每条消息创建一个新的 IV,并在解密期间从密文消息中提取它。
注释:
- 如果您的方案可以处理字节,则无需对
ciphertextMessage
进行编码; - IV 可能具有不同的大小,或者对于 GCM 等其他模式可能不需要随机值;
- 使用 GCM 等身份验证模式要安全得多,但使用 HMAC 对 IV + 密文进行哈希处理也是一种选择(如果您不确定重用加密 key 可能出现的问题,请使用单独的 key );
UTF8.encode
和UTF8.decode
可以简单地替换为任何其他编解码器,以防您的消息不是简单的字符串(UTF8不过强烈推荐用于字符串);- 很多库都会有编码等快捷方式,因此您的代码可能会更短;
- 还有预制的容器格式,例如 CMS 或 NaCL,它们可能比您能想到的任何方案更安全、更灵活;
- 本示例中使用的 CBC 容易受到明文预言机攻击,包括填充预言机攻击
- 对于较大的消息,使用流式处理所有编码、解码、加密和解密操作更有意义(此处显示的方法具有较高且不必要的内存要求);
- 仅为特定类型的消息创建此类类,并创建协议(protocol) + 协议(protocol)描述(如果这样做); 大多数加密 API 不需要包装类,它是专门为这种灵活性而创建的;
MessageEncryptor
仍然非常轻量级 - 实际上它只有一个 key 作为状态(有时还有一个附加 key 和一两个算法)。如果您要使用可以更改值或状态的字段(例如缓冲区)进行扩展,那么您希望每次从类中使用该类时都创建该类的一个新实例不同的线程。否则,您希望在每次成功的消息加密/解密后清除状态。
使用完 key 后,您可能希望安全地销毁它。
关于node.js - 为什么 createCipheriv 和 createDecipheriv 不能在单独的函数中工作 - crypto,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73169766/