2009-03-23 4 views
2

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

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

Алгоритм в исходном коде от MySQL замечен here.

Теперь мне нужно реплицировать то, что делает 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##" 

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

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

Спасибо!

ответ

5

На будущем:

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

«алгоритм просто создает 16 байт буфера установлен на все нуль, то петли через в h все символы строки , которую вы предоставляете, и выполняет назначение с побитовым ИЛИ между двумя значениями. Если мы повторяем до тех пор, пока не попали в конец 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; 
} 

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

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, построенный в рубин, а затем вы можете сделать два «окончательные» методы:

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

:-)

+0

Прохладный, вы получили это :) –

1

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

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

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

EDIT: Я перечитал ваш комментарий о заполнении ключа. У вас есть путаница в битах/байтах в вашем вопросе, и я не уверен, как это применимо в любом случае, так как ваш опубликованный ключ имеет длину 89 символов (712 бит). Возможно, вы должны попробовать с 128-битным ключом/паролем, чтобы устранить это явление заполнения?

Кстати, MySQL дэвам следует отшлепать слабые крипта, есть лучшие способы растянуть пароли, чем просто обивку с нулевых байтами :(

+0

Спасибо за понимание. Ключ не мой, это стороннее приложение, с которым я взаимодействую через API :-). «Настоящий» ключ имеет ту же длину и формат, но не точную строку. Моя цель здесь не быть безопасной, просто «поговорить» с базой данных :-) Спасибо! – kolrie

0

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