我需要模仿MySQL在使用内置函数AES_ENCRYPT()和AES_DECRYPT()加密和解密字符串时所做的事情.
我已经阅读了几篇博文,显然MySQL使用AES 128位加密来实现这些功能.最重要的是,由于此加密需要16位密钥,因此MySQL会使用x0字符(\ 0s)填充字符串,直到它的大小为16位.
从MySQL源代码中的C算法在这里被发现.
现在我需要复制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: <> 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##"
但它确实不起作用.
有没有人知道我如何模仿Ruby中的MySQL AES_ENCRYPT()和AES_DECRYPT()函数行为?
谢谢!
备查:
根据我之前发过的博客文章,这里是MySQL如何使用你提供的密钥AES_ENCRYPT/DECRYPT:
"算法只创建一个16字节缓冲区设置为全零,然后循环遍历所提供的字符串的所有字符,并在两个值之间进行按位OR分配.如果我们迭代直到我们到达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; }
所以我想出了这个方法(在Roby Biedenharn的帮助下,来自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
你就定了!此外,完整的代码可以在这个要点找到:
http://gist.github.com/84093
:-)