使用 attr_encrypted 生成相同密文的唯一 IV

Unique IVs producing identical ciphertext using attr_encrypted

使用最基本的设置:

class User < ActiveRecord::Base
  attr_encrypted :name, 
                 key: 'This is a key that is 256 bits!!', 
                 encode: true, 
                 encode_iv: true, 
                 encode_salt: true
end

提供相同名称时,数据库中的结果如下所示:

╔════╦══════════════════════════════╦═══════════════════╗
║ id ║ encrypted_name               ║ encrypted_name_iv ║
╠════╬══════════════════════════════╬═══════════════════╣
║ 1  ║ aVXZb1b317nroumXVBdV9pGxA2o= ║ JyE7wHups+3upY5e  ║
║ 2  ║ aVXZb1b317nroumXVBdV9pGxA2o= ║ uz/ktrtbUAksg5Vp  ║
╚════╩══════════════════════════════╩═══════════════════╝

为什么密文是一样的?这不是gem默认使用的iv点的一部分吗?

如果明文、密钥和IV相同则密文也相同

看来你需要使用:

attr_encrypted :email, key: 'some secret key', encode: true, encode_iv: true, encode_salt: true

注:encode_iv: true

或者设置默认选项encode_iv: true

文档:attr_encrypted

更新: 下面是原文post解释了整个问题,现在问题已经解决,请看这个答案的底部一个解决方案。

我很确定你注意到 encryptor gem 中的一个相当严重的安全问题attr_encrypted 使用的 gem做实际的加密)。

问题是,当使用aes-256-gcm算法(或任何AES GCM算法时),初始化向量(IV)目前确实没有被考虑加密时。该问题不影响其他算法,但不幸的是 aes-256-gcmattr_encrypted.

中的默认算法

事实证明,it is the order of setting the IV vs. the encryption key what causes the issue. When IV is set before the key (as is in the gem),IV 未被考虑在内,但如果设置 after 密钥。

一些测试来证明问题:

在获取部分 encryptor gem 代码时,我创建了最简单的测试用例来证明问题(在 ruby 2.3.0 下针对 OpenSSL 版本“1.0. 1f 2014 年 1 月 6 日"):

def base64_enc(bytes)
  [bytes].pack("m")
end

def test_aes_encr(n, cipher, data, key, iv, iv_before_key = true)
  cipher = OpenSSL::Cipher.new(cipher)
  cipher.encrypt

  # THIS IS THE KEY PART OF THE ISSUE
  if iv_before_key
    # this is how it's currently present in the encryptor gem code
    cipher.iv = iv
    cipher.key = key
  else
    # this is the version that actually works
    cipher.key = key
    cipher.iv = iv
  end

  if cipher.name.downcase.end_with?("gcm")
    cipher.auth_data = ""
  end

  result = cipher.update(data)
  result << cipher.final

  puts "#{n} #{cipher.name}, iv #{iv_before_key ? "BEFORE" : "AFTER "} key: " +
           "iv=#{iv}, result=#{base64_enc(result)}"
end

def test_encryption
  data = "something private"
  key = "This is a key that is 256 bits!!"

  # control tests using AES-256-CBC
  test_aes_encr(1, "aes-256-cbc", data, key, "aaaabbbbccccdddd", true)
  test_aes_encr(2, "aes-256-cbc", data, key, "eeeeffffgggghhhh", true)
  test_aes_encr(3, "aes-256-cbc", data, key, "aaaabbbbccccdddd", false)
  test_aes_encr(4, "aes-256-cbc", data, key, "eeeeffffgggghhhh", false)

  # failing tests using AES-256-GCM
  test_aes_encr(5, "aes-256-gcm", data, key, "aaaabbbbcccc", true)
  test_aes_encr(6, "aes-256-gcm", data, key, "eeeeffffgggg", true)
  test_aes_encr(7, "aes-256-gcm", data, key, "aaaabbbbcccc", false)
  test_aes_encr(8, "aes-256-gcm", data, key, "eeeeffffgggg", false)
end

运行 test_encryption 使用 AES-256-CBC 加密文本,然后使用 AES-256-GCM,每次在两个机制中使用两个不同的 IV(IV 集 before/after 键),得到以下结果:

# control tests with CBC:
1 AES-256-CBC, iv BEFORE key: iv=aaaabbbbccccdddd, result=4IAGcazRmEUIRDE3ZpEgoS0Nmm1/+nrd5VT2/Xab0WM=
2 AES-256-CBC, iv BEFORE key: iv=eeeeffffgggghhhh, result=T7um2Wgb2vw1r4uryF3xnBeq+KozuetjKGItfNKurGE=
3 AES-256-CBC, iv AFTER  key: iv=aaaabbbbccccdddd, result=4IAGcazRmEUIRDE3ZpEgoS0Nmm1/+nrd5VT2/Xab0WM=
4 AES-256-CBC, iv AFTER  key: iv=eeeeffffgggghhhh, result=T7um2Wgb2vw1r4uryF3xnBeq+KozuetjKGItfNKurGE=

# the problematic tests with GCM:
5 id-aes256-GCM, iv BEFORE key: iv=aaaabbbbcccc, result=Tl/HfkWpwoByeYRz6Mz4yIo=
6 id-aes256-GCM, iv BEFORE key: iv=eeeeffffgggg, result=Tl/HfkWpwoByeYRz6Mz4yIo=
7 id-aes256-GCM, iv AFTER  key: iv=aaaabbbbcccc, result=+4Iyn7RSDKimTQi0S3gn58E=
8 id-aes256-GCM, iv AFTER  key: iv=eeeeffffgggg, result=3m9uEDyb9eh1RD3CuOCmc50=

这些测试表明,虽然设置 IV 与密钥的顺序与 CBC 无关,it is for GCM。更重要的是,CBC 中的加密结果对于两个不同的 IV 是不同的,而如果 IV 设置在密钥之前,则对于 GCM 则不是。

我刚刚创建了一个 pull request 来解决 encryptor gem 中的这个问题。实际上,您现在有几个选择:

  • 等新版本encryptorgem发布。

  • 也对 attr_encrypted 使用盐。无论如何,您应该使用 salt 来进一步保护加密数据。

非常不幸的是,所有已经加密的数据在修复后将变得无法破译,因为突然间 IV 将被考虑在内。

更新:encryptor 3.0.0 可用

您现在可以upgrade the encryptor gem to version 3.0修复了错误。现在,如果这是您第一次使用 encryptorattr_encrypted gem,那么您已经准备就绪,一切都应该正常工作。

如果您的数据已使用 encryptor 2.0.0 加密,则 您必须在 gem 升级后手动重新加密 数据,否则无法正确解密!在 gem 升级期间,您将收到有关此的警告。流程示意图如下:

  • 您必须使用 Encryptor class(参见 README 的示例),使用 :v2_gcm_iv => true 选项解密所有加密数据。这应该可以正确解密您的数据。
  • 然后您必须再次加密相同的数据,现在没有此选项(即 :v2_gcm_iv => false)但包括来自您数据库的正确 IV。
  • 如果您有生产数据,则需要在 gem 更新后立即进行离线升级,以确保不会损坏数据。

更新 2:openssl gem 中的问题已确认并修复

仅供参考,最近 confirmed that this had actually been an issue in the underlying ruby-openssl library and the bug has been fixed 现在 。因此,在未来,甚至 attr_encrypted gem 版本 2.x 与新的 openssl-2.0.0 gem 版本(即截至 2016 年 9 月,现在处于测试阶段。