Попытка расшифровать данные, зашифрованные в другой системе, не сработает, если вы не знаете и не имеете дело со множеством сложных деталей того, как обе системы выполняют криптографию. Хотя и Rails, и инструмент командной строки openssl
под капотом используют библиотеки OpenSSL для своих криптографических операций, они оба используют его по-своему, не взаимодействуя напрямую.
Если вы внимательно посмотрите на две системы, вы увидите, что, например:
- Шифровальщик сообщений Rails не только шифрует сообщение, но и подписывает его
- Шифровальщик Rails использует
Marshal
для сериализации входных данных.
- инструмент
openssl enc
ожидает зашифрованные данные в отдельном формате файла с заголовком Salted__<salt>
(поэтому вы получаете сообщение плохой магический номер от openssl
)
- инструмент
openssl
должен быть правильно настроен для использования тех же шифров, что и шифратор Rails и генератор ключей, поскольку значения по умолчанию openssl
отличаются от значений по умолчанию Rails.
- конфигурация шифров по умолчанию значительно изменилась по сравнению с Rails 5.2.
С этой общей информацией мы можем взглянуть на практический пример. Он протестирован в Rails 4.2, но должен одинаково работать и в Rails 5.1.
Анатомия сообщения, зашифрованного Rails
Позвольте мне начать с немного измененного кода, который вы представили. Единственными изменениями являются установка статических значений password
и salt
и печать большого количества отладочной информации:
def encrypt_text(text_to_encrypt)
password = "password" # the password to derive the key
salt = "saltsalt" # salt must be 8 bytes
key = ActiveSupport::KeyGenerator.new(password).generate_key(salt, 32)
puts "salt (hexa) = #{salt.unpack('H*').first}" # print the saltin HEX
puts "key (hexa) = #{key.unpack('H*').first}" # print the generated key in HEX
crypt = ActiveSupport::MessageEncryptor.new(key)
output = crypt.encrypt_and_sign(text_to_encrypt)
puts "output (base64) = #{output}"
output
end
encrypt_text("secret text")
Когда вы запустите это, вы получите что-то вроде следующего вывода:
salt (hexa) = 73616c7473616c74
key (hexa) = 196827b250431e911310f5dbc82d395782837b7ae56230dce24e497cf07b6518
output (base64) = SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==--80d091e8799776113b2c0efd1bf75b344bf39994
Последняя строка (вывод метода encrypt_and_sign
) представляет собой комбинацию двух частей, разделенных --
(см. источник):
- зашифрованное сообщение (в кодировке Base64) и
- подпись сообщения (в кодировке Base64).
Подпись не важна для шифрования, поэтому давайте посмотрим на первую часть — давайте расшифруем ее в консоли Rails:
> Base64.strict_decode64("SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==")
=> "HdSQv1G+57XZUciUlqY7B0yr2MyJt4uaA8+wgGcYWAg=--9+wXA5bLVoGrzmgyh8mf4w=="
Вы можете видеть, что декодированное сообщение снова состоит из двух частей в кодировке Base64, разделенных --
(см. источник):
- само зашифрованное сообщение
- вектор инициализации, используемый в шифровании
Шифровальщик сообщений Rails по умолчанию использует шифр aes-256-cbc
(обратите внимание, что он изменился с версии Rails 5.2). Для этого шифра требуется вектор инициализации, который случайным образом генерируется Rails и должен присутствовать в зашифрованном выводе, чтобы мы могли использовать его вместе с ключом для расшифровки сообщения.
Кроме того, Rails шифрует входные данные не как простой текст, а как сериализованную версию данных, используя сериализатор Marshal
по умолчанию (источник). Если бы мы расшифровали такое сериализованное значение с помощью openssl, мы все равно получили бы слегка искаженную (сериализованную) версию исходных текстовых данных. Вот почему будет более уместно отключить сериализацию при шифровании данных в Rails. Это можно сделать, передав параметр методу шифрования:
# crypt = ActiveSupport::MessageEncryptor.new(key)
crypt = ActiveSupport::MessageEncryptor.new(key, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
Повторный запуск кода дает результат, который немного короче, чем в предыдущей версии, потому что зашифрованные данные теперь не сериализованы:
salt (hexa) = 73616c7473616c74
key (hexa) = 196827b250431e911310f5dbc82d395782837b7ae56230dce24e497cf07b6518
output (base64) = SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=--58bbaf983fd20459062df8b6c59eb470311cbca9
Наконец, мы должны узнать некоторую информацию о процедуре получения ключа шифрования. исходник сообщает нам, что KeyGenerator использует алгоритм pbkdf2_hmac_sha1
с 2**16 = 65536
итерациями для получения ключа из пароля/секрета.
Анатомия openssl
зашифрованного сообщения
Теперь аналогичное расследование необходимо провести на стороне openssl
, чтобы узнать подробности процесса его расшифровки. Во-первых, если вы зашифруете что-либо с помощью инструмента openssl enc
, вы обнаружите, что выходные данные имеют отличный формат:
Salted__<salt><encrypted_message>
Он начинается с магической строки Salted__
, затем следует соль (в шестнадцатеричном формате) и, наконец, зашифрованные данные. Чтобы иметь возможность расшифровать любые данные с помощью этого инструмента, мы должны преобразовать наши зашифрованные данные в тот же формат.
Инструмент openssl
использует EVP_BytesToKey
(см. source), чтобы получить ключ по умолчанию, но его можно настроить для использования алгоритм pbkdf2_hmac_sha1
с использованием параметров -pbkdf2
и -md sha1
. Количество итераций можно установить с помощью опции -iter
.
Как расшифровать сообщение, зашифрованное Rails, в openssl
Итак, наконец, у нас достаточно информации, чтобы попытаться расшифровать сообщение, зашифрованное Rails, в openssl
.
Сначала мы должны снова декодировать первую часть зашифрованного Rails вывода, чтобы получить зашифрованные данные и вектор инициализации:
> Base64.strict_decode64("SUlIWFBjSXRUc0JodEMzLzhXckJzUT09LS1oZGtPV1ZRc2I5Wi8zOG01dFNOdVdBPT0=")
=> "IIHXPcItTsBhtC3/8WrBsQ==--hdkOWVQsb9Z/38m5tSNuWA=="
Теперь давайте возьмем IV (вторую часть) и преобразуем его в форму шестнадцатеричной строки, так как это форма, которая нужна openssl
:
> Base64.strict_decode64("hdkOWVQsb9Z/38m5tSNuWA==").unpack("H*").first
=> "85d90e59542c6fd67fdfc9b9b5236e58" # the initialization vector in hex form
Теперь нам нужно взять данные, зашифрованные с помощью Rails, и преобразовать их в формат, который openssl
распознает, то есть добавить к ним магическую строку и соль и снова закодировать их в Base64:
> Base64.strict_encode64("Salted__" + "saltsalt" + Base64.strict_decode64("IIHXPcItTsBhtC3/8WrBsQ=="))
=> "U2FsdGVkX19zYWx0c2FsdCCB1z3CLU7AYbQt//FqwbE=" # encrypted data suitable for openssl
Наконец, мы можем создать команду openssl
для расшифровки данных:
$ echo "U2FsdGVkX19zYWx0c2FsdCCB1z3CLU7AYbQt//FqwbE=" |
> openssl enc -aes-256-cbc -d -iv 85d90e59542c6fd67fdfc9b9b5236e58 \
> -pass pass:password -pbkdf2 -iter 65536 -md sha1 -a
secret text
И вуаля, мы успешно расшифровали исходное сообщение!
Параметры openssl
следующие:
-aes-256-cbc
устанавливает тот же шифр, который Rails использует для шифрования.
-d
означает расшифровку
-iv
передает вектор инициализации в виде шестнадцатеричной строки
-pass pass:password
устанавливает пароль, используемый для получения ключа шифрования, на «пароль».
-pbkdf2
и -md sha1
задают тот же алгоритм получения ключа, который используется в Rails (pbkdf2_hmac_sha1
)
-iter 65536
задает такое же количество итераций для получения ключа, как это было сделано в Rails.
-a
позволяет работать с зашифрованными данными в кодировке Base64 - нет необходимости обрабатывать необработанные байты в файлах
По умолчанию openssl
читает из STDIN, поэтому мы просто передаем зашифрованные данные (в правильном формате) в openssl
с помощью эха.
отладка
В случае, если у вас возникнут проблемы при расшифровке с помощью openssl
, полезно добавить в командную строку параметр -P
, который выводит отладочную информацию о параметрах шифра/ключа:
$ echo ... | openssl ... -P
salt=73616C7473616C74
key=196827B250431E911310F5DBC82D395782837B7AE56230DCE24E497CF07B6518
iv =85D90E59542C6FD67FDFC9B9B5236E58
Значения salt
, key
и iv
должны соответствовать значениям отладки, напечатанным исходным кодом в методе encrypt_text
, напечатанном выше. Если они разные, вы знаете, что делаете что-то не так...
Теперь, я думаю, вы можете ожидать подобных проблем при попытке расшифровать сообщение в ходу, но я думаю, что у вас есть несколько хороших указателей, чтобы начать.
person
BoraMa
schedule
28.03.2019
pass.txt
совпадает сencryption_key
? - person Vasiliy Faronov   schedule 26.03.2019