Personalmente, userei mcrypt
come gli altri pubblicati. Ma c'è molto altro da notare ...
Come crittografare e decrittografare una password in PHP?
Vedi sotto per una classe forte che si prende cura di tutto per te:
Qual è l'algoritmo più sicuro con cui crittografare le password?
più sicuro ? nessuno di loro. Il metodo più sicuro per crittografare è proteggere dalle vulnerabilità della divulgazione di informazioni (XSS, inclusione remota, ecc.). Se esce, l'attaccante può eventualmente decifrare la crittografia (nessuna crittografia non è reversibile al 100% senza la chiave - Come sottolinea @NullUserException questo non è del tutto vero. Ci sono alcuni schemi di crittografia che sono impossibili da decifrare come OneTimePad ) .
Dove posso archiviare la chiave privata?
Quello che vorrei fare è usare 3 chiavi. Uno è fornito dall'utente, uno è specifico dell'applicazione e l'altro è specifico dell'utente (come un sale). La chiave specifica dell'applicazione può essere archiviata ovunque (in un file di configurazione esterno alla radice Web, in una variabile ambientale, ecc.). Quello specifico dell'utente verrebbe archiviato in una colonna nel db accanto alla password crittografata. L'utente fornito non sarebbe stato memorizzato. Quindi, faresti qualcosa del genere:
$key = $userKey . $serverKey . $userSuppliedKey;
Il vantaggio è che qualsiasi 2 delle chiavi può essere compromessa senza che i dati vengano compromessi. Se c'è un attacco SQL Injection, possono ottenere $userKey
, ma non l'altro 2. Se c'è un exploit del server locale, possono ottenere $userKey
e $serverKey
, ma non il terzo $userSuppliedKey
. Se vanno a battere l'utente con una chiave inglese, possono ottenere il $userSuppliedKey
, ma non l'altro 2 (ma poi di nuovo, se l'utente viene battuto con una chiave inglese, sei comunque in ritardo).
Invece di archiviare la chiave privata, è una buona idea richiedere agli utenti di inserire la chiave privata ogni volta che hanno bisogno di una password decodificata? (Gli utenti di questa applicazione possono essere fidati)
Assolutamente. In effetti, è l'unico modo in cui lo farei. Altrimenti dovresti archiviare una versione non crittografata in un formato di archiviazione durevole (memoria condivisa come APC o memcached, o in un file di sessione). Questo ti sta esponendo a ulteriori compromessi. Non archiviare mai la versione non crittografata della password in nulla tranne una variabile locale.
In che modo la password può essere rubata e decifrata? Di cosa devo essere consapevole?
Qualsiasi forma di compromesso dei sistemi consentirà loro di visualizzare i dati crittografati. Se possono iniettare codice o accedere al tuo filesystem, possono visualizzare i dati decodificati (poiché possono modificare i file che decodificano i dati). Qualsiasi forma di attacco Replay o MITM darà anche loro pieno accesso alle chiavi coinvolte. Anche annusare il traffico HTTP non elaborato fornirà loro le chiavi.
Usa SSL per tutto il traffico. E assicurati che nulla sul server presenti alcun tipo di vulnerabilità (CSRF, XSS, SQL Injection, escalazione di privilegi, esecuzione di codice in remoto, ecc.).
Modifica: ecco un'implementazione della classe PHP di un metodo di crittografia avanzato:
/**
* 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);
}
}
Si noti che sto usando una funzione di aggiunto in PHP 5.6: hash_equals
. Se hai un valore inferiore a 5.6, puoi utilizzare questa funzione sostitutiva che implementa una funzione di confronto sicura per i tempi usando la doppia verifica 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);
}
Uso:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Quindi, per decrittografare:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Nota che ho usato $e2
la seconda volta per mostrarti che diverse istanze decodificheranno ancora correttamente i dati.
Ora, come funziona / perché usarlo su un'altra soluzione:
chiavi
Le chiavi non vengono utilizzate direttamente. Invece, la chiave è allungata da una derivazione PBKDF2 standard.
La chiave utilizzata per la crittografia è unica per ogni blocco di testo crittografato. La chiave fornita diventa quindi una "chiave master". Questa classe fornisce quindi la rotazione delle chiavi per le chiavi di cifratura e autenticazione.
NOTA IMPORTANTE , il $rounds
parametro è configurato per chiavi casuali effettive di intensità sufficiente (almeno 128 bit di Cryptographically Secure random). Se si intende utilizzare una password o una chiave non casuale (o meno casuale di 128 bit di CS casuale), è necessario aumentare questo parametro. Vorrei suggerire un minimo di 10000 per le password (più puoi permetterti, meglio, ma aggiungerà al runtime) ...
Integrità dei dati
- La versione aggiornata utilizza ENCRYPT-THEN-MAC, che è un metodo molto migliore per garantire l'autenticità dei dati crittografati.
crittografia:
- Utilizza mcrypt per eseguire effettivamente la crittografia. Suggerirei di utilizzare uno
MCRYPT_BLOWFISH
o dei MCRYPT_RIJNDAEL_128
cypher e MCRYPT_MODE_CBC
per la modalità. È abbastanza forte e ancora abbastanza veloce (un ciclo di crittografia e decrittografia richiede circa 1/2 secondo sulla mia macchina).
Ora, per quanto riguarda il punto 3 dal primo elenco, ciò che ti darebbe è una funzione come questa:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Potresti allungarlo nella makeKey()
funzione, ma dal momento che verrà allungato in seguito, non c'è davvero un grosso punto nel farlo.
Per quanto riguarda le dimensioni di archiviazione, dipende dal testo normale. Blowfish utilizza una dimensione di blocco di 8 byte, quindi avrai:
- 16 byte per il sale
- 64 byte per l'hmac
- lunghezza dei dati
- Riempimento in modo che la lunghezza dei dati% 8 == 0
Quindi per un'origine dati a 16 caratteri, ci saranno 16 caratteri di dati da crittografare. Ciò significa che la dimensione effettiva dei dati crittografati è di 16 byte a causa del riempimento. Quindi aggiungere i 16 byte per il sale e 64 byte per l'hmac e la dimensione totale memorizzata è di 96 byte. Quindi nel migliore dei casi c'è un overhead di 80 caratteri, e nel peggiore dei casi un overhead di 87 caratteri ...
Spero che aiuti...
Nota: 12/11/12: ho appena aggiornato questa classe con un metodo di crittografia MOLTO migliore, usando chiavi derivate meglio e riparando la generazione MAC ...