En este artículo te voy a mostrar cómo encriptar y desencriptar datos usando PHP, para mantener los datos seguros. Veremos cómo:
- Cifrar datos
- Descifrar datos
- Cifrar datos usando la contraseña de un usuario
- Descifrar datos usando la contraseña de un usuario
Estos dos últimos ejemplos funcionan muy bien para cuando queremos asegurar la información incluso para los desarrolladores de la aplicación.
Recuerda que encriptar es distinto a hashear; porque cuando encriptamos un valor con PHP lo hacemos de esa manera para desencriptarlo más tarde. En cambio, al hashear una contraseña, su valor no es reversible.
La librería que vamos a usar para encriptar y desencriptar datos en PHP se llama php-encryption y su repositorio en GitHub lo puedes ver aquí.
Nota: voy a usar encriptar y cifrar como sinónimos para este post, al igual que desencriptar y descifrar.
Instalar php-encryption
En la consola ejecuta:
composer require defuse/php-encryption
Si no tienes composer mira aquí cómo instalarlo, y si ya tienes un proyecto iniciado, mira este post para adaptarlo.
Nota: si quieres comprobar que estás descargando el archivo real y que nadie lo interceptó en el camino puedes descargar el archivo phar y comprobar las firmas.
Después simplemente incluye el autoload (o el phar) usando:
require_once "vendor/autoload.php"
Generar clave de encriptación
Para poder cifrar y descifrar, necesitamos una clave de encriptación aleatoria, segura y lo suficientemente larga.
La librería trae consigo un ejecutable ubicado en vendor/bin/generate-defuse-key que genera esta clave aleatoria y segura.
Navega hasta esa ruta y ejecuta el binario, sin argumentos. Así:
Eso te dará una clave totalmente distinta a la mía. Ahora guarda esa clave en un lugar seguro y accesible, es decir:
- Un lugar al que solo tú tengas acceso (no otros usuarios)
- Un lugar al que puedas acceder, por ejemplo una base de datos o un archivo de texto
Nota: recuerda que cualquiera que tenga acceso a esta clave podrá descifrar y cifrar; así que guárdala bien, imagina que son las llaves de la puerta que lleva a la habitación en donde está la información encriptada.
Cifrar / encriptar datos
Ahora que ya tenemos la clave, vamos a cifrar datos. Una vez cifrados podemos guardarlos en una base de datos o en cualquier otro lugar.
Por favor veamos este ejemplo de encriptación de datos con PHP:
def0000033794dc126bf1be7994dab4ec8db7dcd8efb32308a70fa592b7a41cb231494dc6a1689639b006796bec780132bbfa1c7b7415d15d8c797ca107b86e74e9e3cba
<?php
/**
* Cifrar datos con PHP usando php-encryption
*
* https://parzibyte.me/blog
*
*/
require_once "vendor/autoload.php";
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
// Este mensaje puede venir de otro lugar
$mensajeSecreto = "Encriptando con PHP desde parzibyte.me";
// No olvides guardar la clave en un lugar seguro; aquí lo pongo así de simple para ejemplos de
// simplicidad
$contenido = file_get_contents("clave.txt");
// Cargar la clave desde una cadena ASCII (pues la clave no es tan legible ni entendible como una simple cadena)
$clave = Key::loadFromAsciiSafeString($contenido);
// Y ya podemos cifrar datos
$mensajeCifrado = Crypto::encrypt($mensajeSecreto, $clave);
// Este mensaje ya está cifrado; puedes guardarlo en la base de datos ;)
echo $mensajeCifrado;
Como ves, la clave está en un archivo de texto. De nuevo lo digo, este archivo debe estar seguro y puede ser guardado en otro lugar.
Estamos obteniendo su contenido con file_get_contents; después creamos una clave (porque debemos crear una clave a partir de la cadena ascii que guardamos) con loadFromASciiSafeString
.
El cifrado se ve en la línea 21, ya que invocamos a Crypto::encrypt($mensaje, $clave)
. Eso devolverá una cadena cifrada que ya podemos guardar en cualquier medio o base de datos.
Descifrar datos
Ahora veamos el proceso inverso: descifrar datos que previamente ciframos.
Vamos a necesitar la misma clave que usamos para cifrar / encriptar. Veamos el código para descifrar datos con PHP, y abajo lo explico:
def0000033794dc126bf1be7994dab4ec8db7dcd8efb32308a70fa592b7a41cb231494dc6a1689639b006796bec780132bbfa1c7b7415d15d8c797ca107b86e74e9e3cba
<?php
/**
* Descifrar datos con PHP usando php-encryption
*
* https://parzibyte.me/blog
*
*/
require_once "vendor/autoload.php";
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use Defuse\Crypto\Key\Exception\WrongKeyOrModifiedCiphertextException;
// El mensaje o datos cifrados; los cuales deberían recuperarse de una base de datos
$mensajeCifrado = "def5020008531ed581af5efd03d66bb886f85ceba5182ae7479af6921cc3a7a69f3418b7744bdbefe39e4d79a0e0b1ee1d89843d5ba7823ea1a9ae1c95d55f08eafe214554b8cb6667727d43768eabed8d67beb6f756cecef7d63eeecbdaf779b86c95c2aebac725f4f39ac48438c2c2859e6c8e0e5f121c29a6";
// No olvides guardar la clave en un lugar seguro; aquí lo pongo así de simple para ejemplos de
// simplicidad
$contenido = file_get_contents("clave.txt");
// Cargar la clave desde una cadena ASCII (pues la clave no es tan legible ni entendible como una simple cadena)
$clave = Key::loadFromAsciiSafeString($contenido);
// Y ya podemos descifrar datos. El método decrypt lanza una excepción si detecta
// datos corruptos o una clave incorrecta
try {
$mensajeOriginal = Crypto::decrypt($mensajeCifrado, $clave);
// Este mensaje es el original
echo $mensajeOriginal;
} catch (WrongKeyOrModifiedCiphertextException $e) {
exit("Los datos están corruptos o la clave es incorrecta");
}
El proceso de cargar la clave es el mismo. Ahora invocamos al método decrypt($mensajeCifrado, $clave)
Este método va a lanzar una excepción de tipo WrongKeyOrModifiedCiphertextException en caso de que la clave sea errónea o los datos hayan sido modificados (pues si alguien modifica la cadena encriptada, se va a detectar).
Si esta excepción ocurre, puede que alguien haya intentado modificar los datos así que ten cuidado. También puede ser porque usaste una clave errónea; fíjate bien.
Encriptando y desencriptando
Ahora veamos los ejemplos ejecutándose:
Cifrar datos con clave de usuario
Este escenario es el que más me gusta, porque así cada usuario protege sus datos y aunque un atacante robe los datos, tendrá que obtener la clave de cada uno de los usuarios.
1 – Registro de usuario
Por cierto, este método calcula el sha256 de la clave del usuario; y si la guardamos por ahí, encriptar será en vano. Es decir, simplemente por ninguna razón (porque las coincidencias existen) guardes el sha256 de las contraseñas; ya sea en logs, en bases de datos o en cualquier otro lugar.
Basta de charlas, veamos el código:
<?php
// Registrar usuario y generar clave a partir de la contraseña
// https://parzibyte.me/blog
require_once "vendor/autoload.php";
use Defuse\Crypto\KeyProtectedByPassword;
// Esta contraseña es del usuario, cuando se registra
$passwordUsuario = "hunter2";
/*
Aquí guarda al usuario, cifra su contraseña y todo
eso como normalmente lo haces
NO guardes la contraseña en texto plano dentro de la base de datos; mejor
usa un hash como bcrypt:
Cifrando y comprobando contraseñas en PHP
*/
// Si el registro es correcto, le generamos una clave, que será su clave de cifrado
// Fíjate que le pasamos la contraseña en texto plano
$claveDeUsuario = KeyProtectedByPassword::createRandomPasswordProtectedKey($passwordUsuario);
$claveDeUsuarioAscii = $claveDeUsuario->saveToAsciiSafeString();
// La clave debería ser guardada; ya sea en la base de datos o en otro lugar,
// y debe estar ligada al usuario
echo $claveDeUsuarioAscii;
Estoy simplificando mucho las cosas. Tenemos la contraseña del usuario en texto plano, esa contraseña debe ser guardada con el nombre o correo del usuario, así como normalmente se hace.
Después estamos creando una clave del usuario a partir de su contraseña; esa clave también la debemos guardar por ahí. En este caso la estoy imprimiendo.
La clave debe quedar ligada al usuario.
2 – Cuando el usuario inicia sesión
Ahora, cuando el usuario hace login, comprobamos su contraseña y hacemos todo el proceso que hacemos en un login.
En caso de que la contraseña coincida, obtenemos su clave de la base de datos (la cual fue bloqueada con su contraseña) y la desbloqueamos con su contraseña.
<?php
// Desbloquear y guardar clave de usuario, usando su contraseña
// https://parzibyte.me/blog
require_once "vendor/autoload.php";
use Defuse\Crypto\KeyProtectedByPassword;
$passwordUsuario = "hunter2";
// Aquí debes comprobar si la contraseña es correcta y todo eso; en
// caso de que no, sal del script inmediatamente
// Sacar la clave de la base de datos o de donde se haya guardado
$claveProtegidaUsuarioAscii = "def10000def50200207af95c575b163601190c1eab6e8d617adad9368c889876d4d64c92b48e0b2eb4dc97060da78cd5a737095e95fcbface352629ed5f16d6fa53c19158d846e25f4280e5c2b85cdfcefff8eb2fd37ace74d1c2ced8262584dbe86870c9ee8ef33e0efd1e9876f9141075a2da047d744eef149ee2e9d1ba89af062054dbc399c0b6d0004316843a2bd17619314f51d625fa2f2df8ad2d0ba50cca6c783eaa1e8a843264c081b25b4fc1c7eec1301bcb2c2c6c587b034e86eb2bd04e2d06b1579942514cfaac9952308c4eec25c959fbbba1b578e0cba6cebec79a61d733ab94b1410565cdd2e85e6b1879ebbb39b1314d6f75aafb5c6dd35d3";
$claveProtegidaUsuario = KeyProtectedByPassword::loadFromAsciiSafeString($claveProtegidaUsuarioAscii);
// Desbloquearla con la contraseña del usuario
$claveDesbloqueada = $claveProtegidaUsuario->unlockKey($passwordUsuario);
$claveDesbloqueadaAscii = $claveDesbloqueada->saveToAsciiSafeString();
// Ahora guárdala en un lugar accesible, como la sesión; y asegúrate de
// destruir los datos cuando el usuario cierre sesión
// No se hace todo este proceso de desbloqueo porque es inseguro tener la contraseña
// plana almacenada por ahí, y también porque es un poco tardado
session_start();
$_SESSION["clave"] = $claveDesbloqueadaAscii;
El desbloqueo es un poco lento, así que es mejor guardar la clave desbloqueada en un almacenamiento temporal como la sesión.
Recuerda que la clave sin desbloquear debería ser obtenida de la base de datos; yo la pongo ahí por simplicidad.
3 – Cifrar datos con clave de usuario
La clave está en la sesión; así que para cifrar y descifrar datos la tomamos de ahí y llamamos al método encrypt
como en los otros ejemplos.
<?php
// Cifrar datos con contraseña de usuario
// https://parzibyte.me/blog
require_once "vendor/autoload.php";
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
// Ahora vamos a cifrar; recuerda que ya desbloqueamos la clave anteriormente
// y que ahora está en la sesión. Mira login.php para más información
session_start();
$claveAscii = $_SESSION["clave"];
$clave = Key::loadFromAsciiSafeString($claveAscii);
// Definir los datos a encriptar
$datos = "Hola, mundo. Soy un mensaje muy secreto";
// Encriptar, usando la clave del usuario
$datosEncriptados = Crypto::encrypt($datos, $clave);
// Guardarlos; en mi caso lo imprimo
echo $datosEncriptados;
// Salida:
//def502007436b388ae3b265aae2ce07729a6e46018418a70c8d5ea32ab593541b1d6ab91215733088a538474d11ac900fade2f52d40332dc53d0c00a43d848e6902edbc02e9f5abe6ff0f76f251fb64c0d716a8dccf9df5292c6e291da3ff1de21afa02367f5746ea1118d25c8e0841d61e1faae82040d0640448c
Los datos encriptados ya podemos guardarlos, y nadie tendrá acceso a ellos, solo el usuario que conoce la contraseña.
Fíjate que la contraseña en texto plano ya no se usa aquí; solo se usó para desbloquear la clave del usuario.
4 – Descifrar datos con clave de usuario
Para descifrar es lo mismo, pero con el método decrypt
. Es importante mencionar que si la clave o los datos son incorrectos, se producirá una excepción.
<?php
// Descifrar datos con contraseña de usuario
// https://parzibyte.me/blog
require_once "vendor/autoload.php";
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
use Defuse\Crypto\Key;
use Defuse\Crypto\KeyProtectedByPassword;
// Ahora vamos a descifrar; recuerda que ya desbloqueamos la clave anteriormente
// y que ahora está en la sesión. Mira login.php para más información
session_start();
$claveAscii = $_SESSION["clave"];
$clave = Key::loadFromAsciiSafeString($claveAscii);
// Obtener datos encriptados de la base de datos
$datosEncriptados = "def502007436b388ae3b265aae2ce07729a6e46018418a70c8d5ea32ab593541b1d6ab91215733088a538474d11ac900fade2f52d40332dc53d0c00a43d848e6902edbc02e9f5abe6ff0f76f251fb64c0d716a8dccf9df5292c6e291da3ff1de21afa02367f5746ea1118d25c8e0841d61e1faae82040d0640448c";
// Intentar descifrar, usando la clave del usuario
try {
$datosPlanos = Crypto::decrypt($datosEncriptados, $clave);
// Usar los datos planos
echo $datosPlanos;
} catch (WrongKeyOrModifiedCiphertextException $e) {
echo "Error descifrando";
// Cuidado, algo salió mal. Puede ser que:
// Hayas desbloqueado mal la clave
// Alguien ha modificado / corrompido los datos
}
A los datos descifrados los puedes mostrar o usarlos.
Conclusiones y vistazo general
No te confundas; en los primeros ejemplos ciframos y desciframos con una clave general; y con que alguien tuviera esa clave, podría descifrar y cifrar a gusto.
En los ejemplos con el usuario, la clave se genera en el registro, se guarda y se desbloquea únicamente con la contraseña del usuario.
Recuerda que en el caso del usuario debes destruir la sesión con session_destroy()
ya que si no, alguien podría acceder a la clave incluso después de que el usuario haya cerrado la sesión.
Por cierto, en los ejemplos simplifiqué muchas cosas pero fue para hacer esto más comprensivo.
Pronto traeré un ejemplo aplicando todo lo visto aquí; para que veas que realmente funciona.
Si quieres ver el repositorio en GitHub lo dejo aquí.
Hola, muy interesante y muy buen artículo.
El hecho de poder cifrar lo datos con la clave del usuario es muy útil, ahora sí en algún momento el usuario decide cambiar el password, los datos que tenia guardado ya no se podrían recuperar?
Saludos.
Hola. Efectivamente, se tendrían que descifrar y cifrar de nuevo todos los archivos
Saludos 🙂