web-dev-qa-db-ja.com

双方向暗号化:取得できるパスワードを保存する必要があります

ユーザーが取得して表示できるパスワードを保存するアプリケーションを作成しています。パスワードはハードウェアデバイス用であるため、ハッシュに対するチェックは問題外です。

私が知る必要があるのは:

  1. PHPでパスワードを暗号化および復号化するにはどうすればよいですか?

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

  3. 秘密鍵はどこに保存しますか?

  4. 秘密キーを保存する代わりに、パスワードを復号化する必要がある場合は常にユーザーに秘密キーの入力を要求することをお勧めしますか? (このアプリケーションのユーザーは信頼できます)

  5. パスワードはどのようにして盗まれ、解読されますか?何に注意する必要がありますか?

170
HyderA

個人的には、他の人が投稿したようにmcryptを使用します。しかし、注意すべき点はまだまだあります...

  1. PHPでパスワードを暗号化および復号化するにはどうすればよいですか?

    あなたのためにすべてを処理する強力なクラスについては、以下を参照してください:

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

    safest?それらのいずれか。暗号化する場合の最も安全な方法は、情報漏えいの脆弱性(XSS、リモートインクルージョンなど)から保護することです。それが外に出れば、攻撃者は最終的に暗号化を解読できます(暗号化はキーなしでは100%不可逆ではありません-@NullUserExceptionが指摘するように、これは完全に真実ではありません。 OneTimePad )。

  3. 秘密鍵はどこに保存しますか?

    私がすることは3つのキーを使用することです。 1つはユーザー指定、1つはアプリケーション固有、もう1つはユーザー固有(ソルトなど)です。アプリケーション固有のキーはどこにでも保存できます(web-rootの外部の設定ファイル、環境変数など)。ユーザー固有のパスワードは、暗号化されたパスワードの横のdbの列に保存されます。ユーザーが指定したものは保存されません。次に、次のようなことをします。

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

    そこでの利点は、データを危険にさらすことなく、任意の2つのキーを危険にさらすことができることです。 SQLインジェクション攻撃がある場合、それらは$userKeyを取得できますが、他の2は取得できません。ローカルサーバーの悪用がある場合、$userKey$serverKeyを取得できますが、3番目の$userSuppliedKeyは取得できません。彼らがレンチでユーザーを倒すと、彼らは$userSuppliedKeyを取得できますが、他の2は取得できません(しかし、ユーザーがレンチでbeatられた場合は、とにかく遅すぎます)。

  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);
    }
}

PHP 5.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);

2回目に$e2を使用して、異なるインスタンスがデータを適切に復号化することを示していることに注意してください。

さて、それはどのように機能するのか/別のソリューションでそれを使用する理由:

  1. キー

    • キーは直接使用されません。代わりに、キーは標準のPBKDF2派生によって引き伸ばされます。

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

    • 重要な注意事項$roundsパラメーターは、十分な強度の真のランダムキー(最低128ビットの暗号化セキュアランダム)に設定されます。パスワード、またはランダムでないキー(またはランダムではない128ビットのCSランダム)を使用する場合、mustこのパラメーターを増やします。パスワードには最低10000をお勧めします(余裕があればあるほど良いですが、ランタイムに追加されます)...

  2. データの整合性

    • 更新されたバージョンはENCRYPT-THEN-MACを使用します。これは、暗号化されたデータの信頼性を確保するためのはるかに優れた方法です。
  3. 暗号化:

    • Mcryptを使用して、実際に暗号化を実行します。モードにはMCRYPT_BLOWFISHまたはMCRYPT_RIJNDAEL_128 cyphersと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バイトのブロックサイズを使用するため、次のようになります。

  • ソルト用の16バイト
  • hmacの場合は64バイト
  • データ長
  • データ長%8 == 0になるようにパディングする

したがって、16文字のデータソースの場合、16文字のデータが暗号化されます。つまり、実際の暗号化されたデータサイズは、パディングのために16バイトです。次に、saltに16バイト、hmacに64バイトを追加すると、保存される合計サイズは96バイトになります。したがって、最大で80文字のオーバーヘッドがあり、最悪でも87文字のオーバーヘッドがあります...

私はそれが役立つことを願っています...

注:12/11/12:私はこのクラスを、はるかに優れた暗号化方法、より良い派生鍵の使用、およびMAC生成の修正で更新しました。 。

209
ircmaxell

PHPでパスワードを暗号化および復号化するにはどうすればよいですか?多くの暗号化アルゴリズムの1つを実装することにより。 (または多くのライブラリの1つを使用)

パスワードを暗号化する最も安全なアルゴリズムは何ですか?さまざまなアルゴリズムがありますが、どれも100%安全ではありません。しかし、それらの多くは、商業や軍事目的でも十分に安全です

秘密鍵はどこに保存しますか?公開鍵-暗号化アルゴリズム(RSAなど)の実装を決定した場合、秘密鍵を保存しないでください。ユーザーは秘密鍵を持っています。システムには公開キーがあり、任意の場所に保存できます。

秘密鍵を保存する代わりに、パスワードを復号化する必要があるときはいつでもユーザーに秘密鍵の入力を要求することをお勧めしますか?(このアプリケーションのユーザーは信頼できます)ユーザーがとてつもなく長い素数を覚えることができたら-はい、なぜですか。しかし一般的には、ユーザーが自分の鍵をどこかに保存できるシステムを考え出す必要があります。

パスワードはどのようにして盗まれ、解読されますか?何に注意する必要がありますか?これはアルゴリズムに依存します中古。ただし、暗号化されていないパスワードをユーザーに送信したり、ユーザーから送信したりしないでください。クライアント側で暗号化/復号化するか、https(またはユーザーとサーバーとクライアント間の接続を保護する他の暗号化手段)を使用します。

ただし、暗号化された方法でパスワードを保存するだけでよい場合は、単純なXOR暗号を使用することをお勧めします。このアルゴリズムの主な問題は、周波数分析によって簡単に破損する可能性があることです。ただし、一般的にパスワードは長い英語のテキストから作成されるわけではないので、心配する必要はないと思います。 XOR暗号の2番目の問題は、暗号化された形式と復号化された形式の両方のメッセージがある場合、暗号化されたパスワードを簡単に見つけられることです。繰り返しますが、他の手段によって既に侵害されたユーザーにのみ影響を与えるため、あなたのケースでは大きな問題ではありません。

14
Ivan
  1. 後のPHP関数はMcryptです( http://www.php.net/manual/en/intro.mcrypt.php )。

この例では、マニュアルの例を少し編集しています):

<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_Rand);
$key = "This is a very secret key";
$pass = "PasswordHere";
echo strlen($pass) . "\n";

$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $pass, MCRYPT_MODE_ECB, $iv);
echo strlen($crypttext) . "\n";
?>

mcrypt_decrypt を使用してパスワードを復号化します。

  1. 最高の アルゴリズム はかなり主観的です-5人に尋ねて、5つの答えを得てください。個人的には、デフォルト(Blowfish)では十分でない場合、おそらく大きな問題があります!

  2. PHPが暗号化するために必要であることを考えると、どこに隠してもよいかわからない-これについてのコメントを歓迎します。標準のPHPベストコーディングプラクティスがもちろん適用されます!

  3. とにかく暗号化キーがコード内にあることを考えると、あなたが得るものがわからないので、アプリケーションの他の部分は安全です。

  4. 明らかに、暗号化されたパスワードと暗号化キーが盗まれた場合、ゲームオーバーになります。

私は答えにライダーを乗せました-私はPHP暗号の専門家ではありませんが、私が答えたのは標準的なプラクティスだと思います-他の人が持っているコメントを歓迎します。

12
Jon Rhoades

多くのユーザーがmcryptを使用することを提案しています...これは正しいですが、さらに一歩進めて簡単に保存して転送することが好きです(時々暗号化された値はcurlやjsonのような他の技術を使用して送信するのを難しくすることがあります) 。

Mcryptを使用して正常に暗号化した後、base64_encodeで実行し、16進コードに変換します。一度16進コードにすると、さまざまな方法で簡単に転送できます。

$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_Rand);
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$encrypted = mcrypt_generic($td, $unencrypted);
$encrypted = $ua."||||".$iv;
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encrypted = base64_encode($encrypted);
$encrypted = array_shift(unpack('H*', $encrypted));

そして反対側で:

$encrypted = pack('H*', $encrypted);
$encrypted = base64_decode($encrypted);
list($encrypted,$iv) = explode("||||",$encrypted,2);
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
$key = substr("SUPERSECRETKEY",0,mcrypt_enc_get_key_size($td));
mcrypt_generic_init($td, $key, $iv);
$unencrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
6
Bradley

ユーザーの操作なしでユーザーのパスワードを設定できるようにする場合にのみ、公開キー暗号化をお勧めします(これは、リセットや共有パスワードに便利です)。

公開鍵

  1. OpenSSL 拡張、具体的にはopenssl_public_encryptおよびopenssl_private_decrypt
  2. パスワードがキーサイズに収まると仮定すると、これはストレートRSAになります-パディング、そうでない場合は対称レイヤーが必要です
  3. 各ユーザーの両方のキーを保存します。秘密キーのパスフレーズはアプリケーションのパスワードです

対称

  1. Mcrypt 拡張
  2. AES-256はおそらく安全な賭けですが、これはそれ自体がSO質問になる可能性があります
  3. あなたはしません-これはアプリケーションのパスワードになります

両方

4。はい-ユーザーは毎回アプリケーションパスワードを入力する必要がありますが、セッションに保存すると他の問題が発生します

5

  • 誰かがアプリケーションデータを盗む場合、対称暗号と同じくらい安全です(公開鍵方式では、パスフレーズで秘密鍵を保護するために使用されます)。
  • アプリケーションは、できればクライアント証明書を使用して、SSL経由でのみアクセスできるようにする必要があります。
  • SMS経由で送信されるトークンのように、セッションごとに1回だけ使用される認証の2番目の要素を追加することを検討してください。
5
Long Ears

パスワードはハードウェアデバイス用であるため、ハッシュに対するチェックは問題外です。

え?分かりません。パスワードは回復可能でなければならないという意味ですか?

他の人が言ったように、mcrypt拡張は多くの暗号化機能へのアクセスを提供します-しかし、あなたはすべての卵を1つのバスケットに入れるようにユーザーを招待しています-バスケットは潜在的に攻撃者の標的になります-そしてあなたが知らないなら問題の解決を開始する方法は、ユーザーに損害を与えています。データを保護する方法を理解する立場にありません。

ほとんどのセキュリティの脆弱性は、基礎となるアルゴリズムに欠陥があるか安全でないためではなく、アプリケーションコード内でのアルゴリズムの使用方法に問題があるために発生します。

とはいえ、合理的に安全なシステムを構築することは可能です。

ユーザーが別の(特定の)ユーザーが読み取り可能な安全なメッセージを作成する必要がある場合にのみ、非対称暗号化を検討する必要があります。その理由は、計算コストが高いからです。ユーザーが自分のデータを入力および取得するためのリポジトリを提供したいだけであれば、対称暗号化で十分です。

ただし、メッセージを復号化するためのキーを暗号化されたメッセージと同じ場所(または暗号化されたメッセージが保存されている場所)に保存すると、システムは安全ではなくなります。復号化キーと同じトークンをユーザーの認証に使用します(または非対称暗号化の場合は、トークンを秘密キーのパスフレーズとして使用します)。少なくとも一時的に復号化が行われるサーバーにトークンを保存する必要があるため、検索不可能なセッションストレージサブストレートを使用するか、またはトークンを保存するセッションに関連付けられたデーモンに直接トークンを渡すことを検討できます。トークンをメモリに保存し、オンデマンドでメッセージの復号化を実行します。

2
symcbean

私はこのようなことを試みましたが、私は暗号作成者ではなく、phpやプログラミング言語に関する深い知識も持っていないことに注意してください。それは単なるアイデアです。私のアイデアは、keyを何らかのファイルに保存するか、database(または手動で入力)する(場所)を簡単に予測できないことです(もちろん、いつかは解読されますが、概念は解読時間を長くすることです)機密情報を暗号化します。

$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH , MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_Rand);
$key = "evenifyouaccessmydatabaseyouwillneverfindmyemail";
$text = "[email protected]";
echo "Key : ".$key."<br/>";
echo "Text : ".$text . "<br/>";
echo "Md5 : ".md5($text). "<br/>";
echo "Sha1 : ".sha1($text). "<br/>";



$crypttext = mcrypt_encrypt(MCRYPT_BLOWFISH , $key, $text, MCRYPT_MODE_ECB, $iv);
echo "Crypted Data : ".$crypttext."<br>";

$base64 = base64_encode($crypttext);
echo "Encoded Data : ".$base64."<br/>";
$decode =  base64_decode($base64);


$decryptdata = mcrypt_decrypt(MCRYPT_BLOWFISH , $key, $crypttext, MCRYPT_MODE_ECB, $iv);

echo "Decoded Data : ".ereg_replace("?", null ,  $decryptdata); 
//event if i add '?' to the sting to the text it works, I don't know why.

これは単なる概念であることに注意してください。このコードの改善は非常に理解できるでしょう。

2
Santosh Linkha

password_hash および password_verify を使用します

<?php
/**
 * In this case, we want to increase the default cost for BCRYPT to 12.
 * Note that we also switched to BCRYPT, which will always be 60 characters.
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>

解読するには:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
?>
1
jvitoroc