Encriptación y Hashing en PHP

En seguridad, “encriptar” no es lo mismo que “hashear”. Este manual resume cuándo usar cada cosa, cómo hacerlo correctamente en PHP y las buenas prácticas mínimas.

Atajo mental: Hash → solo verificar (no se revierte). Encriptación → ocultar y luego recuperar (reversible con clave).

Índice

Hashing (contraseñas)

Para almacenar contraseñas no se encripta, se usa hash con sal y factor de costo. En PHP: password_hash() y password_verify() (manejan sal y costo por vos).

<?php
$clavePlano = "Secreta.123";

// Crear hash (BCrypt/Argon2 según tu PHP)
$hash = password_hash($clavePlano, PASSWORD_DEFAULT);

// Guardá $hash en BD (no guardes la clave en texto)
echo $hash . "\n";

// Verificar al loguear:
$ok = password_verify("Secreta.123", $hash);
echo $ok ? "✅ Coincide" : "❌ No coincide";
?>

Tips: Usá PASSWORD_DEFAULT y permití rehash con password_needs_rehash() si cambia el costo/algoritmo.

Encriptación simétrica (una clave)

Para datos que necesitás recuperar (ej. DNI cifrado, tokens temporales), usá encriptación simétrica. Con OpenSSL: elegí AES-256 en modo AEAD (GCM) para confidencialidad + integridad.

<?php
$plaintext = "Dato sensible";
$key = random_bytes(32); // 256 bits (guardala segura)
$cipher = "aes-256-gcm";

// IV/nonce único por mensaje (12 bytes recomendado para GCM)
$iv = random_bytes(12);

$ciphertext = openssl_encrypt(
  $plaintext,
  $cipher,
  $key,
  OPENSSL_RAW_DATA,
  $iv,
  $tag  // etiqueta de autenticación (16 bytes)
);

// Luego, para transportar/guardar: base64
$paquete = base64_encode($iv . $tag . $ciphertext);
echo $paquete;

// ----- Decrypt -----
$raw = base64_decode($paquete);
$iv2  = substr($raw, 0, 12);
$tag2 = substr($raw, 12, 16);
$ct2  = substr($raw, 28);

$decrypted = openssl_decrypt(
  $ct2,
  $cipher,
  $key,
  OPENSSL_RAW_DATA,
  $iv2,
  $tag2
);

echo "\n" . $decrypted; // "Dato sensible"
?>

Clave: almacenala fuera del repositorio, en variables de entorno o un secret manager. IV/nonce: nuevo y único por mensaje; puede ir junto con el texto cifrado.

Encriptación moderna con libsodium

libsodium (extensión nativa) provee primitivas modernas y seguras por defecto. Para cifrado autenticado simétrico, usá sodium_crypto_secretbox() (XSalsa20-Poly1305).

<?php
if (!extension_loaded('sodium')) {
  die("Sodium no disponible");
}

$mensaje = "Secreto con sodium";
$key     = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 32 bytes
$nonce   = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes

$cipher  = sodium_crypto_secretbox($mensaje, $nonce, $key);

// Empaquetar para transporte
$paquete = base64_encode($nonce . $cipher);

// ----- Decrypt -----
$raw   = base64_decode($paquete);
$n2    = substr($raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$c2    = substr($raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

$plain = sodium_crypto_secretbox_open($c2, $n2, $key);
if ($plain === false) {
  die("Autenticación falló");
}
echo $plain; // "Secreto con sodium"
?>

Para cifrado asimétrico (emisor y receptor con claves diferentes), podés usar sodium_crypto_box().

Aleatoriedad segura

Para claves, IV, tokens o sales: no uses rand()/mt_rand(). Usá:

<?php
$token = bin2hex(random_bytes(16)); // 32 hex chars
$otp   = random_int(100000, 999999); // 6 dígitos
?>

Buenas prácticas

Errores comunes

Resumen: Hash para contraseñas; cifrado autenticado para datos recuperables. Usá primitivas modernas (sodium o AES-GCM), aleatorios seguros y gestioná bien tus claves.