ユーザーが取得して確認できるパスワードを保存するアプリケーションを作成しています。パスワードはハードウェア デバイス用なので、ハッシュと照合することは不可能です。
私が知りたいのは:
PHP でパスワードを暗号化および復号化するにはどうすればよいですか?
パスワードを暗号化するのに最も安全なアルゴリズムは何ですか?
秘密鍵はどこに保存しますか?
秘密鍵を保存する代わりに、パスワードの復号化が必要なときにユーザーに秘密鍵の入力を求めるのは良い考えでしょうか? (このアプリケーションのユーザーは信頼できます)
パスワードはどのような方法で盗まれ、解読されるのでしょうか? 何に注意する必要がありますか?
ベストアンサー1
個人的には、他の人が投稿したように使用しますmcrypt
。しかし、注意すべき点が他にもたくさんあります...
PHP でパスワードを暗号化および復号化するにはどうすればよいですか?
すべてを処理する強力なクラスについては、以下を参照してください。
パスワードを暗号化するのに最も安全なアルゴリズムは何ですか?
最も安全? いずれかです。暗号化する場合の最も安全な方法は、情報漏洩の脆弱性から保護することです(クロススレッド、リモートインクルードなど)。もし漏洩すれば、攻撃者は最終的に暗号を解読することができます(鍵がなければ100%解読不可能な暗号はありません - @NullUserExceptionが指摘しているように、これは完全に真実ではありません。解読不可能な暗号化方式がいくつかあります。ワンタイムパッド)。
秘密鍵はどこに保存しますか?
私は 3 つのキーを使用します。1 つはユーザーが指定するもの、1 つはアプリケーション固有のもの、もう 1 つはユーザー固有のもの (ソルトなど) です。アプリケーション固有のキーはどこにでも保存できます (Web ルート外の構成ファイル、環境変数など)。ユーザー固有のキーは、暗号化されたパスワードの隣のデータベースの列に保存されます。ユーザーが指定したキーは保存されません。次に、次のようにします。
$key = $userKey . $serverKey . $userSuppliedKey;
ここでの利点は、データが侵害されることなく、キーの2つが侵害される可能性があることです。SQLインジェクション攻撃者は を取得できます
$userKey
が、他の 2 つは取得できません。ローカル サーバーのエクスプロイトがある場合、$userKey
と を取得できます$serverKey
が、3 番目の を取得できません$userSuppliedKey
。レンチでユーザーを殴打した場合、 を取得できます$userSuppliedKey
が、他の 2 つは取得できません (ただし、ユーザーがレンチで殴打された場合は、いずれにしても手遅れです)。秘密鍵を保存する代わりに、パスワードの復号化が必要なときにユーザーに秘密鍵の入力を求めるのは良い考えでしょうか? (このアプリケーションのユーザーは信頼できます)
絶対にそうです。実際、それが私が行う唯一の方法です。そうでなければ、暗号化されていないバージョンを耐久性のあるストレージ形式(共有メモリなど)に保存する必要があります。電子計算機またはメムキャッシュ、またはセッション ファイルに保存すると、さらなる危険にさらされることになります。暗号化されていないバージョンのパスワードをローカル変数以外の場所に保存しないでください。
パスワードはどのような方法で盗まれ、解読されるのでしょうか? 何に注意する必要がありますか?
システムに侵入すれば、暗号化されたデータを見ることができます。コードを挿入したりファイルシステムにアクセスしたりできれば、復号化されたデータを見ることができます(データを復号化するファイルを編集できるため)。リプレイまたは中間者攻撃者は関連するキーに完全にアクセスできるようになります。生の 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
異なるインスタンスでもデータを適切に復号化できることを示すためであることに注意してください。
さて、それはどのように機能し、なぜ他のソリューションよりもこれを使用するのでしょうか。
- キー
キーは直接使用されません。代わりに、キーは標準の PBKDF2 派生によって拡張されます。
暗号化に使用されるキーは、暗号化されたテキスト ブロックごとに一意です。したがって、提供されたキーは「マスター キー」になります。したがって、このクラスは、暗号キーと認証キーのキー ローテーションを提供します。
重要な注意点、
$rounds
パラメータは十分な強度の真のランダムキー(最低128ビットの暗号的に安全なランダム)用に設定されます。パスワードまたは非ランダムキー(または128ビットのCSランダムよりランダムでないキー)を使用する場合は、しなければならないこのパラメータを増やします。パスワードには最低 10000 をお勧めします (余裕があればあるほど良いですが、実行時間が増加します)...
- データの整合性
- 更新バージョンでは ENCRYPT-THEN-MAC が使用され、これは暗号化されたデータの信頼性を確保するためのはるかに優れた方法です。
- 暗号化:
- 暗号化を実際に実行するために mcrypt を使用します。 モードには または cyphers と のいずれかを使用することをお勧めします
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()
が、後でストレッチされるため、そうすることに大きな意味はありません。
ストレージサイズに関しては、プレーンテキストによって異なります。フグ8 バイトのブロック サイズを使用するので、次のようになります。
- ソルト用に16バイト
- hmac の場合は 64 バイト
- データ長
- データ長 % 8 == 0 となるようにパディングする
したがって、16 文字のデータ ソースの場合、暗号化されるデータは 16 文字になります。つまり、実際の暗号化データ サイズは、パディングのため 16 バイトになります。次に、ソルト用の 16 バイトと hmac 用の 64 バイトを追加すると、保存される合計サイズは 96 バイトになります。したがって、最高でも 80 文字のオーバーヘッドがあり、最悪の場合は 87 文字のオーバーヘッドがあります...