2009-03-23 7 views
2

Je dois imiter ce que fait MySQL lors du cryptage et du décryptage de chaînes à l'aide des fonctions intégrées AES_ENCRYPT() et AES_DECRYPT().Fonctions Mimic AES_ENCRYPT et AES_DECRYPT dans Ruby

J'ai lu quelques articles de blog et apparemment MySQL utilise le cryptage AES 128 bits pour ces fonctions. De plus, étant donné que ce chiffrement nécessite une clé de 16 bits, MySQL remplit la chaîne avec des caractères x0 (\ 0s) jusqu'à ce que sa taille soit de 16 bits.

L'algorithme en C du code source MySQL est repéré here.

Maintenant, j'ai besoin de répliquer ce que fait MySQL dans une application Rails, mais chaque chose que j'ai essayé ne fonctionne pas.

est ici un moyen de reproduire le comportement que je reçois:

1) Créer une nouvelle application Rails

rails encryption-test 
cd encryption-test 

2) Créer un nouvel échafaudage

script/generate scaffold user name:string password:binary 

3) Modifier votre config/database.yml et ajouter un test Base de données MySQL

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

4) Exécutez la migration

rake db:migrate 

5) Entrez console, créez un utilisateur et mettre à jour son mot de passe de MySQL requête

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'") 

C'est là que je me suis coincé. Si je tente de le déchiffrer, en utilisant MySQL cela fonctionne:

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

Toutefois, si je tente d'utiliser la bibliothèque OpenSSL, il n'y a aucun moyen que je peux le faire fonctionner:

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" 

J'ai essayé padding la clé :

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##" 

Mais cela ne fonctionne vraiment pas.

Quelqu'un at-il une idée sur comment puis-je imiter le comportement des fonctions MySQL AES_ENCRYPT() et AES_DECRYPT() dans Ruby?

Merci!

Répondre

5

Pour référence future:

Selon le blog que j'ai envoyé avant, voici comment fonctionne MySQL avec la clé que vous fournissez AES_ENCRYPT/DECRYPTER:

« l'algorithme crée juste un tampon de 16 octets réglé sur tous égaux à zéro, puis boucles throug h tous les caractères de la chaîne que vous fournissez et effectue une affectation avec un OU binaire entre les deux valeurs . Si nous itérons jusqu'à ce que nous atteignent la fin de la mémoire tampon de 16 octets, nous juste recommencer depuis le début en faisant^=. Pour les chaînes plus courtes que 16 caractères, nous nous arrêtons à la fin de la chaîne «

Je ne sais pas si vous pouvez lire C, mais voici l'extrait mentionné.

http://pastie.org/425161

spécialement cette partie:

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; 
} 

Alors je suis venu avec cette méthode (avec l'aide de Rob Biedenharn, du forum ruby):Cela, étant donné une chaîne renvoie la clé utilisée par MySQL lors du cryptage et du décryptage. Donc, tout ce dont vous avez besoin est maintenant:

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 

Pour utiliser OpenSSL lib, construit en rubis, et vous pouvez faire les deux méthodes « finales »:

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

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

Et vous êtes prêt! En outre, le code complet se trouve dans ce Gist:

http://gist.github.com/84093

:-)

+0

Cool, tu l'as eu :) –

1

Généralement, vous ne voulez pas bourrer la touche, vous pad/unpad les données à crypter/décrypter. Cela pourrait être une autre source de problèmes. Je suggère d'utiliser des données de test d'un nombre complet de blocs pour éliminer cette possibilité.

Aussi, je soupçonne que la clé de l'API OpenSSL nécessite une clé "littérale", pas une représentation ASCII de la clé comme vous l'avez dans votre code. Étant donné la rareté des docs Ruby OpenSSL et si vous parlez un peu de Java, vous pouvez vouloir prototyper dans JRuby avec le fournisseur BouncyCastle - c'est quelque chose que j'ai fait pour bien travailler avec TwoFish (pas présent) dans OpenSSL API).

EDIT: J'ai relu votre commentaire sur le remplissage de la clé. Vous avez une confusion de bits/octets dans votre question, et je ne suis pas sûr de savoir comment cela s'applique dans tous les cas puisque votre clé affichée a une longueur de 89 caractères (712 bits). Peut-être devriez-vous essayer avec une clé/mot de passe de 128 bits pour éliminer ce phénomène de remplissage?

Soit dit en passant, devs MySQL devrait être fessées pour Crypto faible, il y a de meilleures façons d'étirer les mots de passe que par un simple rembourrage avec zéro octets :(

+0

Merci pour votre avis. La clé n'est pas la mienne, c'est une application tierce avec laquelle j'interagis avec l'API :-). La clé "réelle" a exactement la même longueur et le même format, mais pas la chaîne exacte. Mon but ici n'est pas d'être sûr, juste de "parler" avec la base de données :-) Merci! – kolrie

0

Si vous ne me dérange pas en utilisant une implémentation OpenSSL attr_encrypted est un joyau qui permet le cryptage sans rendez-vous sur la plupart classes, ActiveRecord ou non. Malheureusement, il ne sera pas compatible avec les fonctions AES_EN/DECRYPT de MySQL.