From 476c3ff531c9f642d499d0667b2e75e1f93ab12f Mon Sep 17 00:00:00 2001 From: Gledson A Date: Sun, 20 Oct 2019 21:50:18 -0300 Subject: [PATCH] Adding Vigenere cipher (issue #406) --- .../cryptography/vigenere-cipher/README.md | 80 +++++++++++ .../vigenere-cipher/VigenereCipher.js | 135 ++++++++++++++++++ .../__test__/VigenereCipher.test.js | 45 ++++++ 3 files changed, 260 insertions(+) create mode 100644 src/algorithms/cryptography/vigenere-cipher/README.md create mode 100644 src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js create mode 100644 src/algorithms/cryptography/vigenere-cipher/__test__/VigenereCipher.test.js diff --git a/src/algorithms/cryptography/vigenere-cipher/README.md b/src/algorithms/cryptography/vigenere-cipher/README.md new file mode 100644 index 00000000..f87c9564 --- /dev/null +++ b/src/algorithms/cryptography/vigenere-cipher/README.md @@ -0,0 +1,80 @@ +# Vigenere Cipher + +The cipher is a modified version of Caesar cipher where it uses an *tabula recta* - an table generate by many +characters shifts in the alphabet - serving as a guide to encrypt the message using its, and keyword, indexes. + +## Tabula Recta + +*Tabula Recta* is the table used as a guide to encryption or decryption of the message using its keyword. +It's generated by shifting the alphabet letters, exchangin their positions just like the demonstration below: + +| # | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | +|-------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| **A** | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | +| **B** | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | +| **C** | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | +| **D** | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | +| **E** | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | +| **F** | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | +| **G** | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | +| **H** | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | +| **I** | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | +| **J** | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | +| **K** | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | +| **L** | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | +| **M** | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | +| **N** | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | +| **O** | O | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | +| **P** | P | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | +| **Q** | Q | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | +| **R** | R | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | +| **S** | S | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | +| **T** | T | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | +| **U** | U | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | +| **V** | V | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | +| **W** | W | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | +| **X** | X | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | +| **Y** | Y | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | +| **Z** | Z | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | + +## Keyword + +Used to help on encryption and decryption of the message. They both need to have the same length (keyword and message), so, +in case those don't match, the keyword needs to be adjusted. In case of a keyword with a lesser length than the message, just +repeat the keyword until it fits the necessary length. e. g.: + +Message is `ATTACKATDAWN` and it has twelve characters. +Keyword is `LEMON` and it has five characters. + +So, keyword just needs to fit twelve characters repeating its content. In this case, the new keyword would be `LEMONLEMONLE`. + +## Encrypting message + +For encryption, it needs to iterate over the keyword and the message using the same index, getting their characters and using +them as rows and columns, respectively, in the *tabula recta* to get the encrypted letter. + +Using the same example as in the [keyword](#keyword) section, we have: + +* fisrt letter of the treated key, `L`, with the first letter of the message, `A` give us `L`; +* second letter of the treated key, `E`, with the second letter of the message, `T`, give us `X`. + +So on, and so forth until we get `LXFOPVEFRNHR`. + +## Decrypting message + +For decryption the logic is just a little different. It need to use the *tabula recta* still, but in a different manner. +Let's take our former message and key for the example. We already know that the encrytion generates the hash `LXFOPVEFRNHR` +so, iterating over the key, we use its letters to get rows in the *tabula recta*. Those rows would be the shifted alphabet for +the key letters we are using, so we just need to get their respective *column* to find the original letter. + +Following this logic, we have: + +* row of the first key letter `L` with the value as the first hash letter `L`, giving us the column `A`; +* row of the second key letter `E` with the value as the second hash letter `X`, giving us the column `T`; + +So on and so forth until we have our original message, `ATTACKATDAWN`. + +## References + +* [General explanation about the algorithm by Wikipedia](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) +* [Pratical exercise using the algorithm](https://www.youtube.com/watch?v=SkJcmCaHqS0) \ No newline at end of file diff --git a/src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js b/src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js new file mode 100644 index 00000000..9accec09 --- /dev/null +++ b/src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js @@ -0,0 +1,135 @@ +const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + +class VigenereCipher { + constructor() { + this.tabulaRecta = this.getTabulaRecta(); + } + + /** + * Function to encrypt message. + * + * @param {string} message - Message to be encrypted. + * @param {string} keyword - Key to be used in the tabula recta for the encryption. + * @return {string} + */ + encrypt(message, keyword) { + let hash = ''; + const treatedKeyword = this.getTreatedKeyword(keyword, message.length); + + message.split('').forEach((letter, index) => { + hash += this.tabulaRecta[treatedKeyword[index]][letter]; + }); + + return hash; + } + + /** + * Function to decrypt message. + * + * @param {string} hash - Hash to be decrypted. + * @param {string} keyword - Key to be used in the tabula recta for the decryption. + * @return {string} + */ + decrypt(hash, keyword) { + let message = ''; + const treatedKeyword = this.getTreatedKeyword(keyword, hash.length); + + hash.split('').forEach((letter, index) => { + message += this.getOriginalAlphabetLetter(this.tabulaRecta[treatedKeyword[index]], letter); + }); + + return message; + } + + /** + * Function to get a tabula recta. + * + * @return {object} + */ + getTabulaRecta() { + const tabulaRecta = this.getTabulaRectaSchema(); + + alphabet.forEach((rowLetter, rowIndex) => { + const shiftedAlphabet = this.shiftArray(alphabet, rowIndex); + + alphabet.forEach((columnLetter, columnIndex) => { + tabulaRecta[rowLetter][columnLetter] = shiftedAlphabet[columnIndex]; + }); + }); + + return tabulaRecta; + } + + /** + * Function to get a schema for a tabula recta. + * + * @return {object} + */ + getTabulaRectaSchema() { + const column = {}; + + alphabet.forEach((letter) => { + const row = {}; + + alphabet.forEach((innerLetter) => { + row[innerLetter] = ''; + }); + + column[letter] = row; + }); + + return column; + } + + /** + * Function to shift an array, moving elements in its head to its tail n times. + * + * @param {array} array - Array to be used as a model for the shifting. + * @param {number} times - How many shifts will happen in the array. + * @return {array} + */ + shiftArray(array, times) { + const arrayCopy = [...array]; + + for (let i = 0; i < times; i += 1) { + arrayCopy.push(arrayCopy.shift()); + } + + return arrayCopy; + } + + /** + * Function to get a keyword within the given limit. + * + * @param {string} keyword - Keyword to be verified and treated. + * @param {string} limit - Maximum length the keyword is allowed to have. + * @return {string} + */ + getTreatedKeyword(keyword, limit) { + let treatedKeyword = keyword; + + while (treatedKeyword.length < limit) { + treatedKeyword += treatedKeyword; + } + + if (treatedKeyword.length > limit) { + treatedKeyword = treatedKeyword.substring(0, limit); + } + + return treatedKeyword; + } + + /** + * Function to get a tabula recta column index (always a letter) given its row and + * a value within that row. + * + * @param {string} tabulaRectaRow - One row from the tabula recta. + * @param {string} hashLetter - One letter within the given row. + * @return {string} + */ + getOriginalAlphabetLetter(tabulaRectaRow, hashLetter) { + return Object.keys(tabulaRectaRow).find(key => tabulaRectaRow[key] === hashLetter); + } +} + +export default new VigenereCipher(); diff --git a/src/algorithms/cryptography/vigenere-cipher/__test__/VigenereCipher.test.js b/src/algorithms/cryptography/vigenere-cipher/__test__/VigenereCipher.test.js new file mode 100644 index 00000000..fe78c143 --- /dev/null +++ b/src/algorithms/cryptography/vigenere-cipher/__test__/VigenereCipher.test.js @@ -0,0 +1,45 @@ +import vigenereCipher from '../VigenereCipher'; + +describe('Vigenere cipher tests', () => { + it('should generate LXFOPVEFRNHR for the word ATTACKATDAWN when using the key LEMON', () => { + const word = 'ATTACKATDAWN'; + const keyword = 'LEMON'; + const expectedResult = 'LXFOPVEFRNHR'; + + const result = vigenereCipher.encrypt(word, keyword); + + expect(result).toBe(expectedResult); + }); + + it('should decrypt to ATTACKATDAWN when using hash LXFOPVEFRNHR with key LEMON', () => { + const hash = 'LXFOPVEFRNHR'; + const keyword = 'LEMON'; + const expectedResult = 'ATTACKATDAWN'; + + const result = vigenereCipher.decrypt(hash, keyword); + + expect(result).toBe(expectedResult); + }); + + it('should adapt key when it is longer than the word when encrypting', () => { + const word = 'ATTACKATDAWN'; + const keywordWithinBounds = 'LEMON'; + const keywordOutOfBounds = 'LEMONLEMONLEMON'; + + const resultUsingKeywordWithinBounds = vigenereCipher.encrypt(word, keywordWithinBounds); + const resultUsingKeywordOutOfBounds = vigenereCipher.encrypt(word, keywordOutOfBounds); + + expect(resultUsingKeywordWithinBounds).toBe(resultUsingKeywordOutOfBounds); + }); + + it('should adapt key when it is longer than the word when decrypting', () => { + const hash = 'LXFOPVEFRNHR'; + const keywordWithinBounds = 'LEMON'; + const keywordOutOfBounds = 'LEMONLEMONLEMON'; + + const resultUsingKeywordWithinBounds = vigenereCipher.decrypt(hash, keywordWithinBounds); + const resultUsingKeywordOutOfBounds = vigenereCipher.decrypt(hash, keywordOutOfBounds); + + expect(resultUsingKeywordWithinBounds).toBe(resultUsingKeywordOutOfBounds); + }); +});