¿Cómo cifrar / descifrar datos en php?

110

Actualmente soy estudiante y estoy estudiando PHP, estoy tratando de hacer un cifrado / descifrado simple de datos en PHP. Hice algunas búsquedas en línea y algunas de ellas fueron bastante confusas (al menos para mí).

Esto es lo que estoy tratando de hacer:

Tengo una tabla que consta de estos campos (UserID, Fname, Lname, Email, Password)

Lo que quiero tener es cifrar todos los campos y luego descifrarlos (¿Es posible usarlos sha256para cifrado / descifrado, si no algún algoritmo de cifrado?)

Otra cosa que quiero aprender es cómo crear una forma hash(sha256)combinada con una buena "sal". (Básicamente, solo quiero tener una implementación simple de cifrado / descifrado, hash(sha256)+salt) señor / señora, sus respuestas serían de gran ayuda y serían muy apreciadas. Gracias ++

Randel Ramírez
fuente
9
SHA es un hash, no un cifrado. El punto clave es que un hash no se puede revertir a los datos originales (no fácilmente, de todos modos). Probablemente desee mcrypt o, si no está disponible, recomendaría phpseclib , aunque es importante tener en cuenta que cualquier implementación de PHP puro de cualquier cosa que implique una gran cantidad de matemáticas de bajo nivel será muy lenta ... Por eso me gusta phpseclib, porque usa mcrypt primero si está disponible y solo recurre a las implementaciones de PHP como último recurso.
DaveRandom
7
¡Normalmente no querrás poder descifrar una contraseña!
Ja͢ck
1
Básicamente, no debería pensar en el cifrado a este nivel, debería pensar en el control de acceso, la confidencialidad, la integridad y la autenticación. Después de eso, compruebe cómo puede lograrlo, posiblemente utilizando cifrado o hash seguro. Es posible que desee leer en PBKDF2 y bcrypt / scrypt para comprender el hash seguro de contraseñas y similares.
Maarten Bodewes

Respuestas:

289

Prefacio

Comenzando con la definición de su tabla:

- UserID
- Fname
- Lname
- Email
- Password
- IV

Aquí están los cambios:

  1. Los campos Fname, Lnamey Emailse cifrarán mediante un cifrado simétrico, proporcionado por OpenSSL ,
  2. El IVcampo 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.
  3. El Passwordcampo 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_keyy $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 BINARYo VARBINARY.

El valor de salida, como el IV, es binario; para almacenar esos valores en MySQL, considere usar columnas BINARYoVARBINARY . 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:

  1. PBKDF2 (función de derivación de claves basada en contraseña v2)
  2. 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()

Jack
fuente
¿Qué longitud debo utilizar para almacenar el nombre, apellido, correo electrónico, etc. para una apuesta más segura? varbinary (???)
BentCoder
2
Claro, pero depende de cómo se use. Si publica una biblioteca de cifrado, no sabe cómo la implementarán los desarrolladores. Es por eso que github.com/defuse/php-encryption proporciona cifrado de clave simétrica autenticado y no permite que los desarrolladores lo debiliten sin editar su código.
Scott Arciszewski
2
@Scott Muy bien, he añadido un ejemplo de cifrado autenticado; gracias por el empujón :)
Ja͢ck
1
+1 para cifrado autenticado. No hay suficiente información en la pregunta para decir que AE no es necesaria aquí. Ciertamente, el tráfico de SQL a menudo pasa por una red con propiedades de seguridad desconocidas, al igual que el tráfico de la base de datos al almacenamiento. Copias de seguridad y replicación también. ¿Cuál es el modelo de amenazas? La pregunta no lo dice, y podría ser peligroso hacer suposiciones.
Jason Orendorff
1
En lugar de codificar $iv_size = 16;, usaría: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))para indicar el vínculo entre el tamaño de iv para usar con el cifrado utilizado. También puede expandir un poco la necesidad (o no) de pkcs7_pad()/ pkcs7_unpad(), o simplemente simplificar la publicación eliminándolos y usando "aes-256-ctr". Gran publicación @ Ja͢ck
Patrick Allaert
24

Creo que esto ha sido respondido antes ... pero de todos modos, si desea cifrar / descifrar datos, no puede usar SHA256

//Key
$key = 'SuperSecretKey';

//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);

//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);
romo
fuente
7
Tampoco debería utilizar ECB, de hecho.
Maarten Bodewes
7
Las claves deben ser bytes aleatorios, o debe utilizar una función de derivación de claves segura.
Maarten Bodewes
4
MCRYPT_RIJNDAEL_256 no es una función estandarizada, debe usar AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes
14

Respuesta Antecedentes y explicación

Para comprender esta pregunta, primero debe comprender qué es SHA256. SHA256 es una función hash criptográfica . Una función hash criptográfica es una función unidireccional, cuya salida es criptográficamente segura. Esto significa que es fácil calcular un hash (equivalente a encriptar datos), pero difícil obtener la entrada original usando el hash (equivalente a desencriptar los datos). Dado que el uso de una función hash criptográfica significa que el descifrado no es factible computacionalmente, por lo tanto, no puede realizar el descifrado con SHA256.

Lo que desea utilizar es una función bidireccional, pero más específicamente, un cifrado en bloque . Una función que permite tanto el cifrado como el descifrado de datos. Las funciones mcrypt_encrypty mcrypt_decryptpor defecto utilizan el algoritmo Blowfish. El uso de PHP de mcrypt se puede encontrar en este manual . También existe una lista de definiciones de cifrado para seleccionar el cifrado que utiliza mcrypt. Se puede encontrar una wiki sobre Blowfish en Wikipedia . Un cifrado de bloques cifra la entrada en bloques de tamaño y posición conocidos con una clave conocida, de modo que los datos se puedan descifrar posteriormente utilizando la clave. Esto es lo que SHA256 no puede proporcionarle.

Código

$key = 'ThisIsTheCipherKey';

$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);
cytinus
fuente
Tampoco debería utilizar ECB, de hecho.
Maarten Bodewes
Las claves deben ser bytes aleatorios, o debe utilizar una función de derivación de claves segura.
Maarten Bodewes
4
Nunca use el modo ECB. Es inseguro y la mayoría de las veces no ayuda realmente a cifrar los datos (en lugar de simplemente codificarlos). Consulte el excelente artículo de Wikipedia sobre el tema para obtener más información.
Holger Just
1
Es mejor no usar mcrypt, es abandonware, no se ha actualizado en años y no admite el relleno estándar PKCS # 7 (née PKCS # 5), solo relleno nulo no estándar que ni siquiera se puede usar con datos binarios . mcrypt tenía muchos errores pendientes que se remontan a 2003 .. En su lugar, considere usar defuse , se está manteniendo y es correcto.
zaph
9

Aquí hay un ejemplo usando openssl_encrypt

//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);

//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;
Vivek
fuente
2
En lugar de mcrypt_create_iv(), usaría:, de openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))esta manera la metodología funciona para cualquier valor de $ encryptionMethod y solo usaría la extensión openssl.
Patrick Allaert
El código anterior retornos falsepara openssl_decrypt(). Consulte stackoverflow.com/q/41952509/1066234 Dado que los cifrados de bloque como AES requieren que los datos de entrada sean un múltiplo exacto del tamaño del bloque (16 bytes para AES), es necesario el relleno.
Kai Noack
6
     function my_simple_crypt( $string, $action = 'e' ) {
        // you may change these values to your own
        $secret_key = 'my_simple_secret_key';
        $secret_iv = 'my_simple_secret_iv';

        $output = false;
        $encrypt_method = "AES-256-CBC";
        $key = hash( 'sha256', $secret_key );
        $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

        if( $action == 'e' ) {
            $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
        }
        else if( $action == 'd' ){
            $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
        }

        return $output;
    }
gauravbhai daxini
fuente
muy simple ! Lo uso para el cifrado y descifrado de segmentos de URL. Gracias
Mahbub Tito
0

Me tomó bastante tiempo descubrir cómo no obtener un falseal usar openssl_decrypt()y cómo encriptar y desencriptar funciona.

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

Si desea pasar la cadena cifrada a través de una URL, debe codificar la cadena:

    $encrypted = urlencode($encrypted);

Para comprender mejor lo que está sucediendo, lea:

Para generar claves de 16 bytes de longitud, puede utilizar:

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

Para ver los mensajes de error de openssl puede utilizar: echo openssl_error_string();

Espero que ayude.

Kai Noack
fuente