Попытка расшифровать строку, используя openssl / golang, которая была зашифрована в rails - PullRequest
3 голосов
/ 26 марта 2019

Я пытаюсь расшифровать строку, которая была зашифрована в моем проекте rails. Вот как я шифрую данные:

def encrypt_text(text_To_encrypt)
        # 0. generate the key using command openssl rand -hex 16 on linux machines
        # 1. Read the secret from config
        # 2. Read the salt from config
        # 3. Encrypt the data
        # 4. return the encypted data
        # Ref: http://www.monkeyandcrow.com/blog/reading_rails_how_does_message_encryptor_work/
        secret = Rails.configuration.miscconfig['encryption_key']
        salt = Rails.configuration.miscconfig['encryption_salt']
        key = ActiveSupport::KeyGenerator.new(secret).generate_key(salt, 32)
        crypt = ActiveSupport::MessageEncryptor.new(key)
        encrypted_data = crypt.encrypt_and_sign(text_To_encrypt)
        encrypted_data
end

Теперь проблема в том, что я не могу расшифровать его с помощью openssl. Это просто показывает плохое магическое число. Как только я сделаю это в open ssl, мой план - расшифровать его на golang.

Вот как я пытался расшифровать его с помощью openssl:

openssl enc -d -aes-256-cbc -salt -in encrypted.txt -out decrypted.txt -d -pass pass:<the key given in rails> -a

Это просто показывает плохое магическое число

1 Ответ

1 голос
/ 28 марта 2019

Попытка расшифровать данные, зашифрованные в другой системе, не будет работать, если вы не знаете и не разберетесь со множеством сложных деталей о том, как обе системы выполняют криптографию .Хотя и 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 ):

  1. зашифрованное сообщение (в кодировке Base64) и
  2. подпись сообщения (в кодировке Base64).

Подпись не важна для шифрования, поэтому давайте посмотрим на первыйчасть - давайте расшифруем его в консоли Rails:

> Base64.strict_decode64("SGRTUXYxRys1N1haVWNpVWxxWTdCMHlyMk15SnQ0dWFBOCt3Z0djWVdBZz0tLTkrd1hBNWJMVm9HcnptZ3loOG1mNHc9PQ==")
=> "HdSQv1G+57XZUciUlqY7B0yr2MyJt4uaA8+wgGcYWAg=--9+wXA5bLVoGrzmgyh8mf4w=="

Вы можете видеть, что декодированное сообщение снова состоит из двух частей в кодировке Base64, разделенных -- (см. source ):

  1. само зашифрованное сообщение
  2. вектор инициализации, используемый в шифровании

В шифраторе сообщений 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, напечатанном выше. Если они разные, вы знаете, что делаете что-то не так ...

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

...