cryptopals

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

commit 4ab6c492675ae8cadd151004cd1e51e07d7f89c2
parent 4c29eb21c741ada8593b5266d39b4561b25071b6
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 12 Aug 2023 11:36:12 -0230

Add 5.33.

Diffstat:
M.ghci | 2++
MREADME.md | 1+
Mcryptopals.cabal | 1+
Adocs/s5.md | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Cryptopals/DH.hs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 134 insertions(+), 0 deletions(-)

diff --git a/.ghci b/.ghci @@ -15,3 +15,5 @@ import Data.Traversable import qualified Data.Text.IO as TIO import qualified Data.Text.Encoding as TE import qualified Data.Text as T +import Numeric.Natural +import qualified System.Random.MWC as MWC diff --git a/README.md b/README.md @@ -12,4 +12,5 @@ to build binaries. Use `cabal install` to (I think) dump them in * [Problem Set 2](docs/s2.md) * [Problem Set 3](docs/s3.md) * [Problem Set 4](docs/s4.md) +* [Problem Set 5](docs/s5.md) diff --git a/cryptopals.cabal b/cryptopals.cabal @@ -23,6 +23,7 @@ library Cryptopals.AES , Cryptopals.Block.Attacks , Cryptopals.Block.Tools + , Cryptopals.DH , Cryptopals.Digest.Pure.MD4 , Cryptopals.Digest.Pure.SHA , Cryptopals.MAC diff --git a/docs/s5.md b/docs/s5.md @@ -0,0 +1,80 @@ +#### 5.33 + +The basic Diffie-Hellman algorithm for key exchange between Alice and +Bob goes as follows. Alice and Bob agree on a cyclic group of some +particular order to use. They each randomly and independently pick some +number of times to perform the group operation (which must be less than +the order of the group) and perform the group operation on the generator +that number of times, publishing their results. For generator g, Alice's +number x, and Bob's number y: Alice publishes g ^ x, and Bob publishes g +^ y. + +Each then performs his or her own secret number of additional group +operations on the other's public result, establishing the key g ^ xy. +Since the discrete logarithm problem is hard, an eavesdropper can't (for +an appropriate group) determine how many times either has performed the +individual group operations, even given g; he can only naïvely calculate +g ^ (x + y). + +For the initial illustration here we're using a decidedly insecure +group, i.e. the multiplicative group of 16-bit words modulo 37: + + > gen <- MWC.create + > let p = 37 + > let g = 5 + > a <- fmap (`mod` p) (MWC.uniform gen) :: IO Word16 + > b <- fmap (`mod` p) (MWC.uniform gen) :: IO Word16 + > let bigA = g ^ a `mod` p + > let bigB = g ^ b `mod` p + > let s = bigB ^ a `mod` p + > let t = bigA ^ b `mod` p + > s == t + True + > let k = S.sha1 . BP.runPut $ BP.putWord16be s + > k + ace6f761db204030c1a65c0930bd01fd55ecc429 + +For the big guns, we can pull out GHC's Natural type, for which +mwc-random (our preferred random library) can generate something in a +range. First a modular exponentiation routine: + + -- modified from https://gist.github.com/trevordixon/6788535 + modexp :: Natural -> Natural -> Natural -> Natural + modexp b e m + | e == 0 = 1 + | otherwise = + let t = if B.testBit e 0 then b `mod` m else 1 + in t * modexp ((b * b) `mod` m) (B.shiftR e 1) m `mod` m + +and given that (and appropriate p, g), the key exchange: + + > gen <- MWC.create + > a <- fmap (`mod` p) (MWC.uniformRM (0, p - 1) gen) + > b <- fmap (`mod` p) (MWC.uniformRM (0, p - 1) gen) + > let bigA = modexp g a p + > let bigB = modexp g b p + > let s = modexp bigB a p + > let t = modexp bigA b p + > s == t + True + +Our SHA1 function needs a bytestring to operate on, so to hash the +Natural key we need a quick serialization routine from Naturals +to ByteString. I happened to write one of these a few years ago +for [urbit-hob](http://git.jtobin.io/urbit-hob); it serializes in +little-endian format, and here we'll use a lazy bytestring: + + unroll :: Natural -> BL.ByteString + unroll nat = case nat of + 0 -> BL.singleton 0 + _ -> BL.pack (L.unfoldr step nat) + where + step 0 = Nothing + step i = Just (fromIntegral i, i `B.shiftR` 8) + +so we can get a key via: + + > let k = S.sha1 $ unroll s + > k + 6a86b02347be741dfb6f876819022ae1d7adda18 + diff --git a/lib/Cryptopals/DH.hs b/lib/Cryptopals/DH.hs @@ -0,0 +1,50 @@ + +module Cryptopals.DH ( + p + , g + , modexp + + , unroll + , roll + ) where + +import Control.Monad.Primitive +import qualified Control.Monad.Trans.Reader as R +import qualified Cryptopals.Digest.Pure.SHA as S +import qualified Data.Binary.Get as BG +import qualified Data.Binary.Put as BP +import Data.Bits ((.|.)) +import qualified Data.Bits as B +import qualified Data.ByteString.Lazy as BL +import qualified Data.List as L +import Numeric.Natural +import qualified System.Random.MWC as MWC +import GHC.Word (Word16) + +-- modified from https://gist.github.com/trevordixon/6788535 +modexp :: Natural -> Natural -> Natural -> Natural +modexp b e m + | e == 0 = 1 + | otherwise = + let t = if B.testBit e 0 then b `mod` m else 1 + in t * modexp ((b * b) `mod` m) (B.shiftR e 1) m `mod` m + +-- little-endian natural serialization +unroll :: Natural -> BL.ByteString +unroll nat = case nat of + 0 -> BL.singleton 0 + _ -> BL.pack (L.unfoldr step nat) + where + step 0 = Nothing + step i = Just (fromIntegral i, i `B.shiftR` 8) + +roll :: BL.ByteString -> Natural +roll = foldr unstep 0 . BL.unpack where + unstep b a = a `B.shiftL` 8 .|. fromIntegral b + +p :: Natural +p = 0xffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff + +g :: Natural +g = 2 +