web-dev-qa-db-ja.com

PHP AES暗号化/復号化

PHPでen/decoding文字列の例を見つけました。最初は非常によく見えますが、機能しません:-(

誰が問題が何であるか知っていますか?

$Pass = "Passwort";
$Clear = "Klartext";

$crypted = fnEncrypt($Clear, $Pass);
echo "Encrypted: ".$crypted."</br>";

$newClear = fnDecrypt($crypted, $Pass);
echo "Decrypted: ".$newClear."</br>";

function fnEncrypt($sValue, $sSecretKey) {
    return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sDecrypted, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_Rand))));
}

function fnDecrypt($sValue, $sSecretKey) {
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sEncrypted), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_Rand)));
}

結果は次のとおりです。

暗号化済み:boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=

復号化済み:—‚(ÑÁ ^ yË~F'¸®Ó–í œð2Á_B‰Â—

46
Andreas Prang

$sDecryptedおよび$sEncryptedは、コード内で未定義でした。動作するソリューションを参照してください(が安全ではありません!):


やめる!

この例はinsecure!使用しないでください!


$Pass = "Passwort";
$Clear = "Klartext";        

$crypted = fnEncrypt($Clear, $Pass);
echo "Encrypred: ".$crypted."</br>";

$newClear = fnDecrypt($crypted, $Pass);
echo "Decrypred: ".$newClear."</br>";        

function fnEncrypt($sValue, $sSecretKey)
{
    return rtrim(
        base64_encode(
            mcrypt_encrypt(
                MCRYPT_RIJNDAEL_256,
                $sSecretKey, $sValue, 
                MCRYPT_MODE_ECB, 
                mcrypt_create_iv(
                    mcrypt_get_iv_size(
                        MCRYPT_RIJNDAEL_256, 
                        MCRYPT_MODE_ECB
                    ), 
                    MCRYPT_Rand)
                )
            ), "\0"
        );
}

function fnDecrypt($sValue, $sSecretKey)
{
    return rtrim(
        mcrypt_decrypt(
            MCRYPT_RIJNDAEL_256, 
            $sSecretKey, 
            base64_decode($sValue), 
            MCRYPT_MODE_ECB,
            mcrypt_create_iv(
                mcrypt_get_iv_size(
                    MCRYPT_RIJNDAEL_256,
                    MCRYPT_MODE_ECB
                ), 
                MCRYPT_Rand
            )
        ), "\0"
    );
}

しかし、このコードには他の問題があり、特にECB(encryptionモードではなく、暗号化モードを定義できるビルディングブロック)。最悪の問題の簡単な修正については Fab Saの回答 を、これを正しく行う方法については Scottの回答 を参照してください。

52
zz1433

既存の secure PHP暗号化ライブラリ を使用してください

一般に、他の人の暗号化の実装を破る経験がない限り、独自の暗号化を書くことは悪い考えです。

ここの例はどれもありません 暗号文を認証します 。ビット書き換え攻撃に対して脆弱です。

PECL拡張機能をインストールできる場合は、 libsodium の方が優れています

<?php
// PECL libsodium 0.2.1 and newer

/**
 * Encrypt a message
 * 
 * @param string $message - message to encrypt
 * @param string $key - encryption key
 * @return string
 */
function safeEncrypt($message, $key)
{
    $nonce = \Sodium\randombytes_buf(
        \Sodium\CRYPTO_SECRETBOX_NONCEBYTES
    );

    return base64_encode(
        $nonce.
        \Sodium\crypto_secretbox(
            $message,
            $nonce,
            $key
        )
    );
}

/**
 * Decrypt a message
 * 
 * @param string $encrypted - message encrypted with safeEncrypt()
 * @param string $key - encryption key
 * @return string
 */
function safeDecrypt($encrypted, $key)
{   
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
    $ciphertext = mb_substr($decoded, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

    return \Sodium\crypto_secretbox_open(
        $ciphertext,
        $nonce,
        $key
    );
}    

それをテストするには:

<?php
// This refers to the previous code block.
require "safeCrypto.php"; 

// Do this once then store it somehow:
$key = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';

$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

これは、エンドユーザーが解読したり、確実に改ざんしたりすることができないかなり高い確実性で、クライアントにデータを渡すあらゆる状況で使用できます(サーバー側ストレージのないセッションの暗号化されたCookie、暗号化されたURLパラメーターなど)。それと。

libsodiumはクロスプラットフォーム であるため、これによりPHPとの通信も容易になります。 Javaアプレットまたはネイティブモバイルアプリ。


注:libsodiumを使用した暗号化されたCookieをアプリに追加する必要がある場合、私の雇用主 Paragon Initiative EnterprisesHalite というライブラリを開発しています。

76

重い依存関係を使用したくない 15行のコードで解決可能なものについては、組み込みのOpenSSL関数を使用します。ほとんどのPHPインストールには、PHPで高速で互換性のある安全なAES暗号化を提供するOpenSSLが付属しています。まあ、それはあなたがベストプラクティスに従っている限り安全です。

次のコード:

  • cBCモードでAES256を使用します
  • 他のAES実装と互換性がありますが、mcryptはPKCS#7ではなくPKCS#5を使用するため、mcryptではないです。
  • sHA256を使用して、提供されたパスワードからキーを生成します
  • 整合性チェックのために暗号化されたデータのhmacハッシュを生成します
  • メッセージごとにランダムなIVを生成します
  • iV(16バイト)とハッシュ(32バイト)を暗号文の先頭に追加します
  • かなり安全でなければなりません

IVは公開情報であり、メッセージごとにランダムである必要があります。ハッシュにより、データが改ざんされていないことが保証されます。

function encrypt($plaintext, $password) {
    $method = "AES-256-CBC";
    $key = hash('sha256', $password, true);
    $iv = openssl_random_pseudo_bytes(16);

    $ciphertext = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
    $hash = hash_hmac('sha256', $ciphertext, $key, true);

    return $iv . $hash . $ciphertext;
}

function decrypt($ivHashCiphertext, $password) {
    $method = "AES-256-CBC";
    $iv = substr($ivHashCiphertext, 0, 16);
    $hash = substr($ivHashCiphertext, 16, 32);
    $ciphertext = substr($ivHashCiphertext, 48);
    $key = hash('sha256', $password, true);

    if (hash_hmac('sha256', $ciphertext, $key, true) !== $hash) return null;

    return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
}

使用法:

$encrypted = encrypt('Plaintext string.', 'password'); // this yields a binary string

echo decrypt($encrypted, 'password');
// decrypt($encrypted, 'wrong password') === null
34
blade

詳細については、MCRYPT_MODE_ECBはIV(初期化ベクトル)を使用しません。 ECBモードはメッセージをブロックに分割し、各ブロックは個別に暗号化されます。本当にお勧めしません

CBCモードはIVを使用して、各メッセージを一意にします。 CBCが推奨されており、ECBの代わりに使用する必要があります。

例:

<?php
$password = "myPassword_!";
$messageClear = "Secret message";

// 32 byte binary blob
$aes256Key = hash("SHA256", $password, true);

// for good entropy (for MCRYPT_Rand)
srand((double) microtime() * 1000000);
// generate random iv
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_Rand);


$crypted = fnEncrypt($messageClear, $aes256Key);

$newClear = fnDecrypt($crypted, $aes256Key);

echo
"IV:        <code>".$iv."</code><br/>".
"Encrypred: <code>".$crypted."</code><br/>".
"Decrypred: <code>".$newClear."</code><br/>";

function fnEncrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sValue, MCRYPT_MODE_CBC, $iv)), "\0\3");
}

function fnDecrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_CBC, $iv), "\0\3");
}

各メッセージをデコードするにはIVを保管する必要があります(IVはnotシークレットです)。各メッセージには一意のIVがあるため、各メッセージは一意です。

26
Fabien Sa

AES暗号化で注意すべき重要なことはほとんどありません。

  1. 暗号化キーとしてプレーンテキストを使用しないでください。常にプレーンテキストキーをハッシュし、暗号化に使用します。
  2. 暗号化と復号化には常にランダムIV(初期化ベクトル)を使用します。 真のランダム化が重要です。
  3. 上記のように、 ecb モードを使用せず、代わりにCBCを使用します。
3
Navneet Kumar

これはAES encryptionの有効なソリューションです-opensslを使用して実装されます。暗号ブロック連鎖モード(CBCモード)を使用します。したがって、dataおよびkeyとともに、ivおよびblock sizeを指定できます。

 <?php
      class AESEncryption {

            protected $key;
            protected $data;
            protected $method;
            protected $iv;

            /**
             * Available OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
             *
             * @var type $options
             */
            protected $options = 0;

            /**
             * 
             * @param type $data
             * @param type $key
             * @param type $iv
             * @param type $blockSize
             * @param type $mode
             */
            public function __construct($data = null, $key = null, $iv = null, $blockSize = null, $mode = 'CBC') {
                $this->setData($data);
                $this->setKey($key);
                $this->setInitializationVector($iv);
                $this->setMethod($blockSize, $mode);
            }

            /**
             * 
             * @param type $data
             */
            public function setData($data) {
                $this->data = $data;
            }

            /**
             * 
             * @param type $key
             */
            public function setKey($key) {
                $this->key = $key;
            }

            /**
             * CBC 128 192 256 
              CBC-HMAC-SHA1 128 256
              CBC-HMAC-SHA256 128 256
              CFB 128 192 256
              CFB1 128 192 256
              CFB8 128 192 256
              CTR 128 192 256
              ECB 128 192 256
              OFB 128 192 256
              XTS 128 256
             * @param type $blockSize
             * @param type $mode
             */
            public function setMethod($blockSize, $mode = 'CBC') {
                if($blockSize==192 && in_array('', array('CBC-HMAC-SHA1','CBC-HMAC-SHA256','XTS'))){
                    $this->method=null;
                    throw new Exception('Invalid block size and mode combination!');
                }
                $this->method = 'AES-' . $blockSize . '-' . $mode;
            }

            /**
             * 
             * @param type $data
             */
            public function setInitializationVector($iv) {
                $this->iv = $iv;
            }

            /**
             * 
             * @return boolean
             */
            public function validateParams() {
                if ($this->data != null &&
                        $this->method != null ) {
                    return true;
                } else {
                    return FALSE;
                }
            }

            //it must be the same when you encrypt and decrypt
            protected function getIV() { 
                return $this->iv;
            }

             /**
             * @return type
             * @throws Exception
             */
            public function encrypt() {
                if ($this->validateParams()) { 
                    return trim(openssl_encrypt($this->data, $this->method, $this->key, $this->options,$this->getIV()));
                } else {
                    throw new Exception('Invalid params!');
                }
            }

            /**
             * 
             * @return type
             * @throws Exception
             */
            public function decrypt() {
                if ($this->validateParams()) {
                   $ret=openssl_decrypt($this->data, $this->method, $this->key, $this->options,$this->getIV());

                   return   trim($ret); 
                } else {
                    throw new Exception('Invalid params!');
                }
            }

        }

サンプル使用法:

<?php
        $data = json_encode(['first_name'=>'Dunsin','last_name'=>'Olubobokun','country'=>'Nigeria']);
        $inputKey = "W92ZB837943A711B98D35E799DFE3Z18";
        $iv = "tuqZQhKP48e8Piuc";
        $blockSize = 256;
        $aes = new AESEncryption($data, $inputKey, $iv, $blockSize);
        $enc = $aes->encrypt();
        $aes->setData($enc);
        $dec=$aes->decrypt();
        echo "After encryption: ".$enc."<br/>";
        echo "After decryption: ".$dec."<br/>";
1

MCRYPT_RIJNDAEL_128を使用している場合は、rtrim($output, "\0\3")を試してください。文字列の長さが16未満の場合、復号化関数は16文字の文字列を返し、最後に03を追加します。

これは簡単に確認できます。試みることによって:

$string = "TheString";
$decrypted_string = decrypt_function($stirng, $key);

echo bin2hex($decrypted_string)."=".bin2hex("TheString");
1
Kamen

PHP> = 7.2を使用している場合、暗号化には組み込みのナトリウムコア拡張機能の使用を検討してください。

詳細については、http://php.net/manual/en/intro.sodium.phpをご覧ください。

0
M_R_K