This article is technically Part 3 of a three part series, but don't worry, the first two parts are not critical to understanding this part (and this is the more interesting section tbh).
Here are the links to the others if you're curious:
Using the Vigenère Cipher to Encrypt a Message (Part 1)
Using the Vigenère Cipher to Encrypt a Message (Part 2)
In Part 1, I gave a brief overview of the Vigenère cipher and discussed the two approaches to solving it (the two approaches that I could come up with - there are definitely others). In Part 2, I covered the first approach, which is essentially a Caesar cipher with a dynamic shift number. In this part, I'm going to step through the more interesting solution - the way it's really intended to be done - using the magical Vigenère table.
The Vigenère table looks like this:
By Brandon T. Fields (cdated) - Based upon Vigenere-square.png by en:User:Matt Crypto. This version created by bdesham in Inkscape, and modified by cdated to include visual guides.This vector image was created with Inkscape., Public Domain, Link
Don't worry about deciphering that behemoth right now, you'll gain a deeper understanding as I go over the code to build this thing.
The process breaks down into four primary functions: the generateAlphabet
function, the generateVigenereTable
function, the encodeWithTable
function, and of course the vigenereCipherWithTable
function. Note: this solution is rather imperative and I hope to reimplement it in a more declarative way in the future so follow me on twitter @_vincecampanale or on dev.to for humor and updates (all about JS of course).
So here's the plan:
1) Generate an alphabet starting with a given letter (a in the first column, b in the second, etc) - note: the alphabet must wrap around to the beginning when it reaches z
2) Generate a Vigenere table
- The keys consist of the standard alphabet (a-z)
- Each key's value is an alphabet starting with that key and wrapping back around to a (each value is 26 letters)
3) Encode the message by looking up each letter of the original message in the keys of the Vigenere table, then traversing the table to get the value from the character code of the keyword letter
4) Put it all together in the final function
Step 1: Build the generateAlphabet
function
In this function, the parameter will be a starting index. We are going to iterate over twenty-six char codes, starting at the provided start index. Presumably, the first char code will be 97, and they will go up from there. In order to account for char codes over 122, we add some if/else logic into the String.fromCharCode
method. Ternary operators allow us to keep this code succinct.
function generateAlphabet(start) {
let alphabet = [];
//from start index to 26 chars later
for (let i = start; i < start + 26; i++) {
//convert the char code into a letter and push it to the alphabet array
alphabet.push(String.fromCharCode(
i > 122 ? i - 26 : //if char code > 122, return code - 26, else
i < 97 ? i + 26 : //if char code < 97, return code + 26, else
i //just return the code
));
}
return alphabet; //return the alphabet array
}
Step 2: Build the generateVigenereTable
function
Dedicating a function to generating alphabets with different starting character codes allows us to keep the Vigenère table function surprisingly simple.
All we need to do is instantiate an empty object, table
. Load the keys of that object up with the standard alphabet, starting with the letter 'a' (char code 97). Then for each key in the table, we generate an alphabet that starts at the key's index. So the second key ('b') has an alphabet starting with b and wrapping back around to end with a. The third key ('c') has an alphabet starting with c and wrapping back around to end with b. And so on.
In code:
//generate an object, where each key is a letter and each value is another alphabet
function generateVigenereTable() {
let table = {}; //instantiate a temporary object to hold the table
table.keys = generateAlphabet(97); //set the keys of the object equal to the standard alphabet (starting at 97)
table.keys.forEach((key, index) => { table[key] = generateAlphabet(97 + index) }); //set the value of each key as the alphabet
return table; //return the table
}
Step 3: Encode each character using a Vigenère table
This is the most important step in the solution - the piece where we put our Vigenère table to use. See the comments for a line-by-line explanation.
function encodeWithTable( message, keywordStr ) {
let messageArray = message.split(''); //split the message into an array
let keywordArray = keywordStr.split(''); //split the keyword string into an array
messageArray.forEach((letter, index) => { //for each letter and index in the message array
let messageChar = letter; //make a temp variable to hold the letter
let keywordChar = keywordArray[index]; //get the corresponding letter from the keyword string using the index
let keywordCharIndex = keywordChar.charCodeAt(0) - 97; //get the index of the keyword by subtracting 97 from the charcode
let vigenereTable = generateVigenereTable(); //create a vigenere table
let cipherChar = vigenereTable[messageChar][keywordCharIndex]; //look up the corresponding letter in the table
messageArray[index] = cipherChar; //replace the letter in the message with the cipher letter
});
return messageArray.join(''); //convert the messageArray back to a string and return it
}
Step 4: The Actual Function
Since we've taken the time to break down our problem and write thorough helper functions, the cipher function itself is nothing special. In fact, it's identical to the function in Part 2, except now we are encoding with the Vigenère table, rather than with the boring old Caesar cipher.
function vigenereCipherWithTable(message, keyword = "lemon") {
for ( let i = 0; i < message.length; i++ ) {
keyword += keyword; // repeat the keyword a bunch of times
}
let keywordStr = keyword.substr( 0, message.length ); // cut the keyword string so it's the same length as the message
let ciphertext = encodeWithTable( message, keywordStr ); // encode the string using the vigenere table
return ciphertext //return the cipher text!
}
And there you have it! Have fun passing secret messages back and forth with your friends...good luck decoding them though... ;)
Hope this was enjoyable and helpful. Shoot me an email or tweet at me with comments, questions, complaints, and suggestions.
Til next time ☮
Top comments (7)
What a great article - I'm going to give this a try in my free time.
A little bit of an aside, but have you write "The Code Book: by Simon Singh? I think you'd really like it, and I'd love to see your take on an Enigma cipher.
I have not read that book, but I'm always on the look out for a good stack of paper. I'll check it out :) thanks
Fun to play with, thanks Vince :)
For those considering doing actual crypto in the browser, remember the mantra 'Never write your own crypto' (caveat 'unless you have a university on hand to test it'), then take a look at this nicely curated list: gist.github.com/jo/8619441
I tried to convince our PCI-DSS auditor last year that hashing data in the browser took it out of scope of the audit before it reached our servers.. no luck :(
This is a super cool exercise! And as long as I never catch these 'encrypted' messages while sniffing traffic in a coffee shop, everything will be peachy xD
Why are you spelling Vigenère wrong?
"Some men just want to watch the world burn." - Alfred Pennyworth
I should probably fix that, though...
Oh your quote would have been so perfect if you'd mispelled Alfred's name... Nice article though.