Created
October 4, 2020 07:19
-
-
Save erwinv/ea5afb1ca4102b86b78d8c1915ebf8cf to your computer and use it in GitHub Desktop.
Vigenere Cipher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher#Description | |
class VigenereCipher { | |
constructor(...keys) { | |
this.keys = Array.from(keys).map(normalize) | |
} | |
get key() { return this._key } | |
set key(key) { | |
this._keys = [key] | |
this._key = normalize(key) | |
} | |
get keys() { return this._keys } | |
set keys(keys) { | |
// https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher#Variants | |
function combine(acc, keys) { | |
if (keys.length === 0) { | |
return acc | |
} | |
const [nextKey, ...rest] = keys | |
return combine( | |
VigenereCipher.encrypt(acc, nextKey, true /* symmetric */), | |
rest | |
) | |
} | |
this._keys = keys | |
this._key = combine(keys[0], keys.slice(1)) | |
} | |
_alphabet = memoize(alphabet) | |
encrypt(plainText, symmetric = false) { | |
return this.process(normalize(plainText), keyChar => ({ | |
reference: this._alphabet(), | |
target: this._alphabet(keyChar), | |
}), symmetric) | |
} | |
decrypt(cipherText) { | |
return this.process(cipherText, keyChar => ({ | |
reference: this._alphabet(keyChar), | |
target: this._alphabet(), | |
})) | |
} | |
process(text, resolver, symmetric = false) { | |
return zip(this._key, text, symmetric) | |
.map(([keyChar, textChar]) => ({ | |
...resolver(keyChar), | |
point: textChar, | |
})) | |
.map(({reference, target, point}) => target.charAt(reference.indexOf(point))) | |
.join('') | |
} | |
static encrypt(key, plainText, symmetric = false) { | |
return new VigenereCipher(key).encrypt(plainText, symmetric) | |
} | |
static decrypt(key, cipherText) { | |
return new VigenereCipher(key).decrypt(cipherText) | |
} | |
} | |
function normalize(s) { | |
return `${s}`.split(/\s+/).join('').toUpperCase() | |
} | |
function zip(key, text, symmetric = false) { | |
const zippedLength = symmetric ? | |
lcm(key.length, text.length) | |
: text.length | |
return Array(zippedLength).fill() | |
.map((_, i) => [key[i % key.length], text[i % text.length]]) | |
} | |
function alphabet(pivotChar) { | |
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
if (!pivotChar) return alphabet | |
const pivot = alphabet.indexOf(pivotChar) | |
return alphabet.slice(pivot) + alphabet.slice(0, pivot) | |
} | |
function memoize(fn) { | |
if (fn.length > 1) return fn | |
let cache = new Map() | |
return x => { | |
const key = Symbol.for(x) | |
if (!cache.has(key)) { | |
cache.set(key, fn(x)) | |
} | |
return cache.get(key) | |
} | |
} | |
function gcd(a, b) { | |
return b === 0 ? a : gcd(b, a % b) | |
} | |
function lcm(a, b) { | |
return (a * b) / gcd(a, b) | |
} | |
const cipher = new VigenereCipher('lemon') | |
let cipherText = cipher.encrypt('attack at dawn') | |
console.log( | |
'cipher text:', cipherText, | |
'cipher key:', cipher.key, | |
'decrypted:', cipher.decrypt(cipherText) | |
) | |
cipher.keys = ['go', 'cat'] | |
cipherText = cipher.encrypt('attack at dawn') | |
console.log( | |
'cipher text:', cipherText, | |
'cipher keys:', cipher.keys, | |
'combined key:', cipher.key, | |
'decrypted:', cipher.decrypt(cipherText) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment