個人的には、mcrypt
他の人が投稿したように使用します。しかし、注意すべきことは他にもたくさんあります...
PHPでパスワードを暗号化および復号化するにはどうすればよいですか?
すべてを処理する強力なクラスについては、以下を参照してください。
パスワードを暗号化する最も安全なアルゴリズムは何ですか?
最も安全ですか?それらのいずれか。暗号化する場合の最も安全な方法は、情報漏えいの脆弱性(XSS、リモートインクルードなど)から保護することです。暗号化が解除された場合、攻撃者は最終的に暗号化を解読できます(暗号化は鍵なしでは100%元に戻せません-@NullUserExceptionが指摘しているように、これは完全に真実ではありません。OneTimePadなど、解読できない暗号化スキームがいくつかあります) 。
秘密鍵はどこに保存しますか?
3つのキーを使用します。1つはユーザー指定、1つはアプリケーション固有、もう1つはユーザー固有(ソルトなど)です。アプリケーション固有のキーはどこにでも保存できます(web-rootの外部の設定ファイル、環境変数など)。ユーザー固有のものは、暗号化されたパスワードの隣のdbの列に格納されます。ユーザーが提供したものは保存されません。次に、次のようなことを行います。
$key = $userKey . $serverKey . $userSuppliedKey;
そこでの利点は、データが危険にさらされることなく、任意の2つのキーが危険にさらされる可能性があることです。SQLインジェクション攻撃があれば、彼らが得ることができる$userKey
、ではなく、他の2ローカルサーバーが不正利用がある場合、彼らが得ることができる$userKey
と$serverKey
、しかし第三ません$userSuppliedKey
。ユーザーがレンチでユーザーを倒した場合、彼らはを取得できます$userSuppliedKey
が、他の2つは取得できません(ただし、ユーザーがレンチで倒された場合は、とにかく遅すぎます)。
秘密鍵を保存する代わりに、パスワードを復号化する必要があるときはいつでもユーザーに秘密鍵の入力を要求するのは良い考えですか?(このアプリケーションのユーザーは信頼できます)
もちろんです。実際、それが私が行う唯一の方法です。それ以外の場合は、暗号化されていないバージョンを永続的なストレージ形式(APCやmemcachedなどの共有メモリ、またはセッションファイル)に保存する必要があります。これは、さらなる妥協にさらされています。暗号化されていないバージョンのパスワードは、ローカル変数以外には決して保存しないでください。
パスワードはどのように盗まれ、解読されますか?何に注意する必要がありますか?
システムが侵害されると、暗号化されたデータが表示されます。コードを挿入したり、ファイルシステムにアクセスしたりできる場合は、復号化されたデータを表示できます(データを復号化するファイルを編集できるため)。あらゆる形態のリプレイまたは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);
$e2
2回目は別のインスタンスがデータを適切に復号化することを示すために使用したことに注意してください。
今、それはどのように機能しますか/別のソリューションでそれを使用する理由:
キー
キーは直接使用されません。代わりに、キーは標準のPBKDF2派生によって拡張されます。
暗号化に使用されるキーは、暗号化されたテキストブロックごとに一意です。したがって、提供された鍵は「マスター鍵」になります。したがって、このクラスは、暗号キーと認証キーのキーローテーションを提供します。
重要な注意事項:この$rounds
パラメーターは、十分な強度を持つ真のランダムキー(最低でも128ビットの暗号で保護されたランダム)に対して構成されます。パスワードまたは非ランダムキー(またはランダムではなく128ビットのCSランダム)を使用する場合は、このパラメーターを増やす必要があります。パスワードには最低10000をお勧めします(余裕があるほど良いですが、ランタイムに追加されます)...
データの整合性
- 更新されたバージョンではENCRYPT-THEN-MACを使用しています。これは、暗号化されたデータの信頼性を保証するためのはるかに優れた方法です。
暗号化:
- 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生成を修正しました...