2012-02-08 9 views
6

Aquí están mis requisitos:¿Por qué algo cifrado en PHP no coincide con la misma cadena cifrada en Ruby?

Necesito encriptar una cadena en PHP usando encriptación AES (incluyendo un iv aleatorio), Base64 la codifica, luego la URL lo codifica para que pueda pasarse como un parámetro de URL.

Estoy tratando de obtener el mismo resultado tanto en PHP como en Ruby, pero no puedo hacerlo funcionar.

Aquí está mi código PHP:

function encryptData($data,$iv){ 
    $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 
    $iv_size = mcrypt_enc_get_iv_size($cipher); 
    if (mcrypt_generic_init($cipher, 'g6zys8dlvvut6b1omxc5w15gnfad3jhb', $iv) != -1){ 
     $cipherText = mcrypt_generic($cipher,$data); 
     mcrypt_generic_deinit($cipher); 
     return $cipherText; 
    } 
    else { 
     return false; 
    } 
} 
$data = 'Mary had a little lamb'; 
$iv = '96b88a5f0b9efb43'; 
$crypted_base64 = base64_encode(encryptData($data, $iv)); 

Aquí está mi código Ruby:

module AESCrypt 
    def AESCrypt.encrypt(data, key, iv) 
    aes = OpenSSL::Cipher::Cipher.new("aes-256-cbc") 
    aes.encrypt 
    aes.key = key 
    aes.iv = iv 
    aes.update(data) + aes.final  
    end 
end 

plaintext = "Mary had a little lamb" 
iv = "96b88a5f0b9efb43" 
@crypted = AESCrypt::encrypt(plaintext, "g6zys8dlvvut6b1omxc5w15gnfad3jhb", iv) 
@crypted_base64 = Base64.encode64(@crypted) 
@crypted_base64_url = CGI.escape(@crypted_base64) 

Lo irritante es que ambos ejemplos de código producen similares pero no idénticos valores hash. Por ejemplo, el código anterior genera (base64, no codificado en URL):

PHP: /aRCGgLBMOOAarjjtfTW2Qg2OtbPDLhx3KmgfgMzDJU=

Ruby: /aRCGgLBMOOAarjjtfTW2XIZhZ9VjBx8PdozxSL8IE0=

¿Puede alguien explicar lo que estoy haciendo mal aquí? Además, es más fácil para mí (ya que soy un tipo de Ruby, no PHP por lo general) para arreglar el código de Ruby en lugar del código de PHP. Entonces, si quisieras proporcionar una solución en Ruby que se emparejara bien con PHP, estaría muy agradecido.

Ah, y también, en producción, el iv realmente será aleatorio, pero para este ejemplo lo configuré para que sea permanentemente el mismo, de manera que se pueda comparar la salida.

EDIT:

Gracias a la respuesta de Eugen Rieck, llegué a una solución. Ruby rellena los bloques, pero PHP no, y tienes que hacerlo manualmente. Cambiar el código PHP a la siguiente, y se obtiene cuerdas cifrados que el código anterior Ruby puede descifrar fácilmente:

$iv = '96b88a5f0b9efb43'; 
$data = 'Mary had a little lamb'; 

function encryptData($data,$iv){ 
    $key = 'g6zys8dlvvut6b1omxc5w15gnfad3jhb'; 
    $padded_data = pkcs5_pad($data); 
    $cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $padded_data, MCRYPT_MODE_CBC, $iv); 
    return $cryptogram; 
} 

function pkcs5_pad ($text, $blocksize){ 
    $pad = $blocksize - (strlen($text) % $blocksize); 
    return $text . str_repeat(chr($pad), $pad); 
} 
+2

No puedo ayudar con la solución, pero creo que esto podría tener algo que ver con el relleno de bloques (ya que las cadenas de salida son las mismas para los primeros n caracteres). Es posible que desee probar agregar relleno a texto plano de forma manual en función del tamaño del bloque. – Mikk

+2

Tu código de Ruby está invocando AES-256. Su código PHP está invocando AES-128. ¿Estas seguro que esto es correcto? Tu IV es claramente para 128 ... – Charles

+0

@Charles Yo tampoco entiendo esto. Sin embargo, esta es la única forma en que funcionan las cosas.Cambiar PHP para invocar AES-256 hace que Ruby genere un error de "descifrado incorrecto" de OpenSSL. (Consulte el hilo listado en mi comentario a la respuesta a continuación, discute esto más adelante) También [este] (http://www.chilkatsoft.com/p/php_aes.asp) proporciona una explicación completa de las rarezas de encriptación de PHP. Creo que aclara tu pregunta. – John

Respuesta

8

resulta, esto es bastante fácil: El relleno es el culpable.

AES es un cifrado de bloque, por lo que funciona en bloques de tamaño fijo. Esto significa que el último bloque siempre se rellenará, y, ya sabes, lo bueno de los estándares es que hay tantos para elegir. PHP usa relleno cero, tendrás que mirar en AESCrypt para descubrir qué usa Ruby.

El mismo problema existe con las diversas bibliotecas JS AES y PHP. Terminamos siempre haciendo nuestro propio relleno, ya que esto me ha llevado a la furia rojo sangre algunas veces.

Por supuesto, esto explica por qué la primera parte (que lleva la información) es idéntica, mientras que la segunda parte (que lleva el relleno) es diferente.

+0

¿Quiere decir que PHP utiliza relleno cero (es decir, sin relleno) o que utiliza relleno cero (es decir, relleno del último bloque con 000000)? Hoy rastreé [esta pregunta] (http://stackoverflow.com/questions/1864700/part-ii-how-to-make-ruby-eses-256-cbc-and-php-mcrypt-rijndael-128 -play-well-toge) que parece indicar que Ruby rellena los bloques, pero mcrypt no. Edité mi pregunta anterior para incluir mi respuesta final. – John

+2

@John Es imposible ** no ** usar relleno: un cifrado de bloque por definición necesita un bloque completo de datos para trabajar, por lo que "no hay relleno en absoluto" no es una opción. PHP filtra un bloque incompleto con 0x00, que es una de las posibilidades, pero no la única. IIRC OpenSSL utiliza RFC1423. Ver p. http://www.chilkatsoft.com/p/p_119.asp para más información –

Cuestiones relacionadas