Попытка расшифровать данные, зашифрованные в другой системе, не будет работать, если вы не знаете и не разберетесь со множеством сложных деталей о том, как обе системы выполняют криптографию .Хотя и Rails, и инструмент командной строки openssl
используют библиотеки OpenSSL под капотом для своих криптографических операций, они оба используют их по-своему, которые напрямую не взаимодействуют.
Если вы посмотрите близко кнапример, в двух системах:
- шифровальщик сообщений Rails не только шифрует сообщение, но и подписывает it
- шифровальщик Rails использует
Marshal
Для сериализации входных данных - инструмент
openssl enc
ожидает зашифрованные данные в отдельном формате файла с заголовком Salted__<salt>
(поэтому вы получаете сообщение bad magic number от 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
) представляет собой комбинацию двух частей, разделенных --
(см. source ):
- зашифрованное сообщение (в кодировке Base64) и
- подпись сообщения (в кодировке Base64).
Подпись не важна для шифрования, поэтому давайте посмотрим на первыйчасть - давайте расшифруем его в консоли Rails:
> Base64.strict_decode64("SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==")
=> "HdSQv1G+57XZUciUlqY7B0yr2MyJt4uaA8+wgGcYWAg=--9+wXA5bLVoGrzmgyh8mf4w=="
Вы можете видеть, что декодированное сообщение снова состоит из двух частей в кодировке Base64, разделенных --
(см. source ):
- само зашифрованное сообщение
- вектор инициализации, используемый в шифровании
В шифраторе сообщений Rails по умолчанию используется шифр aes-256-cbc
(обратите внимание, что это изменилось со времен Rails 5.2).Этот шифр нуждается в векторе инициализации, который генерируется Rails случайным образом и должен присутствовать в зашифрованном выводе, чтобы мы могли использовать его вместе с ключом для расшифровки сообщения.
Более того, Rails не шифрует входные данные в виде простого простого текста, а скорее сериализованной версии данных , используя по умолчанию сериализатор Marshal
( source ).Если бы мы расшифровали такое сериализованное значение с помощью 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__
, затем следует salt (в шестнадцатеричной форме) и, наконец, за ним следуют зашифрованные данные. Чтобы иметь возможность расшифровать любые данные с помощью этого инструмента, мы должны перевести наши зашифрованные данные в тот же формат.
Инструмент 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
, используя echo.
отладка
В случае возникновения проблем при расшифровке с помощью openssl
полезно добавить параметр -P
в командную строку, который выводит отладочную информацию о параметрах шифра / ключа:
$ echo ... | openssl ... -P
salt=73616C7473616C74
key=196827B250431E911310F5DBC82D395782837B7AE56230DCE24E497CF07B6518
iv =85D90E59542C6FD67FDFC9B9B5236E58
Значения salt
, key
и iv
должны соответствовать значениям отладки, напечатанным исходным кодом в методе encrypt_text
, напечатанном выше. Если они разные, вы знаете, что делаете что-то не так ...
Теперь, я думаю, вы можете ожидать аналогичных проблем при попытке расшифровать сообщение на ходу, но я думаю, у вас есть несколько хороших указателей для запуска.