54 lines
2.5 KiB
Haskell
54 lines
2.5 KiB
Haskell
-- Each character on a computer is assigned a unique code and the preferred
|
|
-- standard is ASCII (American Standard Code for Information Interchange). For
|
|
-- example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107.
|
|
--
|
|
-- A modern encryption method is to take a text file, convert the bytes to
|
|
-- ASCII, then XOR each byte with a given value, taken from a secret key. The
|
|
-- advantage with the XOR function is that using the same encryption key on the
|
|
-- cipher text, restores the plain text; for example, 65 XOR 42 = 107, then 107
|
|
-- XOR 42 = 65.
|
|
--
|
|
-- For unbreakable encryption, the key is the same length as the plain text
|
|
-- message, and the key is made up of random bytes. The user would keep the
|
|
-- encrypted message and the encryption key in different locations, and without
|
|
-- both "halves", it is impossible to decrypt the message.
|
|
--
|
|
-- Unfortunately, this method is impractical for most users, so the modified
|
|
-- method is to use a password as a key. If the password is shorter than the
|
|
-- message, which is likely, the key is repeated cyclically throughout the
|
|
-- message. The balance for this method is using a sufficiently long password
|
|
-- key for security, but short enough to be memorable.
|
|
--
|
|
-- Your task has been made easy, as the encryption key consists of three lower
|
|
-- case characters. Using cipher.txt (right click and 'Save Link/Target
|
|
-- As...'), a file containing the encrypted ASCII codes, and the knowledge that
|
|
-- the plain text must contain common English words, decrypt the message and
|
|
-- find the sum of the ASCII values in the original text.
|
|
|
|
import Euler
|
|
import Data.Attoparsec.Text
|
|
import Data.Bits
|
|
import Data.Char
|
|
import Debug.Trace
|
|
|
|
import qualified Data.Set as S
|
|
import qualified Data.Text as T
|
|
import qualified Data.Text.IO as T
|
|
|
|
numbersP = decimal `sepBy` char ',' <* optional endOfLine
|
|
|
|
data GSLWord = GSL { gslIndex :: Int, gslFrequency :: Int, gslWord :: T.Text }
|
|
gslWordP = GSL <$> decimal <* skipSpace <*> decimal <* skipSpace <*> takeWhile1 isPrint
|
|
gslListP = gslWordP `sepBy` endOfLine <* optional endOfLine
|
|
|
|
main = do
|
|
Right xs <- parseOnly numbersP <$> T.readFile "p059_cipher.txt"
|
|
Right gsl <- parseOnly (S.fromList . map gslWord <$> gslListP) <$> T.readFile "p059_gsl.txt"
|
|
let lcChars = map fromEnum ['a'..'z']
|
|
print $ snd $ maximumBy (compare `on` fst) $ do
|
|
key <- sequenceA $ replicate 3 lcChars
|
|
let ys = zipWith (xor) (cycle key) xs
|
|
guard $ all (\c -> c >= 1 && c <= 127) ys
|
|
let yt = T.pack $ map toEnum ys
|
|
return (length $ filter (`S.member` gsl) $ map (T.filter isAlpha) $ T.words yt, sum ys)
|