Basic cryptography – the Vigenere cipher

While last week’s Caesar cipher is a fun implementation to practice cryptography, it is incredibly weak. With only 26 possible keys, cracking the cipher by brute force is easy to do by hand. No computers or fancy setups are required.

It’s still a fun toy algorithm to implement, though, and is a gateway of sorts to more sophisticated techniques.

Yesterday, we discussed running key ciphers. These are incredibly powerful as, given a sufficiently random key, they are practically impossible to break, even with a super computer!

To fully illustrate how they work, though, let’s turn to a relatively straightforward implementation – the Vigenere cipher.

The Vigenere cipher

This cipher is a repackaging of the simpler Caesar cipher, using a substitution scheme to protect a plaintext message. Rather than use a single integer rotation for the entire message, though, the Vigenere cipher uses a different rotation for every character of the plaintext.

The decryption key you use determines a different rotation for every character. Effectively, each character of the key sets up a different Caesar for just that character of the plaintext. In this way, so long as your key remains secret, you can encrypt messages of effectively any length!

Given this cipher is relatively simple, you can also encrypt and decrypt messages by hand. The Vigenere square, allows you to map from a plaintext letter to an encrypted letter given the corresponding letter in a key:

Vigenere Square

Implementing the cipher

We can implement this cipher trivially in PHP. First, we need two helper methods. One to sanitize our string and force every character to be an uppercase English letter:

function sanitize(string $str): string
{
	$cleaned = strtoupper($str);
	$cleaned = preg_replace('/[^A-Z]/i', '', $cleaned);

	return $cleaned;
}

Second, we protect against a short key by padding the key with itself up to any arbitrary length of characters:

function pad_key(string $key, int $length): string
{
	$padded = $key;
	while(strlen($padded) < $length) {
		$padded .= $key;
	}

	return $padded;
}

Finally, we leverage these helper functions with our knowledge of the Vigenere cipher to create a full encryption function:

function encrypt(string $message, string $key): string
{
	// Strip non-alpha characters from the key
	$clean_key = pad_key(sanitize($key), strlen(sanitize($message)));

	$pos = 0;
	$ciphertext = '';
	foreach(str_split($message) as $char) {
		$valid = sanitize($char);
		if ($valid !== '') {
			$offset = ord($clean_key[$pos]) - 65;
			$caesar = ((ord($valid) - 65) + $offset) % 26;
			$ciphertext .= chr($caesar + 65);
			$pos += 1;
		} else {
			$ciphertext .= $char;
		}
	}

	return $ciphertext;
}

Our decryption function is basically the same, but in reverse:

function decrypt(string $ciphertext, string $key): string
{
	// Strip non-alpha characters from the key
	$clean_key = pad_key(sanitize($key), strlen(sanitize($ciphertext)));

	$pos = 0;
	$plaintext = '';
	foreach(str_split($ciphertext) as $char) {
		$valid = sanitize($char);
		if ($valid !== '') {
			$offset = ord($clean_key[$pos]) - 65;
			$caesar = ((ord($valid) - 65) - $offset);
			if ($caesar < 0) $caesar += 26;

			$plaintext .= chr($caesar + 65);
			$pos += 1;
		} else {
			$plaintext .= $char;
		}
	}

	return $plaintext;
}

Encrypting for fun and for profit

The advantage of the Vigenere cipher is that it’s easy to use (even by hand) and provides a significant security advantage over the weaker Caesar cipher. However, it is still prone to several cryptographic attacks. You shouldn’t rely on this to protect anything remarkably sensitive.

We’ll cover truly secure encryption schemes in the near future. Practicing with simpler ciphers like Vigenere is a way to practice writing an implementation to level up your own understanding.

Bonus points to anyone who can figure out what key I used to encrypt the following message. Please drop me a line to see if you’re right:

VVRJVCKSATBWBXW! WMI ZCYYUIW MK ACG. RGW, ZPFY HF CBN NOGT MW ZRFFE RZBK?