Prefacio
Comenzando con la definición de su tabla:
- UserID
- Fname
- Lname
- Email
- Password
- IV
Aquí están los cambios:
- Los campos
Fname
, Lname
y Email
se cifrarán mediante un cifrado simétrico, proporcionado por OpenSSL ,
- El
IV
campo almacenará el vector de inicialización utilizado para el cifrado. Los requisitos de almacenamiento dependen del cifrado y el modo utilizado; más sobre esto más adelante.
- El
Password
campo se codificará con un hash de contraseña unidireccional ,
Cifrado
Cifrado y modo
La elección del mejor modo y cifrado de cifrado está más allá del alcance de esta respuesta, pero la elección final afecta el tamaño tanto de la clave de cifrado como del vector de inicialización; para esta publicación usaremos AES-256-CBC que tiene un tamaño de bloque fijo de 16 bytes y un tamaño de clave de 16, 24 o 32 bytes.
Clave de encriptación
Una buena clave de cifrado es un blob binario que se genera a partir de un generador de números aleatorios confiable. Se recomendaría el siguiente ejemplo (> = 5.3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
Esto se puede hacer una o varias veces (si desea crear una cadena de claves de cifrado). Manténgalos lo más privados posible.
IV
El vector de inicialización agrega aleatoriedad al cifrado y es necesario para el modo CBC. Lo ideal es que estos valores se utilicen solo una vez (técnicamente una vez por clave de cifrado), por lo que una actualización de cualquier parte de una fila debería regenerarla.
Se proporciona una función para ayudarlo a generar el IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Ejemplo
Encriptemos el campo de nombre, usando el anterior $encryption_key
y $iv
; para hacer esto, tenemos que rellenar nuestros datos al tamaño del bloque:
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
Requisitos de almacenamiento
La salida cifrada, como la IV, es binaria; El almacenamiento de estos valores en una base de datos se puede lograr utilizando tipos de columna designados como BINARY
o VARBINARY
.
El valor de salida, como el IV, es binario; para almacenar esos valores en MySQL, considere usar columnas BINARY
oVARBINARY
. Si esta no es una opción, también puede convertir los datos binarios en una representación textual usando base64_encode()
o bin2hex()
, hacerlo requiere entre un 33% y un 100% más de espacio de almacenamiento.
Descifrado
El descifrado de los valores almacenados es similar:
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
Cifrado autenticado
Puede mejorar aún más la integridad del texto cifrado generado agregando una firma generada a partir de una clave secreta (diferente de la clave de cifrado) y el texto cifrado. Antes de descifrar el texto cifrado, primero se verifica la firma (preferiblemente con un método de comparación de tiempo constante).
Ejemplo
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
Ver también: hash_equals()
Hash
Debe evitarse en la medida de lo posible almacenar una contraseña reversible en su base de datos; solo desea verificar la contraseña en lugar de conocer su contenido. Si un usuario pierde su contraseña, es mejor permitirle restablecerla en lugar de enviarle la original (asegúrese de que el restablecimiento de contraseña solo se pueda realizar por un tiempo limitado).
La aplicación de una función hash es una operación unidireccional; posteriormente, se puede utilizar de forma segura para la verificación sin revelar los datos originales; para las contraseñas, un método de fuerza bruta es un enfoque factible para descubrirlo debido a su longitud relativamente corta y a la mala elección de contraseñas de muchas personas.
Se crearon algoritmos de hash como MD5 o SHA1 para verificar el contenido del archivo con un valor de hash conocido. Están muy optimizados para hacer esta verificación lo más rápido posible sin dejar de ser precisa. Dado su espacio de salida relativamente limitado, fue fácil construir una base de datos con contraseñas conocidas y sus respectivas salidas hash, las tablas de arco iris.
Agregar una sal a la contraseña antes de aplicar el hash haría inútil una tabla de arco iris, pero los avances recientes del hardware hicieron que las búsquedas de fuerza bruta fueran un enfoque viable. Es por eso que necesita un algoritmo hash que sea deliberadamente lento y simplemente imposible de optimizar. También debería poder aumentar la carga para un hardware más rápido sin afectar la capacidad de verificar los hashes de contraseñas existentes para que estén a prueba de futuro.
Actualmente hay dos opciones populares disponibles:
- PBKDF2 (función de derivación de claves basada en contraseña v2)
- bcrypt (también conocido como Blowfish)
Esta respuesta usará un ejemplo con bcrypt.
Generacion
Se puede generar un hash de contraseña así:
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
La sal se genera con openssl_random_pseudo_bytes()
para formar una masa aleatoria de datos que luego se procesa base64_encode()
y strtr()
coincide con el alfabeto requerido de [A-Za-z0-9/.]
.
La crypt()
función realiza el hash basado en el algoritmo ( $2y$
para Blowfish), el factor de costo (un factor de 13 toma aproximadamente 0.40s en una máquina de 3GHz) y la sal de 22 caracteres.
Validación
Una vez que haya obtenido la fila que contiene la información del usuario, valide la contraseña de esta manera:
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
Para verificar una contraseña, crypt()
vuelve a llamar pero pasa el hash calculado previamente como el valor de sal. El valor de retorno produce el mismo hash si la contraseña dada coincide con el hash. Para verificar el hash, a menudo se recomienda utilizar una función de comparación de tiempo constante para evitar ataques de tiempo.
Hash de contraseña con PHP 5.5
PHP 5.5 introdujo las funciones de hash de contraseñas que puede utilizar para simplificar el método anterior de hash:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
Y verificando:
if (password_verify($given_password, $db_hash)) {
// password valid
}
Ver también: password_hash()
,password_verify()