为什么createCipheriv和createDecipheriv不能在单独的函数中工作-crypto



我开始为我的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;
}

有人能看看吗?对此我非常感谢。

密码是算法,但它们包含状态。此外,他们通常需要静脉注射作为输入,需要对这种情况下使用的CBC进行随机化。假设密钥保持不变,则每个密文的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构造;这些对象携带状态,并且通常是线程安全的,因为它们是可变的
  • 依赖CCD_ 1作为字段,使得消息加密器总是使用相同的密钥
  • 创建一个新的MessageEncryptor实例,以防您想要使用另一个密钥
  • 在加密期间为每个消息创建新的IV,并在解密期间从密文消息中提取它

备注:

  • 如果您的方案可以处理字节,则无需对ciphertextMessage进行编码
  • IV可以具有不同的大小,或者对于诸如GCM的其他模式可能不需要随机值
  • 使用GCM等身份验证模式要安全得多,但使用HMAC对IV+密文进行哈希也是一种选择(如果您不确定重用加密密钥的可能问题,请使用单独的密钥(
  • 如果您的消息不是一个简单的字符串,UTF8.encodeUTF8.decode可以简单地用任何其他编解码器替换(强烈建议使用UTF8作为字符串(
  • 很多库都有编码等快捷方式,所以你的代码可能会更短
  • 还有一些预先制作的容器格式,如CMS或NaCL,它们可能比你能想到的任何方案都更安全、更灵活
  • 本例中使用的CBC易受明文预言机攻击,包括填充预言机攻击
  • 对于较大的消息,将流用于所有编码、解码、加密和解密操作更有意义(此处显示的方法对内存的要求很高且不必要(
  • 仅为特定类型的消息创建这类类,如果这样做,则创建协议+协议描述大多数加密API不需要包装器类,它是故意用这种灵活性创建的

MessageEncryptor仍然非常轻量级——实际上它只有一个键作为状态(有时还有一个额外的键和一两个算法(。如果要扩展可以更改值或状态的字段,如缓冲区,则每次从不同线程使用该类时,都要创建一个新实例。否则,您希望在每次成功的消息加密/解密后清除状态。

你可能想在使用完密钥后安全地销毁它。

最新更新