Encryption¶

Caesar Cipher¶

In [1]:
caesarEnc :: Char -> Char
caesarEnc = succ . succ . succ

caesarDec :: Char -> Char
caesarDec = pred . pred . pred

enc :: String -> String
enc = map caesarEnc

dec :: String -> String
dec = map caesarDec

enc "CAESAR"
dec "DEF"
"FDHVDU"
"ABC"

Vigenere Cipher¶

In [3]:
data Alpha = A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z deriving (Eq, Show, Ord, Enum, Bounded)

bSucc :: (Enum a, Bounded a, Eq a) => a -> a
bSucc b = if b == maxBound then minBound else succ b

bPred :: (Enum a, Bounded a, Eq a) => a -> a
bPred b = if b == minBound then maxBound else pred b

nSucc :: (Enum a, Bounded a, Eq a) => Int -> a -> a
nSucc 0 = id
nSucc 1 = bSucc
nSucc (-1) = bPred
nSucc n
  | n > 1 = bSucc . nSucc (n-1)
  | n < -1 = bPred . nSucc (n+1)

subEnum :: (Ord a, Enum a) => a -> a -> Int
subEnum x y
  | x == y = 0
  | x > y = 1 + subEnum x (succ y)
  | x < y = subEnum (succ x) y - 1

vigEnc :: Alpha -> Alpha -> Alpha
vigEnc k = nSucc (k `subEnum` minBound)

-- vigEnc minBound A
-- bSucc E
-- bSucc A
-- C `subEnum` A
-- nSucc 2 A
-- vigEnc C A

enc :: [Alpha] -> [Alpha] -> [Alpha]
enc key pt = map (uncurry vigEnc) (zip (cycle key) pt)

enc [D,U,H] [C,R,Y,P,T,O]
Use zipWith
Found:
map (uncurry vigEnc) (zip (cycle key) pt)
Why Not:
zipWith (curry (uncurry vigEnc)) (cycle key) pt
[F,L,F,S,N,V]

How Ciphers Work¶

  • Permutation
    • Uniquely inversable transform from item to item
  • Mode of Operation
    • Uses a permutation to process messages of arbitrary size
In [ ]:
import Control.Lens
import Data.List.NonEmpty qualified as NE

data Alpha = A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z deriving (Eq, Show, Ord, Enum, Bounded)

bSucc :: (Enum a, Bounded a, Eq a) => a -> a
bSucc b = if b == maxBound then minBound else succ b

bPred :: (Enum a, Bounded a, Eq a) => a -> a
bPred b = if b == minBound then maxBound else pred b

-- I had to make this newtype wrapper because GHC complained about having Iso directly in a typeclass function type definition.newtype IsoAlpha = IsoAlpha (Iso' Alpha Alpha)

encoder :: IsoAlpha -> Alpha -> Alpha
encoder (IsoAlpha iso) = withIso (from iso) (\f _ -> f)

decoder :: IsoAlpha -> Alpha -> Alpha
decoder (IsoAlpha iso) = withIso (from iso) (\_ g -> g)

class Permutation p where
    permute :: p -> [IsoAlpha]

class (Permutation m) => Mode m where
  encrypt :: m -> [Alpha] -> [Alpha]
  decrypt :: m -> [Alpha] -> [Alpha]

-- Caesar ------------------------------------------------------------

data Caesar = Caesar

instance Permutation Caesar where
  permute _ = pure $ IsoAlpha (iso (bPred . bPred . bPred) (bSucc . bSucc . bSucc))

instance Mode Caesar where
  encrypt Caesar pt = let enc = head (permute Caesar) in map (encoder enc) pt
  decrypt Caesar et = let enc = head (permute Caesar) in map (decoder enc) et

let trans = head (permute Caesar) in encoder trans B

-- Vigenere ----------------------------------------------------------

newtype Vigenere = Vigenere (NE.NonEmpty Alpha)

nSucc :: (Enum a, Bounded a, Eq a) => Int -> a -> a
nSucc 0 = id
nSucc 1 = bSucc
nSucc (-1) = bPred
nSucc n
  | n > 1 = bSucc . nSucc (n-1)
  | n < -1 = bPred . nSucc (n+1)

subEnum :: (Ord a, Enum a) => a -> a -> Int
subEnum x y
  | x == y = 0
  | x > y = 1 + subEnum x (succ y)
  | x < y = subEnum (succ x) y - 1

vigEnc :: Alpha -> Alpha -> Alpha
vigEnc k = nSucc (k `subEnum` minBound)

vigDec :: Alpha -> Alpha -> Alpha
vigDec k = let x = k `subEnum` minBound in nSucc (-x)

instance Permutation Vigenere where
  permute (Vigenere key) = [ IsoAlpha $ iso (vigDec k) (vigEnc k) | k <- NE.toList $ NE.cycle key]

instance Mode Vigenere where
  encrypt v pt = let encs = permute v in zipWith encoder encs pt
  decrypt v ct = let encs = permute v in zipWith decoder encs ct

let ts = permute (Vigenere $ NE.fromList [D,U,H]) in [encoder f a | (f,a) <- zip ts [C,R,Y,P,T,O]]

vDUH = Vigenere $ NE.fromList [D,U,H]

encrypt vDUH [C,R,Y,P,T,O]
encrypt Caesar [C,R,Y,P,T,O]
decrypt Caesar [F,U,B,S,W,R]
decrypt vDUH [F,L,F,S,N,V]

One-Time Pad¶

In [36]:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ImportQualifiedPost #-}

import Control.Lens
import Data.List.NonEmpty qualified as NE
import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Bits (xor)

data Alpha = A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z deriving (Eq, Show, Ord, Enum, Bounded)

bSucc :: (Enum a, Bounded a, Eq a) => a -> a
bSucc b = if b == maxBound then minBound else succ b

bPred :: (Enum a, Bounded a, Eq a) => a -> a
bPred b = if b == minBound then maxBound else pred b

nSucc :: (Enum a, Bounded a, Eq a) => Int -> a -> a
nSucc 0 = id
nSucc 1 = bSucc
nSucc (-1) = bPred
nSucc n
  | n > 1 = bSucc . nSucc (n-1)
  | n < -1 = bPred . nSucc (n+1)

-- Iso Wrapper -----------------------------------------------------

newtype UniqueEncoding plainText cipherText = UniqueEncoding (Iso' plainText cipherText)

mkUniqueEncoding :: (plainText -> cipherText) -> (cipherText -> plainText) -> UniqueEncoding plainText cipherText
mkUniqueEncoding enc dec = UniqueEncoding $ iso enc dec

encoder :: UniqueEncoding plainText cipherText -> plainText -> cipherText
encoder (UniqueEncoding iso) = withIso (from iso) (\_ f -> f)

decoder :: UniqueEncoding plainText cipherText -> cipherText -> plainText
decoder (UniqueEncoding iso) = withIso (from iso) (\g _ -> g)

-- Typeclasses ------------------------------------------------------

class Permutation p plainText cipherText where
  permute :: p -> NE.NonEmpty (UniqueEncoding plainText cipherText)

class (Permutation m plainText cipherText) => Mode m plainText cipherText where
  encrypt :: m -> [plainText] -> [cipherText]
  decrypt :: m -> [cipherText] -> [plainText]

-- Boolean Format --------------------------------------------------

data BooleanOneTimePad = BooleanOneTimePad [Bool]

instance Permutation BooleanOneTimePad Bool Bool where
  permute (BooleanOneTimePad [b]) = let f = xor b in pure $ mkUniqueEncoding f f
  permute (BooleanOneTimePad (b:bs)) = let f = xor b in mkUniqueEncoding f f `NE.cons` permute (BooleanOneTimePad bs)

instance Mode BooleanOneTimePad Bool Bool where
  encrypt botp pt = let perms = permute botp in zipWith encoder (NE.toList perms) pt
  decrypt botp ct = let perms = permute botp in zipWith decoder (NE.toList perms) ct

key1 :: BooleanOneTimePad
key1 = BooleanOneTimePad [True,False,True,True,False,True,False,False]

raw1 :: [Bool]
raw1 = [False,True,True,False,True,True,False,True]

msg1 :: [Bool]
msg1 = encrypt key1 raw1

raw1' :: [Bool]
raw1' = decrypt key1 msg1

msg1
raw1'
raw1 == raw1'

-- Alpha Format -----------------------------------------------------

data AlphaOneTimePad = AlphaOneTimePad [Int]

instance Permutation AlphaOneTimePad Alpha Alpha where
  permute (AlphaOneTimePad [s]) = let f = nSucc s; g = nSucc (-s) in pure $ mkUniqueEncoding f g
  permute (AlphaOneTimePad (s:ss)) = let f = nSucc s; g = nSucc (-s) in mkUniqueEncoding f g `NE.cons` permute (AlphaOneTimePad ss)

instance Mode AlphaOneTimePad Alpha Alpha where
  encrypt = stdEncrypter
  decrypt = stdDecrypter

stdEncrypter :: (Permutation x y z) => x -> [y] -> [z]
stdEncrypter p pt = let perms = permute p in zipWith encoder (NE.toList perms) pt

stdDecrypter :: (Permutation x y z) => x -> [z] -> [y]
stdDecrypter p ct = let perms = permute p in zipWith decoder (NE.toList perms) ct

key2 :: AlphaOneTimePad
key2 = AlphaOneTimePad [3,2,5,4,0,9,4,1,9,8,7,6]

raw2 :: [Alpha]
raw2 =                 [L,E,N,A,B,A,B,Y,G,I,R,L]

msg2 :: [Alpha]
msg2 = encrypt key2 raw2

raw2' :: [Alpha]
raw2' = decrypt key2 msg2

msg2
raw2'
raw2 == raw2'
Use const
Found:
\ g _ -> g
Why Not:
const
Use newtype instead of data
Found:
data BooleanOneTimePad = BooleanOneTimePad [Bool]
Why Not:
newtype BooleanOneTimePad = BooleanOneTimePad [Bool]
Use newtype instead of data
Found:
data AlphaOneTimePad = AlphaOneTimePad [Int]
Why Not:
newtype AlphaOneTimePad = AlphaOneTimePad [Int]
[True,True,False,True,True,False,False,True]
[False,True,True,False,True,True,False,True]
True
[O,G,S,E,B,J,F,Z,P,Q,Y,R]
[L,E,N,A,B,A,B,Y,G,I,R,L]
True

Encryption Security¶

  • This comment about the probability of something happening (specifically about finding the (a?) valid key) basically says that security is a ratio between truth and possibility. I think it's important to call out that just because some datatype may technically be able to represent some 2^256 space, that doesn't necessarily mean that all 2^256 options are possible. The algorithms of the key generation, for example, may be structured such that keys are only generated in some 2^8 subset. Then, you can say "I have a 256-bit key" all you want, but you don't actually have the purported level of security. Obviously, finding such a subset would require analyzing the algorithms and not just the public ciphertext.