Уникальные IVs, производящие идентичный зашифрованный текст, используя 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  ║
╚════╩══════════════════════════════╩═══════════════════╝

Почему зашифрованный текст идентичен? Разве это не та часть точки iv, которую по умолчанию использует драгоценный камень?

Ответы

Ответ 1

Обновление: следующее сообщение, объясняющее всю проблему, проблема исправлена, см. нижнюю часть этого ответа для решения.

Я уверен, что вы заметили довольно неприятную проблему безопасности в encryptor gem (камень, который используется attr_encrypted для выполнения фактических шифров).

Проблема заключается в том, что при использовании алгоритма aes-256-gcm (или любого из алгоритмов AES GCM) вектор инициализации (IV) в настоящее время действительно не учитывается при шифровании. Проблема не влияет на другие алгоритмы, но, к сожалению, aes-256-gcm является алгоритмом по умолчанию в attr_encrypted.

Как оказалось, это порядок установки IV против ключа шифрования, что вызывает вопрос. Когда IV задается перед ключом (поскольку находится в драгоценном камне), IV не учитывается, но он устанавливается после ключа.

Некоторые тесты для проверки проблемы:

Принимая части кода encryptor gem, я создал простейший тестовый пример, чтобы доказать эту проблему (протестирован под ruby ​​2.3.0, скомпилированный с версией OpenSSL "1.0.1f 6 января 2014" ):

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 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 до/после ключа) дает нам следующие результаты:

# 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, это для GCM. Что еще более важно, зашифрованный результат в CBC отличается для двух разных IV, тогда как для GCM не требуется, если IV задан перед ключом.

Я только что создал запрос на перенос, чтобы исправить эту проблему в камне encryptor. На практике у вас есть несколько вариантов:

  • Подождите, пока не будет выпущена новая версия encryptor.

  • Используйте также соль с attr_encrypted. Вы должны использовать соль в любом случае для дальнейшей защиты зашифрованных данных.

Самое печальное, что все уже зашифрованные данные станут неподдающимися исправлению после исправления, так как внезапно будут приняты во внимание IV.

Обновление: encryptor 3.0.0

Теперь вы можете обновить камень encryptor до версии 3.0, в котором исправлена ​​ошибка. Теперь, если вы впервые используете драгоценные камни encryptor или attr_encrypted, вы все настроены, и все должно работать правильно.

Если у вас есть данные, которые уже зашифрованы с помощью encryptor 2.0.0, тогда вы должны вручную заново зашифровать данные после обновления gem, иначе он не сможет правильно расшифровать! Вы будете предупреждены об этом во время обновления жемчужины. Схематическая процедура следующая:

  • Вам необходимо расшифровать все ваши зашифрованные данные с помощью класса encryptor (примеры README), используя опцию :v2_gcm_iv => true, Это должно правильно расшифровать ваши данные.
  • Затем вы должны снова зашифровать одни и те же данные, без этой опции (т.е. :v2_gcm_iv => false), но включая правильный IV из вашей базы данных.
  • Если у вас есть производственные данные, вам нужно будет сделать это обновление в автономном режиме и сразу после обновления gem, чтобы гарантировать отсутствие повреждения данных.

Обновление 2: проблема в openssl подтверждена и исправлена ​​gem

FYI, недавно подтвердил, что это действительно была проблема в базовой библиотеке ruby-openssl и ошибка была исправлена ​​ сейчас. Таким образом, в будущем возможно, что даже attr_encrypted gem version 2.x будет корректно работать при использовании с новой версией openssl-2.0.0 gem (которая теперь находится в бета-версии по состоянию на сентябрь 2016 года).

Ответ 2

Если обычный текст, клавиша и IV совпадают, зашифрованный текст будет таким же.

Похоже, вам нужно использовать:

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

Примечание: encode_iv: true

Или, возможно, установите параметр по умолчанию encode_iv: true

Документация: attr_encrypted