This commit is contained in:
Gledson Afonso 2024-04-25 08:18:04 +08:00 committed by GitHub
commit 280e9ab8f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 260 additions and 0 deletions

View File

@ -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)

View File

@ -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();

View File

@ -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);
});
});