Solución:
Vamos a ver. PKCS # 7 se describe en RFC 5652 (Sintaxis de mensaje criptográfico).
El esquema de relleno en sí se da en la sección 6.3. Proceso de cifrado de contenido. Básicamente dice: agregue tantos bytes como sea necesario para llenar el tamaño de bloque dado (pero al menos uno), y cada uno de ellos debe tener la longitud de relleno como valor.
Por lo tanto, mirando el último byte descifrado, sabemos cuántos bytes eliminar. (También se puede comprobar que todos tengan el mismo valor).
Ahora podría darte un par de funciones PHP para hacer esto, pero mi PHP está un poco oxidado. Entonces, haz esto tú mismo (luego siéntete libre de editar mi respuesta para agregarla), o echa un vistazo a las notas aportadas por el usuario a la documentación de mcrypt; algunas de ellas tratan sobre el relleno y proporcionan una implementación del relleno PKCS # 7 .
Entonces, veamos la primera nota allí en detalle:
<?php
function encrypt($str, $key)
{
$block = mcrypt_get_block_size('des', 'ecb');
Obtiene el tamaño de bloque del algoritmo utilizado. En su caso, usaría aes
o rijndael_128
en lugar de des
, Supongo (no lo probé). (En su lugar, podría simplemente tomar 16
aquí para AES, en lugar de invocar la función).
$pad = $block - (strlen($str) % $block);
Esto calcula el tamaño del acolchado. strlen($str)
es la longitud de sus datos (en bytes), % $block
da el resto módulo $block
, es decir, el número de bytes de datos en el último bloque. $block - ...
así da el número de bytes necesarios para llenar este último bloque (este es ahora un número entre 1
y $block
, inclusive).
$str .= str_repeat(chr($pad), $pad);
str_repeat
produce una cadena que consiste en una repetición de la misma cadena, aquí una repetición del carácter dado por $pad
, $pad
veces, es decir, una cadena de longitud $pad
, lleno $pad
.
$str .= ...
agrega esta cadena de relleno a los datos originales.
return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Aquí está el cifrado en sí. Usar MCRYPT_RIJNDAEL_128
en lugar de MCRYPT_DES
.
}
Ahora la otra dirección:
function decrypt($str, $key)
{
$str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
El descifrado. (Por supuesto, cambiaría el algoritmo, como se indicó anteriormente). $ str ahora es la cadena descifrada, incluido el relleno.
$block = mcrypt_get_block_size('des', 'ecb');
Este es nuevamente el tamaño del bloque. (Véase más arriba.)
$pad = ord($str[($len = strlen($str)) - 1]);
Esto parece un poco extraño. Mejor escríbalo en varios pasos:
$len = strlen($str);
$pad = ord($str[$len-1]);
$len
es ahora la longitud de la cuerda acolchada, y $str[$len - 1]
es el último carácter de esta cadena. ord
convierte esto en un número. Por lo tanto $pad
es el número que usamos anteriormente como valor de relleno para el relleno, y esta es la longitud del relleno.
return substr($str, 0, strlen($str) - $pad);
Así que ahora cortamos el último $pad
bytes de la cadena. (En lugar de strlen($str)
también podríamos escribir $len
aquí: substr($str, 0, $len - $pad)
.).
}
?>
Tenga en cuenta que en lugar de usar substr($str, $len - $pad)
, también se puede escribir substr($str, -$pad)
, como el substr
La función en PHP tiene un manejo especial para operandos / argumentos negativos, para contar desde el final de la cadena. (No sé si esto es más o menos eficiente que obtener primero la longitud y calcular el índice manualmente).
Como se dijo antes y se señaló en el comentario de Rossum, en lugar de simplemente quitar el relleno como se hizo aquí, debe verificar que sea correcto, es decir, mire substr($str, $len - $pad)
y compruebe que todos sus bytes son chr($pad)
. Esto sirve como una pequeña comprobación contra la corrupción (aunque esta comprobación es más eficaz si utiliza un modo de encadenamiento en lugar de ECB, y no sustituye a un MAC real).
(Y aún así, dígale a su cliente que debería pensar en cambiar a un modo más seguro que ECB).
He creado dos métodos para realizar el relleno y el desbloqueo. Las funciones se documentan utilizando phpdoc
y requieren PHP 5. Como notará, la función unpad contiene una gran cantidad de manejo de excepciones, generando no menos de 4 mensajes diferentes para cada posible error.
Para obtener el tamaño de bloque de PHP mcrypt, puede usar mcrypt_get_block_size
, que también define el tamaño del bloque en bytes en lugar de bits.
/**
* Right-pads the data string with 1 to n bytes according to PKCS#7,
* where n is the block size.
* The size of the result is x times n, where x is at least 1.
*
* The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
* This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
*
* @param string $plaintext the plaintext encoded as a string containing bytes
* @param integer $blocksize the block size of the cipher in bytes
* @return string the padded plaintext
*/
function pkcs7pad($plaintext, $blocksize)
{
$padsize = $blocksize - (strlen($plaintext) % $blocksize);
return $plaintext . str_repeat(chr($padsize), $padsize);
}
/**
* Validates and unpads the padded plaintext according to PKCS#7.
* The resulting plaintext will be 1 to n bytes smaller depending on the amount of padding,
* where n is the block size.
*
* The user is required to make sure that plaintext and padding oracles do not apply,
* for instance by providing integrity and authenticity to the IV and ciphertext using a HMAC.
*
* Note that errors during uppadding may occur if the integrity of the ciphertext
* is not validated or if the key is incorrect. A wrong key, IV or ciphertext may all
* lead to errors within this method.
*
* The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
* This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
*
* @param string padded the padded plaintext encoded as a string containing bytes
* @param integer $blocksize the block size of the cipher in bytes
* @return string the unpadded plaintext
* @throws Exception if the unpadding failed
*/
function pkcs7unpad($padded, $blocksize)
{
$l = strlen($padded);
if ($l % $blocksize != 0)
{
throw new Exception("Padded plaintext cannot be divided by the block size");
}
$padsize = ord($padded[$l - 1]);
if ($padsize === 0)
{
throw new Exception("Zero padding found instead of PKCS#7 padding");
}
if ($padsize > $blocksize)
{
throw new Exception("Incorrect amount of PKCS#7 padding for blocksize");
}
// check the correctness of the padding bytes by counting the occurance
$padding = substr($padded, -1 * $padsize);
if (substr_count($padding, chr($padsize)) != $padsize)
{
throw new Exception("Invalid PKCS#7 padding encountered");
}
return substr($padded, 0, $l - $padsize);
}
Esto no invalida la respuesta de Paŭlo Ebermann de ninguna manera, es básicamente la misma respuesta en código y phpdoc en lugar de como descripción.
Tenga en cuenta que devolver un error de relleno a un atacante puede resultar en un ataque de oráculo de relleno que rompe completamente CBC (cuando se usa CBC en lugar de ECB o un cifrado autenticado seguro).