Vigenère Cipher
Monday, August 21, 2023
The Vigenère cipher is a historically interesting method of encrypting a piece of text using a Caesar cipher where each letter is encoded based on a different letter from a repeating input “key” text. If the recipient knows the key, they can recover the plaintext by reversing the encoding process.
For example, suppose that the plaintext to be encrypted is
attackatdawn
The person sending the message chooses a keyword and repeats it until it matches the length of the plaintext, for example, the keyword “LEMON”:
LEMONLEMONLE
We start by defining the available alphabet:
CONSTANT: LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
We can implement this using an inner word and a flag for whether we are encrypting or decrypting a message. For convenience, we will use local variables and make sure both the key and the message are uppercase and drop all non-letters from the input:
:: viginere ( msg key encrypt? -- str )
msg >upper :> MSG
key >upper :> KEY
[
0 MSG [| ch |
ch LETTERS index [| i |
[ 1 + KEY length mod i ] keep
KEY nth LETTERS index
encrypt? [ + ] [ - ] if
LETTERS length [ + ] [ mod ] bi
LETTERS nth ,
] when*
] each drop
] "" make ;
: >vigenere ( msg key -- encrypted ) t viginere ;
: vigenere> ( msg key -- decrypted ) f viginere ;
We can make some test cases showing that it works:
{ "VBVTQBYOLCPB" } [
"ATTACKATDAWN" "VICTORY" >vigenere
] unit-test
{ "ATTACKATDAWN" } [
"VBVTQBYOLCPB" "VICTORY" vigenere>
] unit-test
It gained a reputation for being exceptionally strong – even being described as “unbreakable” and “impossible of translation”. It turns out there is a big difference between difficult and impossible, and a cryptanalysis ultimately found various weaknesses that enabled the cipher to be broken or attacked in certain ways.
While not inventing the cipher that holds his name, Vigenère actually invented a stronger version called an autokey cipher which modifies the key during the encoding and decoding process avoiding one of the weak points when the key is shorter than the message and the letters are reused.
It is a smidge more complex to implement but shares the same structure as the version above:
:: autokey ( msg key encrypt? -- str )
msg >upper :> MSG
key >upper >sbuf :> KEY
[
0 MSG [| ch |
ch LETTERS index [| i |
[ 1 + i ] keep
KEY encrypt? [ ch suffix! ] when nth
LETTERS index
encrypt? [ + ] [ - ] if
LETTERS length [ + ] [ mod ] bi
LETTERS nth
encrypt? [ KEY over suffix! drop ] unless ,
] when*
] each drop
] "" make ;
: >autokey ( msg key -- encrypted ) t autokey ;
: autokey> ( msg key -- decrypted ) f autokey ;
And some more test cases:
{ "FWGLCDEJIKG" } [
"AWESOMENESS" "FACTOR" >autokey
] unit-test
{ "AWESOMENESS" } [
"FWGLCDEJIKG" "FACTOR" autokey>
] unit-test