# Brute force Caesar Cipher decrypter

I wrote the following that tries to crack a message that was encrypted via a simple Caesar cipher through brute force. I'm fairly new to Haskell, so any and all input is appreciated. Because there are infinite ways that something could have been encrypted, it assumes very simple "offset" encryption; like what decShift uses. It also doesn't assume any "wrapping" if an offset character goes out of range.

It works fine (from my testing), I'd just like any input on how it could be made more readable, idiomatic, or generally better.

Use:

simpDec "Encrypted Message", which returns a list of tuples where the first element is the key used to obtain the decryption, and the second is the decrypted message (it returns a list because it's possible for it to return more then one possible decryption).

decUpper = 127 --Upperbound of normal characters
decLower = 32 --Lowerbound of normal characters
decRange = decUpper - decLower --Total range

type Message = String

--Checks if the char is in range
isInRange :: Char -> Bool
isInRange char = ichar <= decUpper && ichar >= decLower
where ichar = ord char

--Makes sure all chars in the Message are in range
isPossible :: Message -> Bool
isPossible [] = False
isPossible m = null [letter | letter <- m, not $isInRange letter] --Returns a list > 0 if it finds consecutive symbols conSymList :: Message -> Char -> [Char] conSymList [] _ = [] conSymList (x:xs) lastChar | isSym x && isSym lastChar = x : conSymList xs x | otherwise = conSymList xs x where isSym c = isSymbol c || isPunctuation c --Checks if a string has 2 symbols beside each other (meaning its improbable) isProbable :: Message -> Bool isProbable [] = False isProbable m = null$ conSymList m 'a'

--Shifts all characters by n
decShift :: Message -> Int -> Message
decShift [] _ = []
decShift (x:xs) n
| iNewChar > 0 = chr iNewChar : decShift xs n
| otherwise = x : decShift xs n
where iNewChar = ord x + n

--Returns a list of tuples of the offset used, and the shifted Message
getCombos :: Message -> [(Int,Message)]
getCombos m = [(off,decShift m off) | off <- [decRange * (-1)..decRange]]

--Returns a list of tuples of possible decryptions (KeyUsed,Message)
simpDec :: Message -> [(Int, Message)]
simpDec m = [(key,try) | (key,try) <- getCombos m, isPossible try && isProbable try]

import Data.Char (chr, isPunctuation, isSymbol, ord)

decUpper = 127 --Upperbound of normal characters
decLower = 32 --Lowerbound of normal characters
decRange = decUpper - decLower --Total range

type Message = String

-- | Checks if the char is in range
isInRange :: Char -> Bool
isInRange char = ichar <= decUpper && ichar >= decLower
where ichar = ord char

-- Test if all chars in the Message are in range
isPossible :: Message -> Bool
isPossible = all isInRange

-- Turn a list into a list of pairs
mkPairs :: [a] -> [(a,a)]
mkPairs xs = zip xs (tail xs)

-- no consecutive symbols in Message
isProbable :: Message -> Bool
isProbable = not . any (\(x,y) -> isSym x && isSym y) . mkPairs
where isSym c = isSymbol c || isPunctuation c

-- Shifts all characters by n
decShift :: Message -> Int -> Message
decShift []     _  = []
decShift (x:xs) n
| iNewChar > 0 = chr iNewChar : decShift xs n
| otherwise    = x : decShift xs n
where iNewChar = ord x + n

-- Returns a list of tuples of the offset used, and the shifted Message
getCombos :: Message -> [(Int,Message)]
getCombos m = [(off,decShift m off) | off <- [negate decRange .. decRange]]

-- Returns a list of tuples of possible decryptions (KeyUsed,Message)
simpDec :: Message -> [(Int, Message)]
simpDec = filter ((\x -> isPossible x && isProbable x) . snd) . getCombos

Changes:

• used more function composition
• simpDec is really a filter on results. getCombos generates data so a list comprehension makes sense.
• simplify isProbable with the definition of mkPairs
• simplify isPossible using the all function.