【问题标题】:nodejs crypto Is there any encrypt and decrypt text, should give different encryption at every timenodejs crypto 有没有加解密的文本,应该每次都给不同的加密
【发布时间】:2018-05-12 14:25:23
【问题描述】:

我知道带有缓冲区的 AES 256 CBC 可以提供不同的加密,但它的长度 66。 这是我的代码

  const crypto = require('crypto');
  const ENCRYPTION_KEY = 'Must256bytes(32characters)secret';
  const IV_LENGTH = 16;
  function encrypt(text) {
    let iv = crypto.randomBytes(IV_LENGTH);
    let cipher = crypto.createCipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
    let encrypted = cipher.update(text.toString());
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    return iv.toString('hex') + 'XX' + encrypted.toString('hex');
  }
  function decrypt(text) {
    let textParts = text.split('XX');
    let iv = new Buffer(textParts.shift(), 'hex');
    let encryptedText = new Buffer(textParts.join('XX'), 'hex');
    let decipher = crypto.createDecipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
    let decrypted = decipher.update(encryptedText);
    try{
      decrypted = Buffer.concat([decrypted, decipher.final()]);
      return decrypted.toString();
    }catch(Err){
      return 'NULL';
    }
  }

问题是加密数据长度为 66,即使文本为 1
那么是否有任何加密和解密方法应该每次都给出不同的加密数据,文本少于10个字符为1(根据我的例子)

谢谢

【问题讨论】:

  • 结果必须是可打印的字符串,还是可以接受 Buffer 作为结果?
  • 另外,10 是硬限制吗?这很容易减少到 24 个字符(如果您可以接受缓冲区,则为 17 个字节)。 10 个字符需要权衡安全性。

标签: node.js encryption aes cryptojs


【解决方案1】:

是的。您可以获得 5 个字节(或 10 个十六进制字符)或更少的密文。但是这样短的密文是有问题的。

基本上有两种方法。我将从更简单的开始。

计数器模式 (CTR)

您可以将 CTR(计数器)模式与 nonce 一起使用。结果,使用 CTR 模式加密 X 字节会为您提供 X 字节。 CTR 模式不需要将明文填充到块大小的 N 倍。

这个随机数(number-used-once)必须是一个唯一的数字,否则你的明文有直接暴露的危险。你不能只依赖一个随机数;由于生日限制,一个 4 字节的随机数具有很高的重复概率。

因此,您要么需要单独存储 nonce,要么在密文中包含一个 4 字节的 nonce。但是,如果您设法重用 nonce,那么您就完蛋了。对于如此少的字节,这意味着您基本上必须保留一个 4 字节的计数器,这意味着必须存储 state

通常 CTR 模式加密例程要求您提供 IV 而不是随机数。这只是初始计数器值。您必须通过获取随机数来构造此值,然后右填充零值字节,直到达到 16 个字节,即 AES 的块大小。

你可以找到示例代码here,别忘了点赞。 Rob 还提供了一些示例代码in his answer

格式保留加密 (FPE)

使用格式保留加密,加密的输出使用精确的位数或偶数作为输入的可能值。听起来不错,但 FPE 由一堆相对复杂的算法组成。基本上你必须在 JavaScript 中找到一个加密库来实现它。

请注意,使用 FPE 时,相同的消息将始终加密为相同的密文。根据应用程序,这可能是也可能不是问题。


注意事项:

  • 您使用的是十六进制编码,这不是目前最密集的编码,您可以使用 base 64 甚至 ASCII 85 编码。
  • 首先将 IV 和密文作为字节连接起来,然后执行编码会更有效。如果您有 IV 的静态大小,则可以简单地使用常量而不是分隔符。
  • 您的密钥应该由随机字节组成,而不是文本字符串。密码需要使用密码哈希进行转换。

【讨论】:

    【解决方案2】:

    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)
    

    【讨论】:

    • 不得不改正我的名字;没有人正确地写出我的荷兰名字......也与你的答案相关,Rob 更容易写:)
    • 很抱歉;我最大的孩子现在正在学习荷兰语,我一直在学习我总是需要对拼写进行三次检查。
    • 谢谢@RobNapier。酷:)
    【解决方案3】:

    您的代码在我这边运行良好,而长长的十六进制字符序列对我来说似乎完全正常。

    您的密文长度始终为 66 个字符(十六进制),因为您要存储 32 个字符的 IV、2 个字符的分隔符(“AP”)和 32 个字符(256 位)的密文。您的代码使用填充,因此,密文将始终是明文长度的下一个 16 个字节(32 个十六进制字符)的倍数,这就是为什么您会得到如此长的十六进制字符串作为明文只有 1 个字符的字符串。

    不幸的是,AES 需要填充(至少在您使用的模式下),否则它将不起作用。

    【讨论】:

    • 填充是什么意思
    • 填充基本上将字节添加到明文的最后一个块的末尾,以使其与算法的块大小相匹配(在 AES 的情况下,16 个字节)。如果没有填充,最终的明文块将不会以与填充相同的方式处理,并且对于 1 个字符的明文字符串,您不会得到如此长的密文。
    • 这是不正确的; AES 不能直接加密长度不正确的数据。如果数据不是长度 16 的倍数,则必须对其进行填充,否则 AES 将无法运行。可以使用密文窃取和流式操作模式(如 CTR)来避免这种情况,但最终长度仍将远大于 10 个可打印字符。
    • @RobNapier 我很快就发现了这一点。感谢您指出了这一点。 @Gopalakrishnan您可以在更新密码之前添加cipher.setAutoPadding(false);,但是除非您的明文长度是块大小的倍数,否则cipher.final() 会引发错误。 AES 似乎需要填充。对于误导性的回答,我深表歉意。
    猜你喜欢
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    • 2021-04-24
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多