php - data - ¿Cómo agregar/eliminar el relleno PKCS7 de una cadena cifrada AES?
php encrypt aes 256 cbc (2)
Veamos. PKCS # 7 se describe en RFC 5652 (Sintaxis de mensaje criptográfico).
El propio esquema de relleno se da en la sección 6.3. Proceso de cifrado de contenido . Básicamente dice: añada 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, al mirar el último byte descifrado, sabemos cuántos bytes eliminar. (También se podría verificar 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. Por lo tanto, hágalo usted mismo (luego edite mi respuesta para agregarla), o eche un vistazo a las notas aportadas por el usuario a la documentación de mcrypt: algunas de ellas son sobre 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'');
Esto obtiene el tamaño de bloque del algoritmo utilizado. En su caso, supongo que usaría aes
o rijndael_128
lugar de des
(no lo rijndael_128
). (En cambio, simplemente podría tomar 16
aquí para AES, en lugar de invocar la función).
$pad = $block - (strlen($str) % $block);
Esto calcula el tamaño de relleno. strlen($str)
es la longitud de tus datos (en bytes), % $block
da el módulo restante $block
, es decir, el número de bytes de datos en el último bloque. $block - ...
tanto, da la cantidad de bytes necesarios para llenar este último bloque (ahora es 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 dada por $pad
, $pad
times, es decir, una cadena de longitud $pad
, rellenada con $pad
. $str .= ...
añade esta cadena de relleno a los datos originales.
return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Aquí está el cifrado en sí. Use MCRYPT_RIJNDAEL_128
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 es ahora 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 se ve un poco extraño. Mejor escribirlo en varios pasos:
$len = strlen($str);
$pad = ord($str[$len-1]);
$len
ahora es la longitud de la cadena rellenada, 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 utilizamos previamente como el valor de relleno para el relleno, y esta es la longitud de relleno.
return substr($str, 0, strlen($str) - $pad);
Entonces ahora cortamos los últimos $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)
, ya que la función substr
en PHP tiene un manejo especial para los operandos / argumentos negativos, para contar desde el final de la cadena. (No sé si esto es más o menos eficiente que obtener la longitud primero y calcular el índice manualmente).
Como se dijo antes y se menciona en el comentario de Rossum, en lugar de simplemente eliminar el relleno como se hace aquí, debe verificar que sea correcto, es decir, consulte substr($str, $len - $pad)
y compruebe que todo los bytes son chr($pad)
. Esto sirve como una pequeña comprobación contra la corrupción (aunque esta comprobación es más efectiva si utiliza un modo de encadenamiento en lugar de ECB, y no reemplaza a un MAC real).
(Y aún así, dígale a su cliente que deberían pensar en cambiar a un modo más seguro que el BCE).
Estoy tratando de cifrar / descifrar una cadena usando el cifrado AES de 128 bits (ECB). Lo que quiero saber es cómo puedo agregar / eliminar el relleno de PKCS7. Parece que la extensión Mcrypt puede encargarse del cifrado / descifrado, pero el relleno debe agregarse / eliminarse manualmente.
¿Algunas ideas?
Creé dos métodos para realizar el relleno y el relleno. Las funciones se documentan utilizando phpdoc
y requieren PHP 5. Como se observará, la función de desbloqueo contiene una gran cantidad de manejo de excepciones, generando no menos de 4 mensajes diferentes para cada posible error.
Para llegar al tamaño de bloque para PHP mcrypt, puede usar mcrypt_get_block_size
, que también define el tamaño de 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 dar como resultado un ataque de oráculo de relleno que rompa completamente el CBC (cuando se usa CBC en lugar de ECB o un cifrado seguro autenticado).