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