Лично я бы использовал mcrypt
как другие опубликованные. Но есть еще много чего отметить ...
Как зашифровать и расшифровать пароль в PHP?
Смотрите ниже сильный класс, который позаботится обо всем для вас:
Какой алгоритм шифрования паролей является самым безопасным?
самый безопасный ? любой из них. Самый безопасный способ шифрования - это защита от уязвимостей раскрытия информации (XSS, удаленное включение и т. Д.). Если он выходит, злоумышленник может в конечном итоге взломать шифрование (никакое шифрование не является на 100% необратимым без ключа - как указывает @NullUserException, это не совсем верно. Существуют некоторые схемы шифрования, которые невозможно взломать, например OneTimePad ) ,
Где я могу хранить закрытый ключ?
То, что я хотел бы сделать, это использовать 3 ключа. Один из них предоставляется пользователем, один - для конкретного приложения, а другой - для конкретного пользователя (например, соль). Специфический ключ приложения может храниться где угодно (в файле конфигурации вне корневого веб-узла, в переменной среды и т. Д.). Конкретный пользователь будет храниться в столбце в БД рядом с зашифрованным паролем. Предоставленный пользователем один не будет сохранен. Затем вы бы сделали что-то вроде этого:
$key = $userKey . $serverKey . $userSuppliedKey;
Преимущество заключается в том, что любые 2 ключа могут быть скомпрометированы без компрометации данных. Если есть атака SQL Injection, они могут получить $userKey
, но не другой 2. Если есть эксплойт локального сервера, они могут получить $userKey
и $serverKey
, но не третий $userSuppliedKey
. Если они избивают пользователя гаечным ключом, они могут получить $userSuppliedKey
, но не два других (но, опять же, если пользователя избивают гаечным ключом, вы все равно опоздаете).
Вместо того, чтобы хранить закрытый ключ, стоит ли требовать, чтобы пользователи вводили закрытый ключ каждый раз, когда им нужен расшифрованный пароль? (Пользователям этого приложения можно доверять)
Абсолютно. На самом деле, это единственный способ, которым я бы это сделал. В противном случае вам потребуется хранить незашифрованную версию в формате длительного хранения (совместно используемая память, такая как APC или memcached, или в файле сеанса). Это подвергает себя дополнительным компромиссам. Никогда не храните незашифрованную версию пароля ни в чем, кроме локальной переменной.
Каким образом пароль может быть украден и расшифрован? Что мне нужно знать?
Любая форма компрометации ваших систем позволит им просматривать зашифрованные данные. Если они могут внедрить код или получить доступ к вашей файловой системе, они могут просматривать расшифрованные данные (поскольку они могут редактировать файлы, которые расшифровывают данные). Любая форма Replay или 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
второй раз, чтобы показать вам, что разные экземпляры все равно будут правильно расшифровывать данные.
Теперь, как это работает / почему использовать его поверх другого решения:
Ключи
Ключи не используются напрямую. Вместо этого ключ растягивается стандартным деривацией PBKDF2.
Ключ, используемый для шифрования, уникален для каждого зашифрованного блока текста. Таким образом, предоставленный ключ становится «главным ключом». Поэтому этот класс обеспечивает поворот ключей для ключей шифрования и аутентификации.
ВАЖНОЕ ПРИМЕЧАНИЕ : $rounds
параметр настроен для истинных случайных ключей достаточной силы (как минимум 128 битов криптографически защищенных случайных ключей). Если вы собираетесь использовать пароль или неслучайный ключ (или менее случайный, чем 128 бит случайного CS), вы должны увеличить этот параметр. Я бы предложил минимум 10000 для паролей (чем больше вы можете себе позволить, тем лучше, но это увеличит время выполнения) ...
Целостность данных
- В обновленной версии используется ENCRYPT-THEN-MAC, который является гораздо лучшим методом для обеспечения подлинности зашифрованных данных.
Шифрование:
- Он использует 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-байтовый размер блока, поэтому у вас будет:
- 16 байт для соли
- 64 байта для hmac
- длина данных
- Заполнение так, чтобы длина данных% 8 == 0
Таким образом, для 16-символьного источника данных будет зашифровано 16 символов данных. Это означает, что фактический размер зашифрованных данных составляет 16 байтов из-за заполнения. Затем добавьте 16 байтов для соли и 64 байта для hmac, и общий сохраненный размер составит 96 байтов. Так что, в лучшем случае, накладные расходы в 80 символов, а в худшем - на 87 символов ...
Надеюсь, это поможет...
Примечание: 12/11/12: я только что обновил этот класс с НАМНОГО лучшим методом шифрования, используя лучшие производные ключи и исправив генерацию MAC ...