Przejdź do treści

Szyfr Cezara w PHP

Cała funkcja ma kilkanaście linijek. Skopiuj ją albo wpisz własny tekst i zobacz, co kod robi z każdą literą.

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('Witaj świecie!', 3), "\n";// Zlwdm śzlhflh!
reklama

Jak uruchomić kod

Zapisz kod jako caesar.php i uruchom go w terminalu. Serwer WWW nie jest do tego potrzebny:

Terminal
php caesar.php

Jeśli sam szyfr jest dla ciebie nowy, zajrzyj do przewodnika, w którym nie ma kodu.

Jak działa ten kod

str_split tnie tekst na pojedyncze bajty, a ctype_alpha wybiera litery od A do Z. ord() podaje numer litery: 87 dla W, 119 dla w. Po odjęciu 65 lub 97 dostajesz miejsce w alfabecie, od 0 do 25.

Po dodaniu przesunięcia modulo zawija wynik, więc przy przesunięciu 3 litera Z trafia na C. chr() robi z liczby z powrotem literę.

Spacje, interpunkcja i cyfry idą gałęzią else bez zmian. Tak samo polskie litery: w UTF-8 „ś” to dwa bajty, żaden z nich nie jest literą dla ctype_alpha, więc oba trafiają do wyniku takie, jakie były.

Deszyfrowanie w PHP

Druga funkcja nie jest potrzebna. Odszyfrowanie to takie samo przesunięcie w drugą stronę, więc wystarczy podać liczbę ujemną:

PHP
echo caesar('Zlwdm śzlhflh!', -3), "\n";// Witaj świecie!

Stąd dodatkowe + 26 we wzorze. W PHP % zachowuje znak lewej liczby, więc -3 % 26 to -3, a takiej pozycji litery nie ma. Po dodaniu 26 i ponownym modulo wychodzi 23, czyli A wraca na X. PHP ma też wbudowane str_rot13(), ale zna ono tylko przesunięcie o 13.

Formularz na stronie

Wstaw ten kod pod funkcję, zapisz plik i uruchom wbudowany serwer PHP poleceniem php -S localhost:8000 caesar.php. Potem otwórz ten adres w przeglądarce.

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>Zaszyfruj</button></form><p><?= htmlspecialchars($result) ?></p>

Oba wyniki przechodzą przez htmlspecialchars, bo tekst pochodzi od odwiedzającego. Bez tego każdy mógłby wstawić na twoją stronę skrypt.

Skrypt w wierszu poleceń

Zastąp dwie ostatnie linijki caesar.php tym kodem, a przesunięcie i tekst będą pochodzić z argumentów:

PHP
if ($argc !== 3) {    exit("Użycie: php caesar.php <przesunięcie> <tekst>\n");} echo caesar($argv[2], (int) $argv[1]), "\n"; // php caesar.php 3 "Witaj"// Zlwdm

Endpoint JSON w Laravelu

W projekcie Laravel umieść funkcję w helperze albo osobnej klasie, a tę trasę dodaj do routes/api.php. Laravel sprawdzi dane i zwróci tablicę jako 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'])];});

Na błędne dane framework sam odpowie błędem, więc funkcja dostaje tylko poprawne wartości. Jeśli potrzebujesz po prostu działającego endpointu, nasze darmowe REST API już działa.

Łamanie szyfru metodą brute force

Przy 26 literach do sprawdzenia jest tylko 25 przesunięć, więc krótka pętla odczyta wiadomość bez klucza. Wystarczy znaleźć w wynikach linijkę, która ma sens:

PHP
function crack(string $text): void{    for ($shift = 1; $shift < 26; $shift++) {        echo $shift, ' ', caesar($text, -$shift), "\n";    }} crack('Zlwdm śzlhflh!');// 1 Ykvcl śykgekg!// 2 Xjubk śxjfdjf!// 3 Witaj świecie!// ...

Więcej o tej metodzie przeczytasz w artykule o ataku brute force, a łamacz online zrobi to samo w przeglądarce.

Szyfr Cezara z polskimi znakami

Żeby przesuwać także ą, ć czy ż, zapisz alfabet w napisie i użyj funkcji mb_, które liczą znaki, a nie bajty. To ten sam alfabet, 35 znaków, którego używa nasze narzędzie:

PHP
const ALPHABET = 'aąbcćdeęfghijklłmnńoópqrsśtuvwxyzźż'; 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('Zażółć gęślą jaźń', 3), "\n";// Acbrńę jhvnć łcąp

Zawijanie korzysta teraz z mb_strlen, więc nie musisz nigdzie wpisywać 35. Wynik możesz porównać w narzędziu szyfru Cezara po wybraniu polskiego alfabetu.

Szyfrowanie pliku tekstowego

Wczytaj cały plik przez file_get_contents, a wynik zapisz przez file_put_contents. Pierwsza funkcja przy błędzie zwraca false, więc warto to sprawdzić.

PHP
$text = file_get_contents('message.txt'); if ($text === false) {    exit("Nie można odczytać message.txt\n");} file_put_contents('encrypted.txt', caesar($text, 3));

Ten sam szyfr jako klasa

Klasa przydaje się, gdy jednego klucza używasz wiele razy. Klasa readonly, dostępna od PHP 8.2, pilnuje, żeby przesunięcie nie zmieniło się po ustawieniu:

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('Witaj świecie!'), "\n";// Bnyfo śbnjhnj!

Testowanie kodu

Ustawienia produkcyjne zwykle wyłączają assert, więc włącz go przy testach: php -d zend.assertions=1 -d assert.exception=1 caesar.php. Jeśli wszystko gra, program wypisze OK. W prawdziwym projekcie te same sprawdzenia pasują do PHPUnit lub 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";

Najczęstsze błędy

Najczęstszy to % przy ujemnym przesunięciu, o czym była mowa wyżej. Drugi to wypisanie wyniku bez htmlspecialchars. Pamiętaj też, że strlen i $text[0] działają na bajtach, więc w tekście UTF-8 przecinają polskie litery na pół. Gdy potrzebujesz całych znaków, używaj funkcji mb_.

Najczęstsze pytania

Jakiej wersji PHP potrzebuję?

Główna funkcja działa w PHP 7 i nowszych, a klasa readonly wymaga PHP 8.2. Kod z tej strony uruchomiliśmy w PHP 8.4.

Czy mogę tym chronić prawdziwe dane?

Nie. Pętla pokazana wyżej łamie ten szyfr w kilka sekund. Używaj go do nauki i łamigłówek. Do prawdziwych danych PHP ma współczesne szyfrowanie w rozszerzeniu sodium, a hasła obsługuje password_hash().

Jak sprawdzić, czy wynik jest poprawny?

Wpisz ten sam tekst i przesunięcie w narzędziu szyfru Cezara i porównaj. Ze swoich testów możesz też wywołać darmowe REST API.

Co dalej