我需要模仿MySQL在使用内置函数AES_ENCRYPT()和AES_DECRYPT()加密和解密字符串时所做的事情。
我已经阅读了几篇博文,显然MySQL使用AES 128位加密来实现这些功能。最重要的是,由于此加密需要16位密钥,因此MySQL会使用x0字符(\ 0s)填充字符串,直到它的大小为16位。
发现了来自MySQL源代码的C中的算法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##"
但它确实不起作用。
有没有人知道我如何模仿Ruby中的MySQL AES_ENCRYPT()和AES_DECRYPT()函数行为?
谢谢!
答案 0 :(得分:5)
供将来参考:
根据我之前发过的博客文章,这是MySQL如何使用的 你提供的密钥AES_ENCRYPT / DECRYPT:
“算法只创建一个16字节 缓冲区设置为全零,然后循环 通过所有的人物 你提供的字符串并执行 在...之间按位OR进行赋值 两个值。如果我们迭代直到我们 我们点击了16字节缓冲区的末尾 从一开始就重新开始 做^ =。对于短于16的字符串 人物,我们停在最后 字符串“
我不知道你是否可以阅读C,但这里是提到的片段:
特别是这部分:
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
你已经定了!此外,完整的代码可以在这个要点中找到:
: - )
答案 1 :(得分:1)
通常,您不想填充密钥,填充/取消填充要加密/解密的数据。这可能是另一个问题的根源。我建议使用完整数量的块的测试数据来消除这种可能性。
另外,我怀疑OpenSSL API的密钥需要一个“文字”密钥,而不是代码中的密钥的ASCII表示。
鉴于OpenSSL ruby文档的缺乏,如果你说一点Java,你可能想在JRuby中使用BouncyCastle提供程序进行原型设计 - 这是我在使用TwoFish时所做的很好的效果(不存在于OpenSSL API)。
编辑:我重新阅读了关于填充密钥的评论。您的问题中有一些位/字节混淆,我不确定这在任何情况下都适用,因为您的发布密钥长度为89个字符(712位)。也许您应该尝试使用128位密钥/密码来消除这种填充现象?
顺便说一句,MySQL开发人员应该为弱加密打屁股,有更好的方法来扩展密码,而不是简单地用零字节填充:(
答案 2 :(得分:0)
如果您不介意使用openssl实现attr_encrypted是一个gem,它将允许对大多数类(ActiveRecord)进行嵌入式加密。遗憾的是,它与MySQL的AES_EN / DECRYPT函数不兼容。