J'essaie de chiffrer/déchiffrer une chaîne à l'aide du chiffrement AES (ECB) à 128 bits. Ce que je veux savoir, c'est comment ajouter/supprimer le rembourrage PKCS7. Il semble que l'extension Mcrypt puisse prendre en charge le cryptage/décryptage, mais le remplissage doit être ajouté/supprimé manuellement.
Des idées?
Voyons voir. PKCS # 7 est décrit dans la RFC 5652 (Syntaxe de message cryptographique).
Le schéma de remplissage lui-même est donné dans la section 6.3. Processus de chiffrement de contenu . Il dit essentiellement: ajoutez autant d’octets que nécessaire pour remplir la taille de bloc donnée (mais au moins un), et chacun d’eux devrait avoir la longueur de remplissage comme valeur.
Ainsi, en regardant le dernier octet déchiffré, nous savons combien d'octets doivent être supprimés. (On peut aussi vérifier qu'ils ont tous la même valeur.)
Je pourrais maintenant vous donner une paire de PHP fonctions pour le faire, mais mon PHP est un peu rouillé. Vous pouvez donc le faire vous-même (n'hésitez pas à modifier ma réponse pour l'ajouter), ou consultez les notes fournies par l'utilisateur à la documentation de mcrypt - bon nombre d'entre elles concernent le remplissage et fournissent une implémentation de Rembourrage PKCS # 7.
Alors, regardons la première note ici en détail:
<?php
function encrypt($str, $key)
{
$block = mcrypt_get_block_size('des', 'ecb');
Cela obtient la taille de bloc de l'algorithme utilisé. Dans votre cas, vous utiliseriez aes
ou rijndael_128
au lieu de des
, je suppose (je ne l'ai pas testé). (Au lieu de cela, vous pourriez simplement prendre 16
ici pour AES, au lieu d'appeler la fonction.)
$pad = $block - (strlen($str) % $block);
Ceci calcule la taille de remplissage. strlen($str)
est la longueur de vos données (en octets), % $block
donne le reste modulo $block
, c’est-à-dire le nombre d’octets de données dans le dernier bloc. $block - ...
donne donc le nombre d'octets nécessaires pour remplir ce dernier bloc (il s'agit maintenant d'un nombre compris entre 1
et $block
inclus).
$str .= str_repeat(chr($pad), $pad);
str_repeat
produit une chaîne consistant en une répétition de la même chaîne, ici une répétition du caractère donné par$pad
, $pad
fois, c'est-à-dire une chaîne de longueur $pad
, remplie avec $pad
. . $str .= ...
ajoute cette chaîne de remplissage aux données d'origine.
return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Voici le cryptage lui-même. Utilisez MCRYPT_RIJNDAEL_128
au lieu de MCRYPT_DES
.
}
Maintenant l'autre direction:
function decrypt($str, $key)
{
$str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Le décryptage. (Vous allez bien sûr changer l'algorithme, comme ci-dessus). $ str est maintenant la chaîne déchiffrée, y compris le remplissage.
$block = mcrypt_get_block_size('des', 'ecb');
C'est encore la taille du bloc. (Voir au dessus.)
$pad = ord($str[($len = strlen($str)) - 1]);
Cela semble un peu étrange. Mieux l'écrire en plusieurs étapes:
$len = strlen($str);
$pad = ord($str[$len-1]);
$len
est maintenant la longueur de la chaîne complétée et $str[$len - 1]
est le dernier caractère de cette chaîne. ord
convertit cela en un nombre. Ainsi, $pad
est le nombre que nous avons précédemment utilisé comme valeur de remplissage pour le remplissage, et il s’agit de la longueur du remplissage.
return substr($str, 0, strlen($str) - $pad);
Alors maintenant, nous avons coupé les derniers $pad
octets de la chaîne. (Au lieu de strlen($str)
, nous pourrions aussi écrire $len
ici: substr($str, 0, $len - $pad)
.).
}
?>
Notez qu'au lieu d'utiliser substr($str, $len - $pad)
, vous pouvez également écrire substr($str, -$pad)
, car la fonction substr
dans PHP dispose d'un traitement spécial pour les opérandes/arguments négatifs, à compter de la fin de la chaîne. (Je ne sais pas si cela est plus ou moins efficace que d’obtenir la longueur d’abord et de calculer l’indice manuellement.)
Comme indiqué précédemment et noté dans le commentaire de rossum, au lieu de simplement supprimer le remplissage comme ici, vous devriez vérifier qu'il est correct - c'est-à-dire regarder substr($str, $len - $pad)
et vérifier que tous ses octets sont chr($pad)
. Ceci sert de léger contrôle contre la corruption (bien que ce contrôle soit plus efficace si vous utilisez un mode de chaînage à la place du mode ECB et ne remplace pas un vrai MAC).
(Et encore, dites à votre client qu'il devrait envisager de passer à un mode plus sécurisé que la BCE.)
J'ai créé deux méthodes pour effectuer le remplissage et le décompression. Les fonctions sont documentées avec phpdoc
et nécessitent PHP 5. Comme vous le remarquerez, la fonction unpad contient beaucoup de traitements d'exception, générant au moins 4 messages différents pour chaque erreur possible.
Pour obtenir la taille de bloc pour PHP mcrypt, vous pouvez utiliser mcrypt_get_block_size
, qui définit également la taille du bloc en octets au lieu 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);
}
Cela n'invalide en rien la réponse de Paolo Ebermann, il s'agit essentiellement de la même réponse dans le code & phpdoc au lieu de la description.
Notez que le fait de renvoyer une erreur de remplissage à un attaquant pourrait entraîner une attaque par un tampon de remplissage Oracle qui rompt complètement CBC (lorsque CBC est utilisé à la place de ECB ou d'un chiffrement authentifié sécurisé).
Appelez simplement la fonction suivante après avoir déchiffré les données
function removePadding($decryptedText){
$strPad = ord($decryptedText[strlen($decryptedText)-1]);
$decryptedText= substr($decryptedText, 0, -$strPad);
return $decryptedText;
}