128ビットAES暗号化(ECB)を使用して文字列を暗号化/復号化しようとしています。私が知りたいのは、それにPKCS7パディングを追加/削除する方法です。 Mcrypt拡張機能は暗号化/復号化を処理できるようですが、パディングは手動で追加/削除する必要があります。
何か案は?
どれどれ。 PKCS#7はRFC 5652(暗号化メッセージ構文)で説明されています。
パディングスキーム自体はセクション 6.3で与えられます。コンテンツ暗号化プロセス 。それは本質的に言う:与えられたブロックサイズ(ただし少なくとも1つ)を満たすために必要なだけ多くのバイトを追加し、それらのそれぞれは値としてパディング長を持っている必要があります。
したがって、最後に復号化されたバイトを見ると、取り除くバイト数がわかります。 (それらがすべて同じ値であることを確認することもできます。)
PHP関数でこれを行うことができますが、私のPHPは少し錆びているので、自分でこれを行ってください。それを追加するために私の答えを編集してください)、またはmcryptドキュメントへの ユーザー投稿ノート を見てください-それらのかなりのいくつかはパディングに関するものであり、 PKCS#7パディングの実装を提供します。
そこで、 最初のメモ を詳しく見てみましょう:
_<?php
function encrypt($str, $key)
{
$block = mcrypt_get_block_size('des', 'ecb');
_
これは、使用されるアルゴリズムのブロックサイズを取得します。あなたの場合、aes
の代わりにdes
または_rijndael_128
_を使用するでしょう(私はテストしていません)。 (代わりに、関数を呼び出す代わりに、AESの場合は単に_16
_を使用できます。)
_ $pad = $block - (strlen($str) % $block);
_
これにより、パディングサイズが計算されます。 strlen($str)
はデータの長さ(バイト単位)、_% $block
_は剰余を_$block
_で剰余しますつまり、最後のブロックのデータバイト数。したがって、_$block - ...
_は、この最後のブロックを満たすのに必要なバイト数を示します(これは、_1
_と_$block
_の間の数値です)。
_ $str .= str_repeat(chr($pad), $pad);
_
_str_repeat
_ は、同じ文字列の繰り返し、ここでは 文字の繰り返しで構成される文字列を生成します _$pad
_、_$pad
_回、つまり_$pad
_で満たされた長さ_$pad
_の文字列で与えられます。 _$str .= ...
_は、このパディング文字列を元のデータに追加します。
_ return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
_
これが暗号化そのものです。 _MCRYPT_RIJNDAEL_128
_の代わりに _MCRYPT_DES
_ を使用します。
_ }
_
今、他の方向:
_ function decrypt($str, $key)
{
$str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
_
解読。 (もちろん、上記のようにアルゴリズムを変更します)。 $ strは、パディングを含む復号化された文字列です。
_ $block = mcrypt_get_block_size('des', 'ecb');
_
これもブロックサイズです。 (上記を参照。)
_ $pad = ord($str[($len = strlen($str)) - 1]);
_
これは少し奇妙に見えます。複数のステップで書くほうがよい:
_ $len = strlen($str);
$pad = ord($str[$len-1]);
_
_$len
_はパディングされた文字列の長さであり、_$str[$len - 1]
_はこの文字列の最後の文字です。 ord
は、これを数値に変換します。したがって、_$pad
_は、以前にパディングの塗りつぶし値として使用した数値であり、これがパディングの長さです。
_ return substr($str, 0, strlen($str) - $pad);
_
そこで、最後の_$pad
_バイトを文字列から切り取ります。 (strlen($str)
の代わりに、ここに_$len
_と書くこともできます:substr($str, 0, $len - $pad)
。)。
_ }
?>
_
PHPのsubstr
関数には特別な処理があるため、substr($str, $len - $pad)
を使用する代わりに、substr($str, -$pad)
を記述することもできます。負のオペランド/引数の場合、文字列の末尾からカウントします(これが最初に長さを取得して手動でインデックスを計算するよりも効率が良いかどうかはわかりません)。
前述のように、rossumによるコメントで述べたように、ここで行ったようにパディングを取り除くのではなく、それが正しいことを確認する必要があります。つまり、substr($str, $len - $pad)
を見て、すべてのバイトがchr($pad)
。これは、破損に対するわずかなチェックとして機能します(このチェックは、ECBの代わりにチェーンモードを使用する場合により効果的であり、実際のMACの代わりにはなりません)。
(それでもなお、ECBよりも安全なモードへの変更を検討する必要があることをクライアントに伝えます。)
パディングとパディング解除を実行する2つのメソッドを作成しました。関数はphpdoc
を使用して文書化されており、PHP 5.が必要です。unpad関数には多くの例外処理が含まれ、エラーごとに4つ以上の異なるメッセージを生成します。 。
PHP mcryptのブロックサイズを取得するには、 mcrypt_get_block_size
は、ブロックサイズをビットではなくバイト単位で定義します。
/**
* 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);
}
これは、PaŭloEbermannの回答を無効にするものではありません。説明としてではなく、コードとphpdocでの基本的に同じ回答です。
攻撃者にパディングエラーを返すと、パディングOracle攻撃が発生し、CBCが完全に破壊される可能性があることに注意してください(ECBの代わりにCBCが使用される場合、または認証された安全な場合)暗号)。
データを復号化した後、次の関数を呼び出すだけです
function removePadding($decryptedText){
$strPad = ord($decryptedText[strlen($decryptedText)-1]);
$decryptedText= substr($decryptedText, 0, -$strPad);
return $decryptedText;
}