cryptopals

Matasano's cryptopals challenges (cryptopals.com).
Log | Files | Refs | README | LICENSE

commit 036cd862f1a7ee69895219e74134958eb9a6a22c
parent 5e5e1796203eebf3ff4ff65306818f0051ede424
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 19 Aug 2023 08:45:33 -0230

Add 5.39.

Diffstat:
Mcryptopals.cabal | 1+
Mdocs/s5.md | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Cryptopals/RSA.hs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/Cryptopals/SRP/Simple.hs | 20++------------------
4 files changed, 138 insertions(+), 18 deletions(-)

diff --git a/cryptopals.cabal b/cryptopals.cabal @@ -32,6 +32,7 @@ library , Cryptopals.Digest.Pure.SHA , Cryptopals.MAC , Cryptopals.MAC.Attacks + , Cryptopals.RSA , Cryptopals.SRP , Cryptopals.SRP.Simple , Cryptopals.Stream.Attacks diff --git a/docs/s5.md b/docs/s5.md @@ -350,3 +350,58 @@ the result pretty quickly: (cryptopals) success (cryptopals) password: omniana +#### 5.39 + +A note on primegen for RSA: I didn't bother with it, as recommended, but +looked at how it should be done. It doesn't seem that bad; one generates +a sufficiently large random number, then tests that it isn't divisible +by the first serveral hundred primes, then performs a probabilistic +primality test sufficiently many times that the error probability is +very small. A reference suggested that the error probability should be +less than 1 / 2^128. + +I used cryptonite's Crypto.Number.Prime module, which implements the +above procedure. + +In any case, everything else is pretty straightforward. To go from +Natural to ByteString and back I used some old functions (roll, unroll) +I wrote for [urbit-hob](http://git.jtobin.io/urbit-hob) a few years +back: + + data Key = Key Natural Natural + deriving (Eq, Show) + + data Keypair = Keypair { + sec :: Key + , pub :: Key + } deriving (Eq, Show) + + keygen :: Int -> IO Keypair + keygen siz = loop where + loop = do + p <- fromIntegral <$> P.generatePrime siz + q <- fromIntegral <$> P.generatePrime siz + let n = p * q + et = pred p * pred q + e = 3 + md = invmod e et + case md of + Nothing -> loop + Just d -> pure $ Keypair (Key e n) (Key d n) + + encrypt :: Key -> BS.ByteString -> BS.ByteString + encrypt (Key e n) m = unroll (DH.modexp (roll m) e n) + + decrypt :: Key -> BS.ByteString -> BS.ByteString + decrypt (Key d n) c = unroll (DH.modexp (roll c) d n) + +Works fine: + + > Keypair sec pub <- keygen 1024 + > let cip = encrypt pub "secret!" + > TIO.putStrLn $ T.take 32 $ B64.encodeBase64 cip + zZFjaw7BR6rP0EaNsTFRnCInwsghANrE + > let msg = decrypt sec cip + > msg + "secret!" + diff --git a/lib/Cryptopals/RSA.hs b/lib/Cryptopals/RSA.hs @@ -0,0 +1,80 @@ +module Cryptopals.RSA ( + invmod + , unroll + , roll + + , keygen + , encrypt + , decrypt + ) where + +import qualified Cryptopals.DH as DH +import qualified Crypto.Number.Prime as P +import qualified Data.Binary as DB +import qualified Data.Bits as B +import qualified Data.ByteString as BS +import Data.List (unfoldr) +import Numeric.Natural + +-- | Simple little-endian ByteString encoding for Naturals. +unroll :: Natural -> BS.ByteString +unroll nat = case nat of + 0 -> BS.singleton 0 + _ -> BS.pack (unfoldr step nat) + where + step 0 = Nothing + step i = Just (fromIntegral i, i `B.shiftR` 8) + +-- | Simple little-endian ByteString decoding for Naturals. +roll :: BS.ByteString -> Natural +roll = foldr unstep 0 . BS.unpack where + unstep b a = a `B.shiftL` 8 B..|. fromIntegral b + +-- egcd/invmod adapted from https://rosettacode.org/wiki/Modular_inverse + +-- for a, b, return x, y, g such that ax + by = g for g = gcd(a, b) +egcd :: Integer -> Integer -> (Integer, Integer, Integer) +egcd a 0 = (1, 0, a) +egcd a b = + let (q, r) = a `quotRem` b + (s, t, g) = egcd b r + in (t, s - q * t, g) + +-- for a, m return x such that ax = 1 mod m +invmod :: Natural -> Natural -> Maybe Natural +invmod (fromIntegral -> a) (fromIntegral -> m) + | 1 == g = Just (pos i) + | otherwise = Nothing + where + (i, _, g) = egcd a m + pos x + | x < 0 = fromIntegral (x + m) + | otherwise = fromIntegral x + +data Key = Key Natural Natural + deriving (Eq, Show) + +data Keypair = Keypair { + sec :: Key + , pub :: Key + } deriving (Eq, Show) + +keygen :: Int -> IO Keypair +keygen siz = loop where + loop = do + p <- fromIntegral <$> P.generatePrime siz + q <- fromIntegral <$> P.generatePrime siz + let n = p * q + et = pred p * pred q + e = 3 + md = invmod e et + case md of + Nothing -> loop + Just d -> pure $ Keypair (Key e n) (Key d n) + +encrypt :: Key -> BS.ByteString -> BS.ByteString +encrypt (Key e n) m = unroll (DH.modexp (roll m) e n) + +decrypt :: Key -> BS.ByteString -> BS.ByteString +decrypt (Key d n) c = unroll (DH.modexp (roll c) d n) + diff --git a/lib/Cryptopals/SRP/Simple.hs b/lib/Cryptopals/SRP/Simple.hs @@ -15,7 +15,6 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BL8 -import qualified Data.HashMap.Lazy as HML import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.IO as TIO @@ -350,26 +349,11 @@ mitm cmd = do Nothing -> do slog "missing required parameters" pure End - Just herpub -> do - slog $ "USiNg PaRaMeTeRs " <> (T.pack . show) herpub + Just (T.pack . show -> herpub) -> do + slog $ "USiNg PaRaMeTeRs " <> herpub <> " aNd " <> B16.encodeBase16 mac slog "GoINg ofFLinE.." pure End _ -> srpsimple cmd -populate - :: MonadIO m - => Natural - -> ReaderT Env m (HML.HashMap BL.ByteString BL.ByteString) -populate herpub = do - Env {..} <- ask - dict <- liftIO $ BL8.readFile "/usr/share/dict/words" - let ls = BL8.lines dict - ns = fmap (fromIntegral . CS.integerDigest . CS.sha256) ls :: [Natural] - ss = fmap (\x -> herpub * DH.modexp eg x en) ns - hs = fmap (CS.bytestringDigest . CS.sha256 . DB.encode) ss - ms = fmap (\s -> CS.bytestringDigest (CS.hmacSha256 s mempty)) hs - - pure . HML.fromList $ zip ms ls -