Mimic AES_ENCRYPT e AES_DECRYPT funzioni in Ruby
-
21-08-2019 - |
Domanda
Ho bisogno di imitare ciò che fa nella codifica e decodifica MySQL stringhe utilizzando le funzioni built-in AES_ENCRYPT () e AES_DECRYPT ().
Ho letto un paio di post di blog e apparentemente MySQL utilizza la crittografia AES a 128 bit per tali funzioni. In cima a quello, dal momento che questo richiede una chiave di crittografia a 16 bit, MySQL pad la stringa con caratteri x0 (\ 0s) fino a quando è a 16 bit nel formato.
L'algoritmo in C dal codice sorgente di MySQL è macchiato qui .
Ora ho bisogno di replicare quello che MySQL fa in un'applicazione Rails, ma ogni singola cosa ho provato, non funziona.
Ecco un modo per replicare il comportamento sto ottenendo:
1) Creare una nuova applicazione Rails
rails encryption-test
cd encryption-test
2) Creazione di un nuovo ponteggio
script/generate scaffold user name:string password:binary
3) Modificare il config / database.yml e aggiungere un database MySQL test
development:
adapter: mysql
host: localhost
database: test
user: <<user>>
password: <<password>>
4) Eseguire la migrazione
rake db:migrate
5) Inserire console, creare un utente e aggiornare la sua password dalla query 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'")
Ecco dove mi sono bloccato. Se tento di decifrarlo, utilizzando MySQL funziona:
>> loaded_user = User.find_by_sql("SELECT AES_DECRYPT(password, '#{key}') AS password FROM users WHERE id=1").first
>> loaded_user['password']
=> "password"
Tuttavia, se tento di usare libreria OpenSSL, non c'è modo che posso farlo funzionare:
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"
Ho provato imbottitura la chiave:
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##"
Ma in realtà non funziona.
Qualcuno ha un indizio su come posso imitare il MySQL AES_ENCRYPT () e AES_DECRYPT () il comportamento funzioni in Ruby?
Grazie!
Soluzione
Per riferimento futuro:
Secondo il post sul blog che ho inviato prima, ecco come funziona con MySQL la chiave che fornisci AES_ENCRYPT / decifrare:
"L'algoritmo crea solo un 16 byte tamponi a tutti zero, allora loop attraverso tutti i personaggi del stringa che fornisci e fa un assegnazione con OR bit a bit tra la due valori. Se iteriamo fino a quando non colpire la fine del buffer 16 byte, abbiamo basta iniziare da capo facendo ^ =. Per le stringhe di lunghezza inferiore a 16 personaggi, ci fermiamo alla fine del stringa ".
Non so se si può leggere C, ma ecco il frammento citato:
specialmente questa parte:
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;
}
Così mi è venuta con questo metodo (con un aiuto da Rob Biedenharn, dal forum ruby):
def mysql_key(key)
final_key = "\0" * 16
key.length.times do |i|
final_key[i%16] ^= key[i]
end
final_key
end
che, data una stringa restituisce la chiave MySQL utilizza nella codifica e decodifica. Quindi tutto ciò che serve ora è:
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
Per usare OpenSSL lib, integrato in rubino, e allora si può fare i due metodi "finali":
def mysql_encrypt(s, key)
encrypt(mysql_key(key), s)
end
def mysql_decrypt(s, key)
decrypt(mysql_key(key), s)
end
E il gioco è fatto! Inoltre, il codice completo può essere trovato in questo Gist:
: -)
Altri suggerimenti
In genere non si vuole per riempire la chiave, si pad / unpad i dati da cifrare / decifrati. Questo potrebbe essere un'altra fonte di problemi. Suggerisco utilizzando i dati di prova di un numero completo di blocchi per eliminare questa possibilità.
Inoltre, ho il sospetto che la chiave per l'API OpenSSL richiede una chiave "letterale", non è una rappresentazione ASCII della chiave, come avete nel vostro codice.
data l'esiguità dei documenti rubino OpenSSL e se si parla un po 'di Java, si consiglia di prototipo in JRuby con il fornitore BouncyCastle - questa è una cosa che ho fatto con buoni risultati quando si lavora con Twofish (non presente nel OpenSSL API).
EDIT: ho riletto i suoi commenti su imbottitura della chiave. Avete qualche bit / byte confusione nella tua domanda, e io non sono sicuro di come questo si applica in ogni caso, in quanto la chiave postato è di 89 caratteri (712 bit) di lunghezza. Forse si dovrebbe provare con una chiave a 128 bit / password per eliminare questo fenomeno padding?
Per inciso, sviluppatori MySQL dovrebbero essere sculacciato per crittografica debole, ci sono modi migliori per allungare password che semplicemente imbottitura con byte nullo: (
Se non ti dispiace utilizzando un'implementazione OpenSSL attr_encrypted è un gioiello che permetterà drop-in la crittografia sulla maggior parte delle classi, ActiveRecord o no. E, purtroppo, non sarà compatibile con le funzioni AES_EN / Decrypt di MySQL però.