1. ホーム
  2. php

[解決済み] 双方向の暗号化 検索可能なパスワードを保存したい

2022-04-21 15:31:41

質問事項

私は、ユーザーが取り出して見ることができるパスワードを保存するアプリケーションを作成しています。 パスワードはハードウェアデバイスのためのものなので、ハッシュとの照合は問題外です。

私が知りたいのは

  1. PHPでパスワードを暗号化・復号化するには?

  2. パスワードを暗号化するのに一番安全なアルゴリズムは何ですか?

  3. 秘密鍵はどこに保存すればよいですか?

  4. 秘密鍵を保管する代わりに、パスワードを復号化する必要があるときに、いつでも秘密鍵の入力をユーザーに要求するのは良いアイデアですか?(このアプリケーションのユーザーは信頼できます)

  5. どのような方法でパスワードを盗まれ、解読される可能性があるのでしょうか?また、どのようなことに気をつければよいですか?

どのように解決するのですか?

個人的には mcrypt 他の方が投稿されているように しかし、注意すべき点はまだまだあります...。

  1. PHPでパスワードを暗号化・復号化するには?

    あなたのためにすべてを引き受けてくれる強力なクラスは以下をご覧ください。

  2. パスワードを暗号化するのに一番安全なアルゴリズムは何でしょうか?

    最も安全 ?のいずれかになります。 暗号化するのであれば、情報漏洩の脆弱性(XSS、リモートインクルージョンなど)から守るのが最も安全な方法です。 もしそれが漏れた場合、攻撃者は最終的に暗号を解読することができます(鍵がなければ100%元に戻らない暗号はありません - @NullUserExceptionが指摘するように、これは完全な真実ではありません。 次のような、クラックが不可能な暗号化方式もあります。 ワンタイムパッド ).

  3. 秘密鍵はどこに保存すればよいですか?

    私なら、3つの鍵を使います。 1つはユーザー提供のもの、1つはアプリケーション固有のもの、そしてもう1つはユーザー固有のもの(ソルトのようなもの)です。 アプリケーション固有の鍵は、どこにでも保存できます(ウェブルート外の設定ファイル、環境変数など)。 ユーザー固有の鍵は、データベース内の暗号化されたパスワードの隣にあるカラムに格納されます。 ユーザーが提供したものは保存されません。 それから、次のようなことをします。

    $key = $userKey . $serverKey . $userSuppliedKey;
    
    

    この利点は、データを危険にさらすことなく、2つのキーのいずれかを侵害できることです。 SQLインジェクション攻撃があった場合、彼らは $userKey しかし、他の2つはそうではありません。 ローカルサーバーが悪用された場合、彼らは $userKey$serverKey であり、3番目の $userSuppliedKey . ユーザーをスパナで殴りに行ったら $userSuppliedKey しかし、レンチを打たれてからでは遅いのです。

  4. 秘密鍵を保存する代わりに、パスワードを復号化する必要があるときはいつでも秘密鍵の入力をユーザーに要求するのは良いアイデアでしょうか?(このアプリケーションのユーザーは信頼できます)

    もちろんです。 実際、私ならその方法しかありません。 そうでなければ、暗号化されていないバージョンを耐久性のあるストレージ形式(APCやmemcachedなどの共有メモリ、またはセッションファイル)に保存する必要があります。 それは、自分自身をさらなる危険にさらすことになります。 暗号化されていないバージョンのパスワードは、ローカル変数以外には絶対に保存しないでください。

  5. パスワードはどのような方法で盗まれ、解読される可能性があるのでしょうか?また、どのようなことに気をつければよいですか?

    あなたのシステムに対するあらゆる形の侵害は、暗号化されたデータを閲覧させることになります。 もし彼らがコードを注入したり、ファイルシステムにアクセスすることができれば、暗号化されたデータを見ることができます(データを解読するファイルを編集することができるため)。 リプレイ攻撃やMITM攻撃も、暗号化された鍵に完全にアクセスできるようになります。 生のHTTPトラフィックをスニッフィングすることで、鍵の入手も可能です。

    すべてのトラフィックにSSLを使用する。 また、サーバに何らかの脆弱性(CSRF、XSS、SQLインジェクション、特権の昇格、リモートコードの実行など)がないことを確認してください。

編集する。 強力な暗号化方式のPHPクラス実装を紹介します。

/**
 * A class to handle secure encryption and decryption of arbitrary data
 *
 * Note that this is not just straight encryption.  It also has a few other
 *  features in it to make the encrypted data far more secure.  Note that any
 *  other implementations used to decrypt data will have to do the same exact
 *  operations.  
 *
 * Security Benefits:
 *
 * - Uses Key stretching
 * - Hides the Initialization Vector
 * - Does HMAC verification of source data
 *
 */
class Encryption {

    /**
     * @var string $cipher The mcrypt cipher to use for this instance
     */
    protected $cipher = '';

    /**
     * @var int $mode The mcrypt cipher mode to use
     */
    protected $mode = '';

    /**
     * @var int $rounds The number of rounds to feed into PBKDF2 for key generation
     */
    protected $rounds = 100;

    /**
     * Constructor!
     *
     * @param string $cipher The MCRYPT_* cypher to use for this instance
     * @param int    $mode   The MCRYPT_MODE_* mode to use for this instance
     * @param int    $rounds The number of PBKDF2 rounds to do on the key
     */
    public function __construct($cipher, $mode, $rounds = 100) {
        $this->cipher = $cipher;
        $this->mode = $mode;
        $this->rounds = (int) $rounds;
    }

    /**
     * Decrypt the data with the provided key
     *
     * @param string $data The encrypted datat to decrypt
     * @param string $key  The key to use for decryption
     * 
     * @returns string|false The returned string if decryption is successful
     *                           false if it is not
     */
    public function decrypt($data, $key) {
        $salt = substr($data, 0, 128);
        $enc = substr($data, 128, -64);
        $mac = substr($data, -64);

        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
             return false;
        }

        $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);

        $data = $this->unpad($dec);

        return $data;
    }

    /**
     * Encrypt the supplied data using the supplied key
     * 
     * @param string $data The data to encrypt
     * @param string $key  The key to encrypt with
     *
     * @returns string The encrypted data
     */
    public function encrypt($data, $key) {
        $salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
        list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);

        $data = $this->pad($data);

        $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);

        $mac = hash_hmac('sha512', $enc, $macKey, true);
        return $salt . $enc . $mac;
    }

    /**
     * Generates a set of keys given a random salt and a master key
     *
     * @param string $salt A random string to change the keys each encryption
     * @param string $key  The supplied key to encrypt with
     *
     * @returns array An array of keys (a cipher key, a mac key, and a IV)
     */
    protected function getKeys($salt, $key) {
        $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
        $keySize = mcrypt_get_key_size($this->cipher, $this->mode);
        $length = 2 * $keySize + $ivSize;

        $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);

        $cipherKey = substr($key, 0, $keySize);
        $macKey = substr($key, $keySize, $keySize);
        $iv = substr($key, 2 * $keySize);
        return array($cipherKey, $macKey, $iv);
    }

    /**
     * Stretch the key using the PBKDF2 algorithm
     *
     * @see http://en.wikipedia.org/wiki/PBKDF2
     *
     * @param string $algo   The algorithm to use
     * @param string $key    The key to stretch
     * @param string $salt   A random salt
     * @param int    $rounds The number of rounds to derive
     * @param int    $length The length of the output key
     *
     * @returns string The derived key.
     */
    protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
        $size   = strlen(hash($algo, '', true));
        $len    = ceil($length / $size);
        $result = '';
        for ($i = 1; $i <= $len; $i++) {
            $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
            $res = $tmp;
            for ($j = 1; $j < $rounds; $j++) {
                 $tmp  = hash_hmac($algo, $tmp, $key, true);
                 $res ^= $tmp;
            }
            $result .= $res;
        }
        return substr($result, 0, $length);
    }

    protected function pad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $padAmount = $length - strlen($data) % $length;
        if ($padAmount == 0) {
            $padAmount = $length;
        }
        return $data . str_repeat(chr($padAmount), $padAmount);
    }

    protected function unpad($data) {
        $length = mcrypt_get_block_size($this->cipher, $this->mode);
        $last = ord($data[strlen($data) - 1]);
        if ($last > $length) return false;
        if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
            return false;
        }
        return substr($data, 0, -1 * $last);
    }
}

PHP5.6で追加された関数を使用していることに注意してください。 hash_equals . 5.6 よりも低いバージョンを使っている場合は、この代用関数を使うことができます。 タイミングセーフな比較 を使用した関数です。 ダブルHMAC検証 :

function hash_equals($a, $b) {
    $key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
    return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}

使用方法

$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);

次に、復号化する。

$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);

なお、私は $e2 のように、異なるインスタンスでも適切にデータを復号できることを示すために、2回目の復号を行いました。

さて、どのように機能するのか、なぜ他のソリューションではなくこれを使用するのか。

  1. キー(鍵

    • 鍵は直接使用しない。 その代わり、鍵は標準的なPBKDF2派生によって伸張される。

    • 暗号化に使用される鍵は、暗号化されたテキストブロックごとに一意です。したがって、提供された鍵はマスターキーとなります。したがって、このクラスは暗号鍵および認証鍵のキーローテーションを提供します。

    • 重要なお知らせ を使用すると $rounds パラメータは、十分な強度の真のランダムキー(最低でも128ビットのCryptographically Secureランダム)用に設定されています。パスワードや非ランダム鍵(あるいは128ビットのCSランダムよりランダム性が低い)を使う場合は 必須 このパラメータを増やします。パスワードは最低でも10000を推奨します(余裕があればあるほどいいですが、実行時間が増えます)...。

  2. データの完全性

    • アップデート版ではENCRYPT-THEN-MACを使用しており、暗号化されたデータの真正性を確保するための方法として、はるかに優れています。
  3. 暗号化する。

    • 実際に暗号化を行うにはmcryptを使用します。 私は、以下のどちらかを使用することをお勧めします。 MCRYPT_BLOWFISH または MCRYPT_RIJNDAEL_128 サイファーと MCRYPT_MODE_CBC を使用します。 これは十分に強力で、しかもかなり高速です(私のマシンでは、暗号化と復号化のサイクルは約1/2秒かかります)。

さて、最初のリストのポイント3についてですが、そうすると、次のような関数ができます。

function makeKey($userKey, $serverKey, $userSuppliedKey) {
    $key = hash_hmac('sha512', $userKey, $serverKey);
    $key = hash_hmac('sha512', $key, $userSuppliedKey);
    return $key;
}

で伸ばせばいいんじゃない? makeKey() 関数を使用していますが、後で伸張されるため、そうすることに大きな意味はありません。

ストレージのサイズに関しては、プレーンテキストに依存します。 Blowfishは8バイトのブロックサイズを使うので、8バイトのブロックサイズを持つことになります。

  • 16バイト(ソルト
  • 64バイト:hmac
  • データ長
  • データ長 % 8 == 0 となるようにパディングを行う。

つまり、16文字のデータソースでは、暗号化されるデータは16文字になります。つまり、パディングを行うため、実際の暗号化データのサイズは16バイトになります。 これにソルトの16バイトとhmacの64バイトを加えると、保存されるサイズは合計96バイトになります。 つまり、せいぜい80文字のオーバーヘッド、最悪でも87文字のオーバーヘッドがあるわけです...。

参考になれば幸いです...。

12/11/12: このクラスを更新し、より優れた暗号化方式、より優れた派生鍵の使用、MACの生成を修正しました。