import Data.Bits
import System.Random
import Data.Char
--Keys, in the form n, k (where k is i for the public key, and j for the private key)
data Key = Public Integer Integer | Private Integer Integer
deriving (Eq, Ord, Show)
-- Extended Euclidian algorithm using the recursive method, returning (gcd, x, y)
eea :: (Integral a) => a -> a -> (a, a, a)
eea a b
| b == 0 = (a, 1, 0)
| otherwise = (d, t, s - q * t)
where
(q, r) = a `divMod` b
(d, s, t) = eea b r
-- Modular multiplicative inverse for a (mod m)
mminv :: (Integral a) => a -> a-> a
mminv a m
| gcd /= 1 = error "Number doesn't have a multiplicative inverse for this modulus!"
| otherwise = x `mod` m
where
(gcd, x, _) = eea a m
-- Modular exponentiation by squaring (using Montgomery's ladder to prevent side-channel (i.e. implementation based)
-- attacks as discussed in http://www.sidechannelattacks.com/details/paper_details.aspx?fid=661
mexp :: Integer -> Integer -> Integer -> Integer
mexp x n m
| n == 0 = 1
| otherwise = fst (foldl (mexp' m) (x, x ^ 2) [ testBit n (k - b - 2) | b <- [0 ..(k - 2)] ])
where
k = ceiling ( logBase 2 (fromIntegral (n + 1)) )
mexp' :: Integer -> (Integer, Integer) -> Bool -> (Integer, Integer)
mexp' m xs b
| b == False = ((x1 ^ 2) `mod` m, (x1 * x2) `mod` m)
| otherwise = ((x1 * x2) `mod` m, (x2 ^ 2) `mod` m)
where
x1 = fst xs
x2 = snd xs
-- Generate public and private keys using the multiplicative inverse of i, mod phi
generateKeys :: Integer -> Integer -> Integer -> (Key, Key)
generateKeys p q i
| gcd /= 1 = error "Public exponent i is not coprime with phi!"
| otherwise = (Public n i, Private n j)
where
n = p * q
phi = (p - 1) * (q - 1)
(gcd, _, _) = eea i phi
j = mminv i phi
-- Code or decode an integer, given a public/private key
rsaCoder :: Key -> Integer -> Integer
rsaCoder (Public n k) x = mexp x k n
rsaCoder (Private n k) x = mexp x k n
-- Primality tester from http://www.haskell.org/haskellwiki/Testing_primality, but using my own mexp function instead of theirs
-- BEGIN --
-- (eq. to) find2km (2^k * n) = (k,n)
find2km :: Integral a => a -> (a,a)
find2km n = f 0 n
where
f k m
| r == 1 = (k,m)
| otherwise = f (k+1) q
where (q,r) = quotRem m 2
-- n is the number to test; a is the (presumably randomly chosen) witness
millerRabinPrimality :: Integer -> Integer -> Bool
millerRabinPrimality n a
| a <= 1 || a >= n-1 =
error $ "millerRabinPrimality: a out of range ("
++ show a ++ " for "++ show n ++ ")"
| n < 2 = False
| even n = False
| b0 == 1 || b0 == n' = True
| otherwise = iter (tail b)
where
n' = n-1
(k,m) = find2km n'
b0 = mexp a m n -- modified this line
b = take (fromIntegral k) $ iterate (squareMod n) b0
iter [] = False
iter (x:xs)
| x == 1 = False
| x == n' = True
| otherwise = iter xs
squareMod :: Integral a => a -> a -> a
squareMod a b = (b * b) `rem` a
-- END --
-- Use the Miller-Rabin method of primality testing, with a witness of 100 (i.e. a non-prime probability of 2^(-100),
-- according to http://snippets.dzone.com/posts/show/4200)
primeTest :: Integer -> Bool
primeTest x = millerRabinPrimality x 100
-- Generate an n-bit random prime number
getPrime :: Integer -> IO Integer
getPrime n = do
r <- randomRIO (2 ^ n, (2 ^ (n + 1)) - 1)
if (primeTest r)
then
return r
else
getPrime n
-- Encode a string byte-wise as an list of RSA-encrypted integers (this is not a good way of doing it, as frequency
-- analysis can easily be performed for frequent characters
encode:: String -> Key -> [Integer]
encode s k = [rsaCoder k (toInteger $ ord i) | i <- s]
-- Decode a list of RSA-encrypted integers byte-wise to a string
decode:: [Integer] -> Key -> String
decode s k = [chr $ fromInteger $ rsaCoder k i | i <- s]
main :: IO()
main = do
p <- getPrime 256
q <- getPrime 256
i <- getPrime 256
putStr $ "p: " ++ (show p) ++ " " ++ show (primeTest p) ++ "\n"
putStr $ "q: " ++ (show q) ++ " " ++ show (primeTest q) ++ "\n"
putStr $ "i: " ++ (show i) ++ " " ++ show (primeTest i) ++ "\n"
let keys = generateKeys p q i
let pub = fst keys
let priv = snd keys
putStr $ show pub
putStr "\n"
putStr $ show priv
putStr "\nType the text to encode:\n"
plaintext <- getLine
putStr "\n"
let encrypted = encode plaintext pub
putStr $ "Encrypted:\n" ++ (show encrypted) ++ "\n"
let decrypted = decode encrypted priv
putStr $ "Decrypted:\n" ++ (show decrypted) ++ "\n"