cryptopals

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

commit d6300a6ddf03e6ff84b49cddaed8b907a45a11c3
parent 706563d1fa30a3bd75f94e6987b90860f8fa522a
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue,  1 Aug 2023 18:41:59 -0230

Add 3.23.

Diffstat:
Mdocs/s3.md | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/Cryptopals/Stream/RNG/MT19937.hs | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
2 files changed, 152 insertions(+), 28 deletions(-)

diff --git a/docs/s3.md b/docs/s3.md @@ -129,6 +129,10 @@ There's also a binary: 3421126067 4083286876 +(N.b., the [original +paper](http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/ARTICLES/mt.pdf) on +the Mersenne Twister is very well-written and readable.) + #### 3.22 After the fourth or fifth time my children woke me up in the middle of @@ -165,3 +169,77 @@ we get: So, via the same timestamp calculator, it was seeded at Sun Jul 30 2023 06:05:00 GMT-0230 (heure d’été de Terre-Neuve). +#### 3.23 + +A Mersenne Twister outputs elements of its internal state transformed +by a tempering map. The state is perturbed every 624 iterations (the +"twist"), but if we have all 624 outputs generated from a constant +internal state, that state can be recovered by running the outputs +through the inverse tempering map. + +The tempering transform (in Cryptopals.Stream.RNG.MT19937) can be +expressed by: + + temper :: Word32 -> Word32 + temper = e4 . e3 . e2 . e1 where + e1 = rs u + e2 = ls s b + e3 = ls t c + e4 = rs l + +and the inverse transform by: + + untemper :: Word32 -> Word32 + untemper = n1 . n2 . n3 . n4 where + n1 = rsinv u + n2 = lsinv s b + n3 = lsinv t c + n4 = rsinv l + +given the following salad of internal functions: + + ls :: Word32 -> Word32 -> Word32 -> Word32 + ls s m a = a `B.xor` (B.shiftL a (fi s) .&. m) + + rs :: Word32 -> Word32 -> Word32 + rs s a = a `B.xor` B.shiftR a (fi s) + + lsinv :: Word32 -> Word32 -> Word32 -> Word32 + lsinv s bm = loop 0 where + loop j !b + | j >= fi w = b + | otherwise = + let m = mask j (min (fi w - 1) (j + fi s - 1)) + x = ((m .&. b) `B.shiftL` fi s) .&. bm + in loop (j + fi s) (b `B.xor` x) + + rsinv :: Word32 -> Word32 -> Word32 + rsinv s = loop (fi w - 1) where + loop j !b + | j <= 0 = b + | otherwise = + let m = mask (max 0 (j - fi s + 1)) j + x = (m .&. b) `B.shiftR` fi s + in loop (j - fi s) (b `B.xor` x) + + mask :: B.Bits b => Int -> Int -> b + mask l h = loop l B.zeroBits where + loop j !b + | j > h = b + | otherwise = + loop (succ j) (B.setBit b j) + +So we can run a generator for 624 iterations, capture the outputs, and +untemper them to recover the internal state for those 624 iterations +(with the caveat that the outputs we need to observe must occur absent +any intermediate twists of the internal state): + + > let gen = seed 42 + > let (bs, g) = bytes 624 gen + > let cloned = Gen 624 (VU.fromList . fmap untemper $ bs) + > fst (bytes 3 g) + [108880612,791707097,4134543476] + > fst (bytes 3 cloned) + [108880612,791707097,4134543476] + + diff --git a/lib/Cryptopals/Stream/RNG/MT19937.hs b/lib/Cryptopals/Stream/RNG/MT19937.hs @@ -3,6 +3,9 @@ module Cryptopals.Stream.RNG.MT19937 ( , seed , extract , bytes + + , temper + , untemper ) where import qualified Control.Monad.ST as ST @@ -17,28 +20,27 @@ fi = fromIntegral -- following notation in https://en.wikipedia.org/wiki/Mersenne_Twister -w, n, m, r, a, u, d, s, b, t, c, l :: Word32 -w = 32 -n = 624 -m = 397 -r = 31 -a = 0x9908B0DF -u = 11 -d = 0xFFFFFFFF -s = 7 -b = 0x9D2C5680 -t = 15 -c = 0xEFC60000 -l = 18 +w, n, m, r, a, u, s, b, t, c, l :: Word32 +w = 32 -- word size +n = 624 -- degree of recurrence +m = 397 -- 'middle term' +r = 31 -- word separation index +a = 0x9908B0DF -- rational normal form twist matrix coefficients +u = 11 -- tempering parameter +s = 7 -- tempering parameter (shift) +b = 0x9D2C5680 -- tempering parameter (mask) +t = 15 -- tempering parameter (shift) +c = 0xEFC60000 -- tempering parameter (mask) +l = 18 -- tempering parameter f :: Word32 f = 1812433253 lm :: Word32 -lm = B.shiftL 1 (fi r) - 1 -- 0x0111 1111 1111 1111 1111 1111 1111 1111 +lm = B.shiftL 1 (fi r) - 1 -- 0b0111 1111 1111 1111 1111 1111 1111 1111 um :: Word32 -um = B.complement lm -- 0x1000 0000 0000 0000 0000 0000 0000 0000 +um = B.complement lm -- 0b1000 0000 0000 0000 0000 0000 0000 0000 data Gen = Gen !Word32 !(VU.Vector Word32) deriving Eq @@ -46,6 +48,14 @@ data Gen = Gen !Word32 !(VU.Vector Word32) instance Show Gen where show Gen {} = "<MT19937.Gen>" +bytes :: Int -> Gen -> ([Word32], Gen) +bytes = loop mempty where + loop !acc j gen + | j == 0 = (reverse acc, gen) + | otherwise = + let (w, g) = extract gen + in loop (w : acc) (pred j) g + seed :: Word32 -> Gen seed s = Gen n (loop 0 mempty) where loop j !acc @@ -62,13 +72,54 @@ extract gen@(Gen idx _) = then twist gen else gen - y0 = g `VU.unsafeIndex` fi i - y1 = y0 `B.xor` ((B.shiftR y0 (fi u)) .&. d) - y2 = y1 `B.xor` ((B.shiftL y1 (fi s)) .&. b) - y3 = y2 `B.xor` ((B.shiftL y2 (fi t)) .&. c) - y4 = y3 `B.xor` (B.shiftR y3 18) + y = g `VU.unsafeIndex` fi i + + in (temper y, Gen (succ i) g) + +temper :: Word32 -> Word32 +temper = e4 . e3 . e2 . e1 where + e1 = rs u + e2 = ls s b + e3 = ls t c + e4 = rs l + +untemper :: Word32 -> Word32 +untemper = n1 . n2 . n3 . n4 where + n1 = rsinv u + n2 = lsinv s b + n3 = lsinv t c + n4 = rsinv l + +mask :: B.Bits b => Int -> Int -> b +mask l h = loop l B.zeroBits where + loop j !b + | j > h = b + | otherwise = + loop (succ j) (B.setBit b j) + +ls :: Word32 -> Word32 -> Word32 -> Word32 +ls s m a = a `B.xor` (B.shiftL a (fi s) .&. m) + +lsinv :: Word32 -> Word32 -> Word32 -> Word32 +lsinv s bm = loop 0 where + loop j !b + | j >= fi w = b + | otherwise = + let m = mask j (min (fi w - 1) (j + fi s - 1)) + x = ((m .&. b) `B.shiftL` fi s) .&. bm + in loop (j + fi s) (b `B.xor` x) - in (y4, Gen (succ i) g) +rs :: Word32 -> Word32 -> Word32 +rs s a = a `B.xor` B.shiftR a (fi s) + +rsinv :: Word32 -> Word32 -> Word32 +rsinv s = loop (fi w - 1) where + loop j !b + | j <= 0 = b + | otherwise = + let m = mask (max 0 (j - fi s + 1)) j + x = (m .&. b) `B.shiftR` fi s + in loop (j - fi s) (b `B.xor` x) twist :: Gen -> Gen twist (Gen i gen) = ST.runST $ do @@ -95,11 +146,6 @@ twist (Gen i gen) = ST.runST $ do fen <- VU.freeze g pure (Gen 0 fen) -bytes :: Int -> Gen -> ([Word32], Gen) -bytes = loop mempty where - loop !acc j gen - | j == 0 = (reverse acc, gen) - | otherwise = - let (w, g) = extract gen - in loop (w : acc) (pred j) g +clone :: [Word32] -> Gen +clone = Gen n . VU.fromList . fmap untemper