Saltar al contenido

Cifrado César en PHP

La función completa tiene alrededor de una docena de líneas. Cópiala, o escribe tu propio texto y mira qué hace el código con cada letra.

caesar.php
<?php function caesar(string $text, int $shift): string{    $result = '';    foreach (str_split($text) as $c) {        if (ctype_alpha($c)) {            $base = ctype_upper($c) ? 65 : 97;            $index = ((ord($c) - $base + $shift) % 26 + 26) % 26;            $result .= chr($index + $base);        } else {            $result .= $c;        }    }    return $result;} echo caesar('¡Hola, mundo!', 3), "\n";// ¡Krod, pxqgr!
anuncio

Ejecutar el código

Guarda el código como caesar.php y ejecútalo en la terminal. No necesitas un servidor web para esto:

Terminal
php caesar.php

Si el cifrado en sí es nuevo para ti, la guía lo explica sin código.

Cómo funciona el código

str_split corta el texto en bytes sueltos y ctype_alpha reconoce las letras de la A a la Z. ord() da el número de una letra: 72 para la H y 104 para la h. Al restar 65 o 97 obtienes su posición en el alfabeto, de 0 a 25.

Después de sumar el desplazamiento, el módulo da la vuelta al alfabeto: con un desplazamiento de 3, la Z cae en la C. chr() devuelve el número a su forma de letra.

Espacios, signos y cifras pasan por la rama else sin cambios. Lo mismo ocurre con la ñ: en UTF-8 son dos bytes, y ninguno es una letra para ctype_alpha.

Descifrar en PHP

No hace falta una segunda función. Descifrar es desplazar en sentido contrario, así que basta con pasar un número negativo:

PHP
echo caesar('¡Krod, pxqgr!', -3), "\n";// ¡Hola, mundo!

De ahí el + 26 adicional de la fórmula. En PHP, % conserva el signo del número de la izquierda: -3 % 26 da -3, y esa posición no existe. Si sumas 26 y vuelves a aplicar el módulo sale 23, y la A vuelve a la X. PHP incluye además str_rot13(), aunque solo conoce el desplazamiento de 13.

Un formulario web

Coloca este código debajo de la función, guarda el archivo y arranca el servidor integrado con php -S localhost:8000 caesar.php. Después abre esa dirección en el navegador.

PHP
<?php$text = $_POST['text'] ?? '';$shift = (int) ($_POST['shift'] ?? 3);$result = $text === '' ? '' : caesar($text, $shift);?><form method="post">    <textarea name="text"><?= htmlspecialchars($text) ?></textarea>    <input name="shift" type="number" value="<?= $shift ?>">    <button>Cifrar</button></form><p><?= htmlspecialchars($result) ?></p>

Las dos salidas pasan por htmlspecialchars, porque el texto viene del visitante. Sin eso, cualquiera podría meter un script en tu página.

Un script de línea de comandos

Sustituye las dos últimas líneas de caesar.php por este código para recibir el desplazamiento y el texto como argumentos:

PHP
if ($argc !== 3) {    exit("Uso: php caesar.php <desplazamiento> <texto>\n");} echo caesar($argv[2], (int) $argv[1]), "\n"; // php caesar.php 3 "Hola"// Krod

Un endpoint JSON en Laravel

En un proyecto Laravel, pon la función en un helper o en una clase propia y añade esta ruta a routes/api.php. Laravel valida los datos y devuelve el array como JSON.

PHP
use Illuminate\Http\Request;use Illuminate\Support\Facades\Route; Route::post('/caesar', function (Request $request) {    $data = $request->validate([        'text' => 'required|string|max:1000',        'shift' => 'required|integer|between:-25,25',    ]);     return ['result' => caesar($data['text'], (int) $data['shift'])];});

Ante datos no válidos, el framework responde con un error por sí solo, de modo que la función solo recibe valores limpios. Si solo necesitas un endpoint que funcione, nuestra API REST gratuita ya está en marcha.

Romper el cifrado por fuerza bruta

Con 26 letras solo hay 25 desplazamientos posibles, y un bucle corto basta para leer el mensaje sin la clave. Busca en la salida la línea que tenga sentido:

PHP
function crack(string $text): void{    for ($shift = 1; $shift < 26; $shift++) {        echo $shift, ' ', caesar($text, -$shift), "\n";    }} crack('¡Krod, pxqgr!');// 1 ¡Jqnc, owpfq!// 2 ¡Ipmb, nvoep!// 3 ¡Hola, mundo!// ...

El artículo sobre el ataque de fuerza bruta entra en más detalle, y el descifrador en línea hace lo mismo en tu navegador.

Cifrado César con la ñ

Para desplazar también la ñ, guarda el alfabeto en una cadena y usa las funciones mb_, que cuentan caracteres y no bytes. Son las mismas 27 letras que usa nuestra herramienta:

PHP
const ALPHABET = 'abcdefghijklmnñopqrstuvwxyz'; function caesarAlphabet(string $text, int $shift): string{    $size = mb_strlen(ALPHABET);    $result = '';    foreach (mb_str_split($text) as $c) {        $lower = mb_strtolower($c);        $position = mb_strpos(ALPHABET, $lower);        if ($position === false) {            $result .= $c;        } else {            $index = (($position + $shift) % $size + $size) % $size;            $next = mb_substr(ALPHABET, $index, 1);            $result .= $c === $lower ? $next : mb_strtoupper($next);        }    }    return $result;} echo caesarAlphabet('El niño', 3), "\n";// Hñ plqr

Ahora la vuelta depende de mb_strlen, así que no tienes que escribir el 27 en ningún sitio. Puedes comparar el resultado en la herramienta de cifrado César con el alfabeto español.

Cifrar un archivo de texto

Lee el archivo entero con file_get_contents y escribe el resultado con file_put_contents. La primera devuelve false si falla, así que conviene comprobarlo.

PHP
$text = file_get_contents('message.txt'); if ($text === false) {    exit("No se puede leer message.txt\n");} file_put_contents('encrypted.txt', caesar($text, 3));

El mismo cifrado como clase

Una clase resulta útil si usas la misma clave muchas veces. Una clase readonly, disponible desde PHP 8.2, evita que el desplazamiento cambie después de fijarlo:

PHP
final readonly class CaesarCipher{    public function __construct(private int $shift = 3)    {    }     public function encrypt(string $text): string    {        return caesar($text, $this->shift);    }     public function decrypt(string $text): string    {        return caesar($text, -$this->shift);    }} echo (new CaesarCipher(5))->encrypt('¡Hola, mundo!'), "\n";// ¡Mtqf, rzsit!

Probar el código

La configuración de producción suele desactivar assert, así que actívalo al probar: php -d zend.assertions=1 -d assert.exception=1 caesar.php. Si todo va bien, imprime OK. En un proyecto real, las mismas comprobaciones encajan en PHPUnit o Pest.

PHP
assert(caesar('ABC', 3) === 'DEF');assert(caesar('xyz', 3) === 'abc');assert(caesar('Hello, World!', 5) === 'Mjqqt, Btwqi!');assert(caesar(caesar('PHP', 7), -7) === 'PHP');assert(caesar('A', -27) === 'Z');assert(caesar('é', 3) === 'é');echo "OK\n";

Errores frecuentes

El más común es % con un desplazamiento negativo, como vimos antes. Le sigue mostrar el resultado sin htmlspecialchars. Además, strlen y $text[0] trabajan con bytes, de modo que en UTF-8 parten la ñ por la mitad. Para caracteres completos, usa las funciones mb_.

Preguntas frecuentes

¿Qué versión de PHP necesito?

La función principal funciona en PHP 7 y posteriores, y la clase readonly requiere PHP 8.2. Hemos ejecutado el código de esta página en PHP 8.4.

¿Sirve para proteger datos reales?

No. El bucle de arriba lo rompe en segundos. Úsalo para aprender y para pasatiempos. Para datos reales, PHP tiene cifrado moderno en la extensión sodium, y las contraseñas se tratan con password_hash().

¿Cómo compruebo que mi resultado es correcto?

Introduce el mismo texto y desplazamiento en nuestra herramienta de cifrado César y compara. También puedes llamar a la API REST gratuita desde tus pruebas.

Sigue aprendiendo