Ответ 1
Лично я использовал бы mcrypt
, как и другие. Но есть еще много примечаний...
-
Как зашифровать и дешифровать пароль в PHP?
Ниже приведен сильный класс, который заботится обо всем для вас:
-
Какой самый безопасный алгоритм для шифрования паролей?
безопасный? любой из них. Самый безопасный метод, если вы собираетесь шифровать, - это защита от уязвимостей раскрытия информации (XSS, удаленное включение и т.д.). Если он выйдет, злоумышленник может в конечном итоге взломать шифрование (без шифрования на 100% не обратимо без ключа). Как указывает @NullUserException, это не совсем так. Существуют некоторые схемы шифрования, которые невозможно взломать, например OneTimePad).
-
Где я могу хранить закрытый ключ?
Что бы я сделал, это использовать 3 клавиши. Один из них предоставляется пользователю, один из них является специфичным для приложения, а другой - специфичным для пользователя (например, соль). Ключ, специфичный для приложения, можно хранить в любом месте (в файле конфигурации вне веб-корня, в переменной среды и т.д.). Конкретный пользователь будет храниться в столбце в db рядом с зашифрованным паролем. Пользователь, поставленный пользователем, не будет сохранен. Тогда вы сделали бы что-то вроде этого:
$key = $userKey . $serverKey . $userSuppliedKey;
Преимущество там в том, что любые 2 ключа могут быть скомпрометированы без ущерба для данных. Если есть атака SQL Injection, они могут получить
$userKey
, но не другие 2. Если есть локальный сервер, они могут получить$userKey
и$serverKey
, но не третий$userSuppliedKey
. Если они будут бить пользователя с помощью гаечного ключа, они могут получить$userSuppliedKey
, но не другие 2 (но опять же, если пользователь избит гаечным ключом, вы все равно слишком поздно). -
Вместо того, чтобы хранить закрытый ключ, рекомендуется ли пользователям вводить закрытый ключ в любое время, когда им нужен пароль, дешифрованный? (Пользователям этого приложения можно доверять)
Совершенно верно. На самом деле, это единственный способ сделать это. В противном случае вам нужно будет сохранить незашифрованную версию в формате длительного хранения (разделяемая память, такая как APC или memcached, или в файле сеанса). Это подвергает себя дополнительным компромиссам. Никогда не храните незашифрованную версию пароля ничем, кроме локальной.
-
Каким образом пароль может быть украден и дешифрован? Что мне нужно знать?
Любая форма компрометации ваших систем позволит им просматривать зашифрованные данные. Если они могут вводить код или подключаться к вашей файловой системе, они могут просматривать дешифрованные данные (поскольку они могут редактировать файлы, которые расшифровывают данные). Любая форма атаки Replay или MITM также даст им полный доступ к задействованным клавишам. Обнюхание необработанного HTTP-трафика также даст им ключи.
Использовать SSL для всего трафика. И убедитесь, что на сервере нет каких-либо уязвимостей (CSRF, XSS, SQL Injection, Privilege Escalation, Remote Code Execution и т.д.).
Изменить: Здесь реализована реализация класса 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.
-
Ключ, используемый для шифрования, уникален для каждого зашифрованного блока текста. Таким образом, поставляемый ключ становится "основным ключом". Таким образом, этот класс обеспечивает ключевое вращение для ключей шифрования и auth.
-
ВАЖНОЕ ПРИМЕЧАНИЕ, параметр
$rounds
настроен для истинных случайных ключей достаточной силы (128 бит криптографически безопасного случайного как минимум). Если вы собираетесь использовать пароль или неслучайный ключ (или менее случайные, а затем 128 бит случайного CS), вы должны увеличивать этот параметр. Я бы предложил минимум 10000 для паролей (чем больше вы можете позволить себе, тем лучше, но это добавит во время выполнения)...
-
-
Целостность данных
- В обновленной версии используется ENCRYPT-THEN-MAC, что является гораздо лучшим способом обеспечения подлинности зашифрованных данных.
-
Шифрование:
- Он использует mcrypt для фактического выполнения шифрования. Я бы предложил использовать для
MCRYPT_BLOWFISH
илиMCRYPT_RIJNDAEL_128
cyphers иMCRYPT_MODE_CBC
. Он достаточно прочен и по-прежнему довольно быстрый (цикл шифрования и дешифрования занимает около 1/2 секунды на моей машине).
- Он использует mcrypt для фактического выполнения шифрования. Я бы предложил использовать для
Теперь, что касается пункта 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: Я только что обновил этот класс с помощью лучшего метода шифрования MUCH, используя лучшие производные ключи и исправляя генерацию MAC...