Osobiście użyłbym tak, mcrypt
jak inni opublikowali. Ale jest o wiele więcej wartych uwagi ...
Jak zaszyfrować i odszyfrować hasło w PHP?
Poniżej znajdziesz silną klasę, która zajmie się wszystkim za Ciebie:
Jaki jest najbezpieczniejszy algorytm szyfrowania haseł?
najbezpieczniejszy ? ktokolwiek z nich. Najbezpieczniejszą metodą, jeśli zamierzasz szyfrować, jest ochrona przed lukami w ujawnianiu informacji (XSS, zdalne włączanie itp.). Jeśli się wydostanie, atakujący może w końcu złamać szyfrowanie (żadne szyfrowanie nie jest w 100% nieodwracalne bez klucza - jak zauważa @NullUserException, nie jest to do końca prawdą. Istnieją pewne schematy szyfrowania, których nie można złamać, takie jak OneTimePad ) .
Gdzie mogę przechowywać klucz prywatny?
Chciałbym użyć 3 kluczy. Jeden jest dostarczany przez użytkownika, jeden jest specyficzny dla aplikacji, a drugi dla użytkownika (jak sól). Klucz aplikacji może być przechowywany w dowolnym miejscu (w pliku konfiguracyjnym poza katalogiem głównym, w zmiennej środowiskowej itp.). Specyficzne dla użytkownika będzie przechowywane w kolumnie w bazie danych obok zaszyfrowanego hasła. Podany przez użytkownika nie byłby przechowywany. Następnie zrobiłbyś coś takiego:
$key = $userKey . $serverKey . $userSuppliedKey;
Zaletą jest to, że dowolne 2 klucze mogą zostać naruszone bez narażania danych. W przypadku ataku typu SQL Injection mogą zdobyć $userKey
drugie 2. Jeśli istnieje exploit na serwerze lokalnym, mogą zdobyć $userKey
i $serverKey
, ale nie trzeci $userSuppliedKey
. Jeśli pobiją użytkownika kluczem, mogą dostać $userSuppliedKey
, ale pozostałe 2 nie (ale z drugiej strony, jeśli użytkownik zostanie pobity kluczem, i tak spóźnisz się).
Czy zamiast przechowywać klucz prywatny, warto wymagać od użytkowników wprowadzenia klucza prywatnego za każdym razem, gdy potrzebują odszyfrowania hasła? (Można zaufać użytkownikom tej aplikacji)
Absolutnie. Właściwie to jedyny sposób, w jaki bym to zrobił. W przeciwnym razie musisz przechowywać niezaszyfrowaną wersję w trwałym formacie pamięci (pamięć współdzielona, taka jak APC lub memcached, lub w pliku sesji). To narażanie się na dodatkowe kompromisy. Nigdy nie przechowuj niezaszyfrowanej wersji hasła w niczym z wyjątkiem zmiennej lokalnej.
W jaki sposób można ukraść i odszyfrować hasło? Czego muszę być świadomy?
Każda forma naruszenia bezpieczeństwa Twoich systemów umożliwi im przeglądanie zaszyfrowanych danych. Jeśli mogą wstrzyknąć kod lub dostać się do systemu plików, mogą przeglądać odszyfrowane dane (ponieważ mogą edytować pliki, które odszyfrowują dane). Każda forma ataku typu Replay lub MITM zapewni im również pełny dostęp do odpowiednich kluczy. Sniffowanie nieprzetworzonego ruchu HTTP również da im klucze.
Używaj SSL dla całego ruchu. I upewnij się, że nic na serwerze nie ma jakichkolwiek luk w zabezpieczeniach (CSRF, XSS, SQL Injection, Eskalacja uprawnień, zdalne wykonanie kodu itp.).
Edycja: Oto implementacja klasy PHP silnej metody szyfrowania:
/**
* 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);
}
}
Należy pamiętać, że używam funkcji dodana w PHP 5.6: hash_equals
. Jeśli masz mniej niż 5,6, możesz użyć tej zastępczej funkcji, która implementuje funkcję porównywania z bezpiecznym czasem, używając podwójnej weryfikacji 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);
}
Stosowanie:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Następnie, aby odszyfrować:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Zwróć uwagę, że użyłem $e2
drugiego razu, aby pokazać, że różne instancje nadal będą poprawnie odszyfrowywać dane.
Teraz, jak to działa / dlaczego używać go zamiast innego rozwiązania:
Klucze
Klucze nie są używane bezpośrednio. Zamiast tego klucz jest rozciągany przez standardowe wyprowadzenie PBKDF2.
Klucz używany do szyfrowania jest unikalny dla każdego zaszyfrowanego bloku tekstu. Dlatego dostarczony klucz staje się „kluczem głównym”. Dlatego ta klasa zapewnia rotację kluczy dla kluczy szyfrujących i uwierzytelniających.
WAŻNA UWAGA , $rounds
parametr jest skonfigurowany dla prawdziwych losowych kluczy o wystarczającej sile (co najmniej 128 bitów losowych kluczy Cryptographically Secure). Jeśli zamierzasz używać hasła lub nielosowego klucza (lub mniej losowego niż 128 bitów CS losowo), musisz zwiększyć ten parametr. Sugerowałbym minimum 10000 haseł (im więcej cię stać, tym lepiej, ale doda to do środowiska uruchomieniowego) ...
Integralność danych
- Zaktualizowana wersja używa ENCRYPT-THEN-MAC, co jest znacznie lepszą metodą zapewnienia autentyczności zaszyfrowanych danych.
Szyfrowanie:
- Używa mcrypt, aby faktycznie wykonać szyfrowanie. Sugerowałbym użycie albo
MCRYPT_BLOWFISH
lub MCRYPT_RIJNDAEL_128
szyfrów i MCRYPT_MODE_CBC
dla trybu. Jest wystarczająco silny i nadal dość szybki (cykl szyfrowania i deszyfrowania zajmuje około 1/2 sekundy na moim komputerze).
Teraz, jeśli chodzi o punkt 3 z pierwszej listy, to dałoby ci taką funkcję:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Możesz go rozciągnąć w makeKey()
funkcji, ale ponieważ zostanie rozciągnięty później, nie ma to większego sensu.
Jeśli chodzi o wielkość magazynu, zależy to od zwykłego tekstu. Blowfish używa 8-bajtowego rozmiaru bloku, więc będziesz mieć:
- 16 bajtów na sól
- 64 bajty dla pliku hmac
- długość danych
- Wypełnienie tak, aby długość danych% 8 == 0
W przypadku 16-znakowego źródła danych do zaszyfrowania będzie 16 znaków. Oznacza to, że rzeczywisty rozmiar zaszyfrowanych danych wynosi 16 bajtów ze względu na wypełnienie. Następnie dodaj 16 bajtów na sól i 64 bajty na hmac, a całkowity przechowywany rozmiar wynosi 96 bajtów. Więc co najwyżej jest 80 znaków, aw najgorszym 87 znaków ...
Mam nadzieję że to pomogło...
Uwaga: 12/11/12: Właśnie zaktualizowałem tę klasę o DUŻO lepszą metodę szyfrowania, używając lepiej wyprowadzonych kluczy i naprawiając generowanie MAC ...