mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-09-20 07:43:04 +08:00
Merge 476c3ff531
into ca3d16dcce
This commit is contained in:
commit
308acd760e
80
src/algorithms/cryptography/vigenere-cipher/README.md
Normal file
80
src/algorithms/cryptography/vigenere-cipher/README.md
Normal 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)
|
135
src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js
Normal file
135
src/algorithms/cryptography/vigenere-cipher/VigenereCipher.js
Normal 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();
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user