Running the code
Save the code as caesar.php and run it from the terminal. You don’t need a web server for this:
php caesar.phpIf the cipher itself is new to you, the guide explains it without code.
How the code works
str_split cuts the text into single bytes and ctype_alpha picks out the letters A to Z. ord() gives a letter’s number: 72 for H and 104 for h. Subtracting 65 or 97 gives its place in the alphabet, from 0 to 25.
After the shift is added, the modulo wraps the result around, so with a shift of 3 the letter Z lands on C. chr() then turns the number back into a letter.
Spaces, punctuation and digits take the else branch unchanged. Accented letters do too: in UTF-8, é is two bytes, neither of them a letter for ctype_alpha, so both are copied as they are.
Decrypting in PHP
You don’t need a second function. Decrypting is the same shift in the other direction, so pass a negative number:
echo caesar('Khoor Zruog!', -3), "\n";// Hello World!This is why the formula has the extra + 26. In PHP, % keeps the sign of the left number, so -3 % 26 is -3, which is not a letter position. Adding 26 and taking the modulo again gives 23, which takes A back to X. By the way, PHP has str_rot13() built in, but it only knows the shift of 13.
A web form
Put this under the function, save the file and start PHP’s built-in server with php -S localhost:8000 caesar.php. Then open that address in a browser.
<?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>Encrypt</button></form><p><?= htmlspecialchars($result) ?></p>Both outputs go through htmlspecialchars, because the text comes from the visitor. Without it, anyone could put a script into your page.
A command-line script
Replace the last two lines of caesar.php with this to take the shift and the text as arguments:
if ($argc !== 3) { exit("Usage: php caesar.php <shift> <text>\n");} echo caesar($argv[2], (int) $argv[1]), "\n"; // php caesar.php 3 "Hello"// KhoorA JSON endpoint in Laravel
In a Laravel project, put the function in a helper or a class of its own and add this route to routes/api.php. Laravel validates the input and returns the array as JSON.
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'])];});Invalid input gets an error response automatically, so the function only ever sees clean values. If you just need a working endpoint, our free REST API is already running.
Breaking the cipher with brute force
An alphabet of 26 letters leaves only 25 shifts to try, so a short loop can read a message without the key. Look through the output for the line that makes sense:
function crack(string $text): void{ for ($shift = 1; $shift < 26; $shift++) { echo $shift, ' ', caesar($text, -$shift), "\n"; }} crack('Khoor Zruog!');// 1 Jgnnq Yqtnf!// 2 Ifmmp Xpsme!// 3 Hello World!// ...The article on brute force attacks goes deeper, and the online solver does the same in your browser.
Other alphabets and digits
To shift anything beyond A to Z, keep the alphabet in a string and use the mb_ functions, which count characters and not bytes. In this example the alphabet includes digits:
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789'; 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('Room 101', 3), "\n";// Urrp 434The wrap-around now uses mb_strlen, so you only have to change the string to support another language. Our Caesar cipher tool has ready alphabets for Polish, German, Spanish and French if you want to compare results.
Encrypting a text file
Read the whole file with file_get_contents and write the result with file_put_contents. The first one returns false on failure, so check for it.
$text = file_get_contents('message.txt'); if ($text === false) { exit("Cannot read message.txt\n");} file_put_contents('encrypted.txt', caesar($text, 3));The same cipher as a class
A class is useful when one key is used many times. A readonly class, available from PHP 8.2, keeps the shift from changing after it is set:
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('Hello World!'), "\n";// Mjqqt Btwqi!Testing your code
Production settings usually switch assert off, so turn it on when you run the tests: php -d zend.assertions=1 -d assert.exception=1 caesar.php. If nothing is wrong, it prints OK. In a real project, the same checks fit into PHPUnit or Pest.
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";Common mistakes
The most common one is % with a negative shift, described above. Next comes printing the result without htmlspecialchars. And strlen and $text[0] work on bytes, so on UTF-8 text they cut letters in half. Use the mb_ functions whenever you need whole characters.
Frequently asked questions
Which PHP version do I need?
The main function works on PHP 7 and newer, and the readonly class needs PHP 8.2. We ran the code on this page on PHP 8.4.
Can I use this to protect real data?
No. Anyone can break it in seconds with the loop shown above. Use it to learn and for puzzles. For real data, PHP has modern encryption in its sodium extension, and passwords belong in password_hash().
How can I check that my result is right?
Enter the same text and shift in our Caesar cipher tool and compare. You can also call the free REST API from your own tests.