Вопрос

Мне нужно имитировать то, что делает MySQL при шифровании и дешифровании строк, используя встроенные функции AES_ENCRYPT() и AES_DECRYPT().

Я прочитал пару сообщений в блоге и, по-видимому, MySQL использует 128-битное шифрование AES для этих функций.Кроме того, поскольку для этого шифрования требуется 16-битный ключ, MySQL дополняет строку символами x0 (\0s), пока ее размер не станет 16-битным.

Обнаружен алгоритм на языке C из исходного кода MySQL. здесь.

Теперь мне нужно повторить то, что MySQL делает в приложении Rails, но все, что я пробовал, не работает.

Вот способ воспроизвести поведение, которое я получаю:

1) Создайте новое приложение Rails.

rails encryption-test
cd encryption-test

2) Создайте новые леса.

script/generate scaffold user name:string password:binary

3) Отредактируйте свой config/database.yml и добавьте тестовую базу данных MySQL.

development:
    adapter: mysql
    host: localhost
    database: test
    user: <<user>>
    password: <<password>>

4) Запустите миграцию

rake db:migrate

5) Войдите в консоль, создайте пользователя и обновите его пароль из запроса MySQL.

script/console
Loading development environment (Rails 2.2.2)
>> User.create(:name => "John Doe")
>> key = "82pjd12398JKBSDIGUSisahdoahOUASDHsdapdjqwjeASIduAsdh078asdASD087asdADSsdjhA7809asdajhADSs"
>> ActiveRecord::Base.connection.execute("UPDATE users SET password = AES_ENCRYPT('password', '#{key}') WHERE name='John Doe'")

Вот где я застрял.Если я попытаюсь его расшифровать, используя MySQL, это сработает:

>> loaded_user = User.find_by_sql("SELECT AES_DECRYPT(password, '#{key}') AS password FROM users WHERE id=1").first
>> loaded_user['password']
=> "password"

Однако если я попытаюсь использовать библиотеку OpenSSL, я не смогу заставить ее работать:

cipher = OpenSSL::Cipher::Cipher.new("AES-128-ECB") 
cipher.padding = 0
cipher.key = key
cipher.decrypt 

user = User.find(1)
cipher.update(user.password) << cipher.final #=> "########gf####\027\227"

Я попробовал дополнить ключ:

desired_length = 16 * ((key.length / 16) + 1)
padded_key = key + "\0" * (desired_length - key.length)

cipher = OpenSSL::Cipher::Cipher.new("AES-128-ECB") 
cipher.key = key
cipher.decrypt 

user = User.find(1)
cipher.update(user.password) << cipher.final #=> ""|\e\261\205:\032s\273\242\030\261\272P##"

Но это действительно не работает.

Кто-нибудь знает, как я могу имитировать поведение функций MySQL AES_ENCRYPT() и AES_DECRYPT() в Ruby?

Спасибо!

Это было полезно?

Решение

Для дальнейшего использования:

Согласно сообщению в блоге, который я отправлял ранее, вот как MySQL работает с ключом, который вы предоставляете AES_ENCRYPT / DECRYPT:

«Алгоритм просто создает 16 -байтовый буфер, установленный на все ноль, затем проходит через все символы, которую вы предоставляете, и выполняет назначение с бить и между двумя значениями.Если мы идентифицируем, пока не достигнем конца 16 -байтового буфера, мы просто начинаем с самого начала, делая ^=.Для струн короче 16 символов, мы останавливаемся в конце строки ».

Я не знаю, умеете ли вы читать C, но вот упомянутый фрагмент:

http://pastie.org/425161

Специально эта часть:

bzero((char*) rkey,AES_KEY_LENGTH/8);      /* Set initial key  */

for (ptr= rkey, sptr= key; sptr < key_end; ptr++,sptr++)
{
  if (ptr == rkey_end)
    ptr= rkey;  /*  Just loop over tmp_key until we used all key */
  *ptr^= (uint8) *sptr;
}

Итак, я придумал этот метод (с помощью Роба Биденхарна с рубинового форума):

def mysql_key(key)
   final_key = "\0" * 16
   key.length.times do |i|
     final_key[i%16] ^= key[i]
   end
   final_key
end

Это, учитывая строку, возвращает ключ, который MySQL использует при шифровании и дешифровании.Итак, все, что вам сейчас нужно, это:

def aes(m,k,t)
  (aes = OpenSSL::Cipher::AES128.new("ECB").send(m)).key = k
  aes.update(t) << aes.final
end

def encrypt(key, text)
  aes(:encrypt, key, text)
end

def decrypt(key, text)
  aes(:decrypt, key, text)
end

Чтобы использовать библиотеку openssl, встроенную в Ruby, затем вы можете сделать два «окончательных» метода:

def mysql_encrypt(s, key)
  encrypt(mysql_key(key), s)
end

def mysql_decrypt(s, key)
  decrypt(mysql_key(key), s)
end

И все готово!Кроме того, полный код можно найти в этом Gist:

http://gist.github.com/84093

:-)

Другие советы

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

Кроме того, я подозреваю, что ключ для API OpenSSL требует «буквального» ключа, а не ASCII-представления ключа, как у вас в вашем коде.

Учитывая нехватку документации OpenSSL Ruby и если вы немного говорите на Java, вы можете создать прототип в JRuby с помощью провайдера BouncyCastle - это то, что я сделал с хорошим эффектом при работе с TwoFish (отсутствует в API OpenSSL). .

РЕДАКТИРОВАТЬ:Я перечитал ваш комментарий о дополнении ключа.В вашем вопросе есть некоторая путаница в битах/байтах, и я не уверен, как это применимо в любом случае, поскольку длина вашего опубликованного ключа составляет 89 символов (712 бит).Возможно, вам следует попробовать использовать 128-битный ключ/пароль, чтобы устранить это явление заполнения?

Между прочим, разработчиков MySQL следует наказывать за слабую криптографию, есть лучшие способы растянуть пароли, чем простое заполнение нулевыми байтами :(

Если вы не против использовать реализацию openssl attr_encrypted это драгоценный камень, который позволяет использовать шифрование для большинства классов, ActiveRecord или нет.К сожалению, он не будет совместим с функциями MySQL AES_EN/DECRYPT.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top