Personalmente, usaría mcrypt
como otros publicados. Pero hay mucho más para notar ...
¿Cómo cifro y descifro una contraseña en PHP?
Vea a continuación una clase sólida que se encarga de todo por usted:
¿Cuál es el algoritmo más seguro para cifrar las contraseñas?
más seguro ? cualquiera de ellos. El método más seguro si va a cifrar es proteger contra vulnerabilidades de divulgación de información (XSS, inclusión remota, etc.). Si sale, el atacante eventualmente puede descifrar el cifrado (ningún cifrado es 100% irreversible sin la clave - Como @NullUserException señala que esto no es del todo cierto. Hay algunos esquemas de cifrado que son imposibles de descifrar como OneTimePad ) .
¿Dónde guardo la clave privada?
Lo que haría es usar 3 teclas. Uno es proporcionado por el usuario, uno es específico de la aplicación y el otro es específico del usuario (como una sal). La clave específica de la aplicación se puede almacenar en cualquier lugar (en un archivo de configuración fuera de la raíz web, en una variable de entorno, etc.). El usuario específico se almacenaría en una columna en la base de datos junto a la contraseña cifrada. El usuario suministrado uno no sería almacenado. Entonces, harías algo como esto:
$key = $userKey . $serverKey . $userSuppliedKey;
El beneficio allí es que cualquiera de las 2 claves puede verse comprometida sin que los datos se vean comprometidos. Si hay un ataque de inyección SQL, pueden obtener el $userKey
, pero no el otro 2. Si hay un exploit de servidor local, pueden obtener $userKey
y $serverKey
, pero no el tercero $userSuppliedKey
. Si van a golpear al usuario con una llave inglesa, pueden obtener los $userSuppliedKey
otros 2, pero no los otros 2 (pero, de nuevo, si el usuario es golpeado con una llave inglesa, de todos modos es demasiado tarde).
En lugar de almacenar la clave privada, ¿es una buena idea exigir a los usuarios que ingresen la clave privada cada vez que necesiten una contraseña descifrada? (Se puede confiar en los usuarios de esta aplicación)
Absolutamente. De hecho, esa es la única forma en que lo haría. De lo contrario, necesitaría almacenar una versión sin cifrar en un formato de almacenamiento duradero (memoria compartida como APC o memcached, o en un archivo de sesión). Eso es exponerse a compromisos adicionales. Nunca almacene la versión sin cifrar de la contraseña en otra cosa que no sea una variable local.
¿De qué maneras se puede robar y descifrar la contraseña? ¿Qué necesito tener en cuenta?
Cualquier forma de compromiso de sus sistemas les permitirá ver datos cifrados. Si pueden inyectar código o acceder a su sistema de archivos, pueden ver los datos descifrados (ya que pueden editar los archivos que descifran los datos). Cualquier forma de reproducción o ataque MITM también les dará acceso completo a las teclas involucradas. Oler el tráfico HTTP sin procesar también les dará las claves.
Use SSL para todo el tráfico. Y asegúrese de que nada en el servidor tenga ningún tipo de vulnerabilidad (CSRF, XSS, inyección de SQL, escalada de privilegios, ejecución remota de código, etc.).
Editar: Aquí hay una implementación de clase PHP de un método de cifrado fuerte:
/**
* 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);
}
}
Tenga en cuenta que estoy usando una función añadida en PHP 5.6: hash_equals
. Si está en un nivel inferior a 5.6, puede usar esta función de sustitución que implementa una función de comparación segura de tiempo usando la verificación HMAC doble :
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);
Luego, para descifrar:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Tenga en cuenta que usé $e2
la segunda vez para mostrarle que diferentes instancias aún descifrarán los datos correctamente.
Ahora, cómo funciona / por qué usarlo sobre otra solución:
Llaves
Las teclas no se usan directamente. En cambio, la clave se estira mediante una derivación PBKDF2 estándar.
La clave utilizada para el cifrado es única para cada bloque de texto cifrado. Por lo tanto, la clave suministrada se convierte en una "clave maestra". Por lo tanto, esta clase proporciona rotación de claves para claves de cifrado y autenticación.
NOTA IMPORTANTE , el $rounds
parámetro está configurado para claves aleatorias verdaderas de potencia suficiente (128 bits de aleatorización criptográfica segura como mínimo). Si va a utilizar una contraseña o una clave no aleatoria (o menos aleatoria que 128 bits de CS aleatoria), debe aumentar este parámetro. Sugeriría un mínimo de 10000 para las contraseñas (cuanto más pueda pagar, mejor, pero se agregará al tiempo de ejecución) ...
Integridad de los datos
- La versión actualizada utiliza ENCRYPT-THEN-MAC, que es un método mucho mejor para garantizar la autenticidad de los datos cifrados.
Cifrado:
- Utiliza mcrypt para realizar el cifrado. Sugeriría usar cualquiera
MCRYPT_BLOWFISH
o las MCRYPT_RIJNDAEL_128
cifras y MCRYPT_MODE_CBC
para el modo. Es lo suficientemente fuerte y sigue siendo bastante rápido (un ciclo de cifrado y descifrado tarda aproximadamente 1/2 segundo en mi máquina).
Ahora, en cuanto al punto 3 de la primera lista, lo que eso le daría es una función como esta:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Podrías estirarlo en la makeKey()
función, pero como se estirará más tarde, no hay realmente un gran punto para hacerlo.
En cuanto al tamaño de almacenamiento, depende del texto sin formato. Blowfish utiliza un tamaño de bloque de 8 bytes, por lo que tendrá:
- 16 bytes para la sal
- 64 bytes para el hmac
- longitud de datos
- Relleno para que la longitud de datos% 8 == 0
Entonces, para una fuente de datos de 16 caracteres, habrá 16 caracteres de datos para encriptar. Eso significa que el tamaño real de los datos cifrados es de 16 bytes debido al relleno. Luego agregue los 16 bytes para el salt y los 64 bytes para el hmac y el tamaño total almacenado es de 96 bytes. Entonces, en el mejor de los casos, una sobrecarga de 80 caracteres, y en el peor, una sobrecarga de 87 caracteres ...
Espero que eso ayude...
Nota: 12/12/12: Acabo de actualizar esta clase con un método de cifrado MUCHO mejor, usando mejores claves derivadas y arreglando la generación MAC ...