cryptopals

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

commit 68413610696fcf046bb1ca04491c6d130f8fe7ce
parent 3e69e92f046e2ed9dcdc18a2e91de7a605f3f63a
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue, 15 Aug 2023 22:32:08 -0230

Misc fixes.

Diffstat:
Mdocs/s5.md | 36+++++++++++++++++++++---------------
Mlib/Cryptopals/DH/Core.hs | 12+++++++++++-
Mlib/Cryptopals/DH/Session.hs | 68+++++++++++++++++++++++++++-----------------------------------------
3 files changed, 59 insertions(+), 57 deletions(-)

diff --git a/docs/s5.md b/docs/s5.md @@ -72,11 +72,11 @@ the initial illustration in the next challenge. Opening two instances of GHCi, we can run e.g.: - > bob "3000" dh + > bob "3000" dh in one, and: - > alice "3000" dh + > alice "3000" dh in the other and watch the logs for fun. Here I'll interleave the relevant parts of the logs for readability: @@ -169,8 +169,8 @@ b mod p = 1, as will the shared key from Alice's perspective (since 1 ^ a mod p = 1). Mallory thus needs to forward a 1 as Alice's public key in order for Bob to agree on the shared key. -For g = p, Bob computes B = p ^ b mod p = 0, so Mallory can forward a -p as Alice's public key in order for them to agree on the shared key. +For g = p, Bob computes B = p ^ b mod p = 0, so Mallory can forward p as +Alice's public key in order for them to agree on the shared key. Finally, the case of g = p - 1. Note that for any p > 1 and any even b, we have (for appropriate coefficients a, c, etc.): @@ -193,24 +193,30 @@ So Bob's public key will be either 1 or p - 1 depending on whether his secret key is even or odd. Alice will thus compute: s = B ^ a mod p - = 1 } b even or a even - p - 1 } b odd and a odd. + = 1 } b even or a even (p = 3/4) + p - 1 } b odd and a odd. (p = 1/4). -If Mallory thus forwards A = 1 to Bob, we have: +If Mallory then forwards A = g = p - 1 to Bob, we have: t = A ^ b mod p - = 1. + = 1 } b even (p = 1/2) + = p - 1 } b odd (p = 1/2). + +This all yields the following table: -So, Alice and Bob will agree on the key 1 (i.e., the attack succeeds) -whenever 'b' is even or 'a' is even, an event that occurs with -probability 1/2 + 1/2 - 1/4 = 3/4. + a + even odd + even s = 1, t = 1 s = 1, t = 1 + b + odd s = 1, t = p - 1 s = p - 1, t = p - 1 -(Mallory could ensure the attack works every time by forwarding 1's for -*both* public keys, but that seems against the spirit of the question.) +such that Alice and Bob will agree on the shared key with probability +3/4. Mallory also has to choose which shared key value he uses; if he +uses 1, then the attack succeeds with probability 1/2, and if he uses p +- 1, then it succeeds with probability 1/4. Here are the interleaved logs of a successful attack. Start mallory with -e.g. the `dhngmitm 1` or `dhngmitm p` protocol to perform this attack -with g = 1 or g = p, or use `dhngmitm'` for the g = p - 1 case: +e.g. the `dhngmitm 1`, `dhngmitm p`, or `dhngmitm (p - 1)` protocol: (cryptopals) bob: listening.. (cryptopals) mallory: LiSteNIng.. diff --git a/lib/Cryptopals/DH/Core.hs b/lib/Cryptopals/DH/Core.hs @@ -10,6 +10,7 @@ module Cryptopals.DH.Core ( , modexp , genpair , derivekey + , encodekey ) where import Control.Monad.Primitive @@ -61,5 +62,14 @@ genpair (Group p g) gen = do derivekey :: Group -> Keys -> Natural -> BS.ByteString derivekey (Group p _) Keys {..} pk = let nat = modexp pk sec p - in BS.take 16 . BL.toStrict . CS.bytestringDigest $ CS.sha1 (DB.encode nat) + in encodekey nat + +encodekey :: Natural -> BS.ByteString +encodekey = + BS.take 16 + . BL.toStrict + . CS.bytestringDigest + . CS.sha1 + . DB.encode + diff --git a/lib/Cryptopals/DH/Session.hs b/lib/Cryptopals/DH/Session.hs @@ -18,7 +18,6 @@ module Cryptopals.DH.Session ( , dhmitm , dhngmitm - , dhngmitm' , session , dance @@ -153,10 +152,6 @@ dhng = seval ngeval dhngmitm :: Natural -> Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) dhngmitm = seval . malgeval --- mitm negotiated-group dh evaluation, g = p - 1 -dhngmitm' :: Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) -dhngmitm' = seval malgeval' - -- diffie-hellman protocol eval dheval :: Command @@ -225,18 +220,26 @@ mitmeval = \case SendMessage cip -> do slog $ "rECeIveD CiPHeRTexT " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - msg <- decrypt cip - slog $ "DEcRyptEd cIPheRTeXt: \"" <> TE.decodeLatin1 msg <> "\"" + mmsg <- decryptLenient cip + case mmsg of + Nothing -> + slog "couLdN'T DeCRyPt CiPHertExT" + Just msg -> + slog $ "DEcRyptEd cIPheRTeXt: \"" <> TE.decodeLatin1 msg <> "\"" slog "reLayINg cIpheRtExt" pure $ Just (SendMessage cip) SendTerminal cip -> do slog $ "reCeiVeD CipHeRtExt " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - msg <- decrypt cip - slog $ "DeCrYpteD cIphErteXt: \"" <> TE.decodeLatin1 msg <> "\"" + mmsg <- decryptLenient cip + case mmsg of + Nothing -> + slog "couLdN'T DeCRyPt CiPHertExT" + Just msg -> + slog $ "DeCrYpteD cIphErteXt: \"" <> TE.decodeLatin1 msg <> "\"" slog "ReLaYINg CiPHeRTexT" - pure $ Just (SendTerminal cip) + pure (Just (SendTerminal cip)) cmd -> do slog "RelAyInG coMmaNd" @@ -309,7 +312,6 @@ malgeval malg = \case slog "nOt eXPecTinG gRoUp and PublIc KeY" pure Nothing - -- only want to send bogus key on the first time SendPublic pk -> do slog $ "REceIvED pUBlic keY " <> renderkey pk slog $ "SeNDing BoGuS kEy " <> renderkey malg @@ -317,36 +319,6 @@ malgeval malg = \case cmd -> mitmeval cmd --- negotiated-group mitm protocol eval, g = p - 1 -malgeval' - :: Command - -> StateT Sesh IO (Maybe Command) -malgeval' = \case - AckGroup -> do - slog "rECeiVed aCK" - slog "ReLaYINg ACk" - pure (Just AckGroup) - - SendParams grp pk -> do - slog "nOt eXPecTinG gRoUp and PublIc KeY" - pure Nothing - - SendPublic pk -> do - slog $ "REceIvED pUBlic keY " <> renderkey pk - sesh@Sesh {..} <- S.get - case dhKeys of - Nothing -> do - S.put sesh { - dhKeys = Just (Keys 1 1) - } - slog $ "SeNDing BoGuS kEy " <> renderkey 1 - pure $ Just (SendPublic 1) - Just Keys {..} -> do - slog $ "ReLAyINg pUbliC KeY " <> renderkey pk - pure $ Just (SendPublic pk) - - cmd -> malgeval (p - 1) cmd - genGroup :: Natural -> Natural -> StateT Sesh IO Group genGroup p g = do sesh <- S.get @@ -416,6 +388,20 @@ decrypt cip = do liftIO SE.exitFailure Just msg -> pure msg +decryptLenient :: BS.ByteString -> StateT Sesh IO (Maybe BS.ByteString) +decryptLenient cip = do + sesh@Sesh {..} <- S.get + case dhKey of + Nothing -> do + slog "missing shared key" + liftIO SE.exitFailure + Just k -> do + case CU.unpkcs7 (AES.decryptCbcAES128 k cip) of + Nothing -> do + slog "couldn't decrypt ciphertext" + pure Nothing + Just msg -> pure (Just msg) + renderkey :: Natural -> T.Text renderkey = B16.encodeBase16