Имитировать функции AES_ENCRYPT и AES_DECRYPT в Ruby - PullRequest
2 голосов
/ 24 марта 2009

Мне нужно подражать тому, что 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?

Спасибо!

Ответы [ 3 ]

5 голосов
/ 24 марта 2009

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

Согласно сообщению в блоге, которое я отправлял ранее, вот как 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;
}

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

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 lib, встроенный в 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

: -)

1 голос
/ 24 марта 2009

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

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

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

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

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

0 голосов
/ 28 октября 2010

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

...