mcryptは非推奨ですが、代替手段は何ですか?


103

ここに投稿されたコメントによると、mcrypt-extensionは非推奨になり、PHP 7.2で削除されます。だから私はパスワードを暗号化する別の方法を探しています。

今私は何かを使っています

mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($key, true), $string, MCRYPT_MODE_CBC, $iv)

私の顧客は新しいパスワードを生成せずにパスワードを「回復」するオプションを望んでいるため、暗号化されたパスワードはもちろんPHP 7.xxでサポートされ、解読可能である必要があります。 1。


9
なぜパスワードを暗号化/復号化する必要があるのですか?なぜそれらをハッシュしてpassword_hash検証しpassword_verifyませんか?
パニックにならないでください。

3
「暗号化されたパスワードも解読可能でなければならない」 -なぜですか?安全に聞こえません。何か特別な理由は?
Funk Forty Niner、2016

24
「私の顧客は、新しいパスワードを生成せずにパスワードを「回復」するオプションを望んでいるからです。」-これは安全ではなく、代わりにパスワードをリセットするオプションを提供する必要があります。
Funk Forty Niner、2016

4
パスワードを暗号化しないでください。攻撃者がDBを取得すると、暗号化キーも取得します。ランダムなソルトを使用してHMACを約100ミリ秒反復し、ハッシュとともにソルトを保存します。password_hash、PBKDF2、Bcryptなどの関数を使用します。ポイントは、攻撃者がブルートフォースでパスワードを見つけるのに多くの時間を費やすようにすることです。
zaph

2
PHPマニュアルから->この関数は、PHP 7.1.0で廃止されました。この機能に依存することはお勧めできません。代替案はナトリウムです-> php.net/manual/en/book.sodium.php
MarcoZen

回答:


47

解読できないようにパスワードをハッシュ化することをお勧めします。これにより、データベースまたはファイルへのアクセス権を取得した可能性のある攻撃者にとって、事態は少し難しくなります。

データを暗号化して復号化する必要がある場合は、安全な暗号化/復号化のガイドがhttps://paragonie.com/white-paper/2015-secure-php-data-encryptionから入手できます。そのリンクを要約すると:

  • Libsodiumを使用する-PHP拡張
  • Libsodiumを使用できない場合は、defuse / php-encryption-ストレートPHPコードを使用してください
  • Libsodiumやdefuse / php-encryptionを使用できない場合は、OpenSSLを使用してください -多くのサーバーにはすでにインストールされています。そうでない場合は、-with-openssl [= DIR]でコンパイルできます。

1
libsodiumが一般的ではないため、opensslを最初に試す必要があります。質問の場合、すべてのネイティブ拡張が出ない限り、Raw phpを使用しないでください
JSON

OpenSSLは非常に一般的であっても、それはPHP 7はそのコア暗号化のためのlibsodiumを使用するようだ securityintelligence.com/news/...
シャディ・

1
PHPで機能するSodium-compatgithub.com/paragonie/sodium_compat)というライブラリがあることに注意してください> = 5.2.4
RaelB

30

@rqLizardで提案されているように、代わりにopenssl_encrypt/ openssl_decryptPHP関数を使用できます。これにより、Rijndael暗号化とも呼ばれるAES(高度暗号化標準)を実装するためのより優れた代替手段が提供されます。

php.netでの次のスコットのコメントに従って

2015年にデータを暗号化/暗号化するコードを記述している場合は、openssl_encrypt()およびを使用する必要がありopenssl_decrypt()ます。基盤となるライブラリ(libmcrypt)は2007年以降廃止されており、OpenSSL(AES-NI最新のプロセッサで活用され、キャッシュタイミングが安全)よりもパフォーマンスがはるかに劣ります。

また、でMCRYPT_RIJNDAEL_256はありませんAES-256。Rijndaelブロック暗号の別のバリアントです。あなたがしたい場合AES-256にはmcrypt、あなたが使用する必要がありMCRYPT_RIJNDAEL_128、32バイトのキーで。OpenSSLを使用すると、どのモードを使用するか(つまり、aes-128-cbcvs aes-256-ctr)がより明確になります。

OpenSSLは、mcryptのNULLバイトパディングではなく、CBCモードでPKCS7パディングも使用します。したがって、mcryptはOpenSSLよりもコードをOracle攻撃のパディングに対して脆弱にする可能性が高くなります。

最後に、暗号文(Encrypt Then MAC)を認証していない場合は、間違っています。

参考文献:

コード例

例1

PHP 7.1以降のGCMモードでのAES認証暗号化の例

<?php
//$key should have been previously generated in a cryptographically safe way, like openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$cipher = "aes-128-gcm";
if (in_array($cipher, openssl_get_cipher_methods()))
{
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
    //store $cipher, $iv, and $tag for decryption later
    $original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
    echo $original_plaintext."\n";
}
?>

例2

PHP 5.6以降のAES認証暗号化の例

<?php
//$key previously generated safely, ie: openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );

//decrypt later....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac))//PHP 5.6+ timing attack safe comparison
{
    echo $original_plaintext."\n";
}
?>

例3

上記の例に基づいて、ユーザーのセッションIDの暗号化を目的とする次のコードを変更しました。

class Session {

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $session_id, MCRYPT_MODE_CBC, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($encrypt);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId);
    // Decrypt the string.
    $decryptedSessionId = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decoded, MCRYPT_MODE_CBC, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, "\0");
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    return md5($this->_getSalt());
  }

  public function _getSalt() {
    return md5($this->drupal->drupalGetHashSalt());
  }

}

に:

class Session {

  const SESS_CIPHER = 'aes-128-cbc';

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $ciphertext = openssl_encrypt($session_id, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($ciphertext);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the Drupal hash salt as a key.
    $key = $this->_getSalt();
    // Get the iv.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId, TRUE);
    // Decrypt the string.
    $decryptedSessionId = openssl_decrypt($decoded, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, '\0');
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    $ivlen = openssl_cipher_iv_length(self::SESS_CIPHER);
    return substr(md5($this->_getSalt()), 0, $ivlen);
  }

  public function _getSalt() {
    return $this->drupal->drupalGetHashSalt();
  }

}

明確にするために、2つの暗号化は異なるブロックサイズと異なる暗号化データを使用するため、上記の変更は真の変換ではありません。さらに、デフォルトのパディングは異なり、MCRYPT_RIJNDAEL非標準のnullパディングのみをサポートします。@zaph


追加のメモ(@zaphのコメントから):

  • Rijndael 128MCRYPT_RIJNDAEL_128AES 同等ですが、Rijndael 256MCRYPT_RIJNDAEL_256 AES-256ではありません。これは、256が256ビットのブロックサイズを指定しているのに対し、AESは1つのブロックサイズ(128ビット)しか持たないためです。したがって、基本的に256ビット(MCRYPT_RIJNDAEL_256)のブロックサイズのRijndaelは、mcrypt開発者の選択により誤って名前が付けられています。@zaph
  • ブロックサイズが256のRijndaelは、128ビットのブロックサイズを使用する場合よりも安全性が低くなる可能性があります。第2に、AESが一般的に利用可能である一方で、ブロックサイズが256ビットのラインダールでは利用できないという点で、相互運用性が妨げられています。
  • Rijndaelのブロックサイズが異なる暗号化では、異なる暗号化データが生成されます。

    例えば、MCRYPT_RIJNDAEL_256(に相当しないAES-256)256ビットおよび鍵に渡さに基づいて、キーのサイズの大きさとラインダールブロック暗号の異なる変異体を定義する。ここで、aes-256-cbcラインダールは、のキーサイズ128ビットのブロックサイズであります256ビット。したがって、mcryptは数値を使用してブロックサイズを指定するため、異なるブロックサイズを使用して完全に異なる暗号化データを生成します。OpenSSLは数値を使用してキーサイズを指定します(AESには128ビットのブロックサイズが1つしかありません)。つまり、基本的にAESはRijndaelであり、ブロックサイズは128ビット、キーサイズは128、192、および256ビットです。したがって、OpenSSLではRijndael 128と呼ばれるAESを使用することをお勧めします。


1
一般に、ブロックサイズが256ビットのRijndaelを使用することは、mcrypt開発者の選択による誤りです。さらに、ブロックサイズが256のRijndaelは、128ビットのブロックサイズを使用する場合よりも安全性が低くなる可能性があります。さらに、AESは一般に利用可能ですが、ブロックサイズが256ビットのRijndaelはそうではないという点で、相互運用性が妨げられています。
-zaph

なんで$session_id = rtrim($decryptedSessionId, "\0");openssl_decrypt最後に不要な文字を返すことは可能ですか?暗号化された変数が0で終わる場合はどうなりencrypt("abc0")ますか(つまり?
hlscalon

@hiscalon "\0""0"NULL文字ではなく、ASCIIコードが0x00(16進数の0)です。
kiamlaluno

11

Rijndaelの純粋なPHP実装は、phpseclibがcomposerパッケージとして利用可能であり、PHP 7.3(私がテスト)で動作します。

phpseclibドキュメントには、基本的な変数(暗号、モード、キーサイズ、ビットサイズ)を入力した後にサンプルコード生成するページがあります。Rijndael、ECB、256、256について次のように出力します。

mycryptのコード

$decoded = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ENCRYPT_KEY, $term, MCRYPT_MODE_ECB);

ライブラリでこのように機能します

$rijndael = new \phpseclib\Crypt\Rijndael(\phpseclib\Crypt\Rijndael::MODE_ECB);
$rijndael->setKey(ENCRYPT_KEY);
$rijndael->setKeyLength(256);
$rijndael->disablePadding();
$rijndael->setBlockLength(256);

$decoded = $rijndael->decrypt($term);

* $termだったbase64_decoded


11

ここの他の回答で詳述されているように、私が見つけた最良のソリューションはOpenSSLを使用することです。これはPHPに組み込まれており、外部ライブラリは必要ありません。以下に簡単な例を示します。

暗号化するには:

function encrypt($key, $payload) {
  $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
  $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv);
  return base64_encode($encrypted . '::' . $iv);
}

復号化するには:

function decrypt($key, $garble) {
    list($encrypted_data, $iv) = explode('::', base64_decode($garble), 2);
    return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}

参照リンク:https : //www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/


友達に良いカルマがたくさん!たとえば、パスワードが古いコードで暗号化されている場合、新しい復号化コードはそれを確認できません。この新しいコードで再保存して暗号化する必要があります。
Lumis

簡単な移行スクリプトでその問題を解決できます。古い方法を使用して復号化し、新しい方法で暗号化して保存します。代わりの方法は、ユーザーのテーブルにフラグを追加し、パスワードの変更が必要なすべてのユーザーアカウントで強制的にパスワードをリセットするスクリプトを作成することです。
cecil merrel別名bringrainfire

8

phpseclib pollyfillパッケージを使用できます。rijndael 256での暗号化/復号化にopen sslまたはlibsodiumを使用することはできません。別の問題として、コードを置き換える必要はありません。


2
これは非常に役に立ちました。php-mcrypt拡張機能を削除する必要があり、これは魅力のように機能します。
DannyB

mcrypt_compat実行してインストールしましたがcomposer require phpseclib/mcrypt_compat、まだPHP Fatal error: Uncaught Error: Call to undefined function mcrypt_get_key_size() in /app/kohana/classes/Kohana/Encrypt.php:124php 7.2.26とKohanaフレームワークを使用しています。composerでインストールした後、実行する必要がある手順はありますか?
M-Dahab

とった。require APPPATH . '/vendor/autoload.php';の下部に追加する必要がありbootstrap.phpます。
M-Dahab

3

OpenSSLはmcrypt積極的に開発および保守されているため、OpenSSLを使用する必要があります。これにより、セキュリティ、保守性、移植性が向上します。次に、AES暗号化/復号化をはるかに高速に実行します。デフォルトではPKCS7パディングを使用しますが、OPENSSL_ZERO_PADDING必要に応じて指定できます。32バイトのバイナリキーで使用する場合は、aes-256-cbcどちらを使用するかを明確に指定できますMCRYPT_RIJNDAEL_128

Mcryptを使用したコード例を次に示します。

PKCS7パディングを使用してMcryptで記述された未認証のAES-256-CBC暗号化ライブラリ。

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeMcryptAES
{
    const CIPHER = MCRYPT_RIJNDAEL_128;

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_URANDOM);

        // Add PKCS7 Padding
        $block = mcrypt_get_block_size(self::CIPHER);
        $pad = $block - (mb_strlen($message, '8bit') % $block, '8bit');
        $message .= str_repeat(chr($pad), $pad);

        $ciphertext = mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $message,
            MCRYPT_MODE_CBC,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        $plaintext = mcrypt_decrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $ciphertext,
            MCRYPT_MODE_CBC,
            $iv
        );

        $len = mb_strlen($plaintext, '8bit');
        $pad = ord($plaintext[$len - 1]);
        if ($pad <= 0 || $pad > $block) {
            // Padding error!
            return false;
        }
        return mb_substr($plaintext, 0, $len - $pad, '8bit');
    }
}

そして、ここにOpenSSLを使用して書かれたバージョンがあります:

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeOpensslAES
{
    const METHOD = 'aes-256-cbc';

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = openssl_random_pseudo_bytes($ivsize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        return openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );
    }
}

出典:「MCRYPT」という単語をPHPコードに入力している場合は、間違っています。


2

私はこれを非常に遅く投稿していますが、PHP 7.2xバージョンで同じ問題に直面している他の誰かに役立つことを願っています。

私はこれをPHP 7.2xで使用しています。

public function make_hash($userStr){
        try{
            /** 
             * Used and tested on PHP 7.2x, Salt has been removed manually, it is now added by PHP 
             */
             return password_hash($userStr, PASSWORD_BCRYPT);
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

次に、次の関数を使用してハッシュを認証します。

public function varify_user($userStr,$hash){
        try{
            if (password_verify($userStr, $hash)) {
                 return true;
                }
            else {
                return false;
                }
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

//例:

  //create hash from user string

 $user_password = $obj->make_hash2($user_key);

このハッシュを認証するには、次のコードを使用します。

if($obj->varify_user($key, $user_key)){
      //this is correct, you can proceed with  
    }

それで全部です


1

指摘したように、ユーザーのパスワードを解読可能な形式で保存しないでください。リバーシブル暗号化は、ハッカーがユーザーのパスワードを見つけるための簡単なルートを提供します。これは、ユーザーが同じパスワードを使用する場合に、ユーザーのアカウントを他のサイトに危険にさらすことにもつながります。

PHPは、ランダムにソルト化された一方向のハッシュ暗号化のための強力な関数のペアを提供します— password_hash()password_verify()。ハッシュは自動的にランダムにソルト処理されるため、ハッカーがパスワードハッシュのプリコンパイル済みテーブルを利用してパスワードをリバースエンジニアリングする方法はありません。PASSWORD_DEFAULTオプションを設定すると、PHPの将来のバージョンでは、より強力なアルゴリズムを自動的に使用して、コードを更新することなくパスワードハッシュを生成します。



0

私の暗号オブジェクトを翻訳することができました

  • mcryptを使用してphpのコピーを取得し、古いデータを復号化します。私はに行ってきましたhttp://php.net/get/php-7.1.12.tar.gz/from/a/mirror、それは、その後、内線/ mcrypt拡張モジュール(のconfigure;メイク; make installを)を加えてコンパイル。php.iniにもextenstion = mcrypt.so行を追加する必要があったと思います。すべてのデータが暗号化されていない中間バージョンのデータを構築する一連のスクリプト。

  • opensslの公開鍵と秘密鍵を作成する

    openssl genrsa -des3 -out pkey.pem 2048
    (set a password)
    openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
  • 暗号化するには(公開鍵を使用)、openssl_sealを使用します。私が読んだことから、RSAキーを使用したopenssl_encryptは、キーの長さより11バイト少ない制限があります(Thomas Horstenによるhttp://php.net/manual/en/function.openssl-public-encrypt.phpコメントを参照)

    $pubKey = openssl_get_publickey(file_get_contents('./pkey-pub.pem'));
    openssl_seal($pwd, $sealed, $ekeys, [ $pubKey ]);
    $encryptedPassword = base64_encode($sealed);
    $key = base64_encode($ekeys[0]);

おそらく生のバイナリを保存できます。

  • 復号化するには(秘密鍵を使用)

    $passphrase="passphrase here";
    $privKey = openssl_get_privatekey(file_get_contents('./pkey.pem'), $passphrase);
    // I base64_decode() from my db columns
    openssl_open($encryptedPassword, $plain, $key, $privKey);
    echo "<h3>Password=$plain</h3>";

PS空の文字列( "")は暗号化できません

PPSこれは、ユーザー検証用ではないパスワードデータベース用です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.