cryptopals

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

commit 9f663beda8bacccc4d138aa67543ff7d3069e73c
parent 67c029356b1fe359bc143a1ecb61e190d805012a
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun,  6 Aug 2023 20:45:10 -0230

Add 4.27.

Diffstat:
Mdocs/s4.md | 44++++++++++++++++++++++++++++++++++++++++++++
Mlib/Cryptopals/Block/Attacks.hs | 46+++++++++++++++++++++++++++++++++++++---------
2 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/docs/s4.md b/docs/s4.md @@ -78,4 +78,48 @@ the desired plaintext: #### 4.27 +This one works exactly as advertised. The ivl{encrypt, decrypt}CbcAES128 +functions in Cryptopals.Block.Attacks will {en, de}crypt inputs in CBC +mode using identical key and IV's, and ivlVerifier serves as the desired +oracle. + +First, assembling the nasty ciphertext: + + > let b = "YELLOW SUBMARINE" + > B16.encodeBase16 consistentKey + "d18a7e96a50f45cb9b928e502c2b310d" + > let cip = ivlEncryptCbcAES128 consistentKey (b <> b <> b) + > let cs = CU.chunks 16 cip + > let mcip = cs !! 0 <> BS.replicate 16 0 <> cs !! 0 + +And now recovering the key: + + > let Left mpay = bfcIvVerifier mcip + > let ps = CU.chunks 16 mpay + > B16.encodeBase16 $ (ps !! 0) `CU.fixedXor` (ps !! 2) + "d18a7e96a50f45cb9b928e502c2b310d" + +As for how this works: refer back to the omnipresent CBC-mode decryption +scheme from 2.16 (here modified): + + for ciphertext c = (c_1, c_2, c_3) + block decryption w/key k dec_k + xor operator + + + let p_1 = dec_k(c_1) + k + p_2 = dec_k(c_2) + c_1 + p_3 = dec_k(c_3) + c_2 + + in plaintext p = (p_1, p_2, p_3) + +So if we provide the modified `c = (c_1, 0, c_1)`, decryption will give us: + + p_1' = dec_k(c_1) + k + p_2' = dec_k(0) + c_1 + p_3' = dec_k(c_1) + 0 + +such that, trivially: + + p_1' + p_3' = dec_k(c_1) + k + dec_k(c_1) + 0 + = k. diff --git a/lib/Cryptopals/Block/Attacks.hs b/lib/Cryptopals/Block/Attacks.hs @@ -362,13 +362,41 @@ rnBest s = loop (0, 1 / 0, s) 0 where -- CBC key recovery w/IV=key -bfcIvEncrypter :: BS.ByteString -> BS.ByteString -bfcIvEncrypter input = - AES.encryptCbcAES128 consistentKey consistentKey padded - where - filtered = BS.filter (`notElem` (BS.unpack ";=")) input - plaintext = "comment1=cooking%20MCs;userdata=" <> filtered <> - ";comment2=%20like%20a%20pound%20of%20bacon" - padded = CU.lpkcs7 plaintext - +-- Usually we include the IV with the ciphertext, but that won't fly here +-- as it would very obviously expose the key. Instead let's omit the IV +-- in the ciphertext: +ivlEncryptCbcAES128 + :: BS.ByteString -> BS.ByteString -> BS.ByteString +ivlEncryptCbcAES128 key plaintext = loop key mempty (BS.splitAt 16 plaintext) + where + loop las !acc (b, bs) = + let xed = CU.fixedXor las b + enc = AES.encryptEcbAES128 key xed + nacc = acc <> enc + in if BS.null bs + then nacc + else loop enc nacc (BS.splitAt 16 bs) + +ivlDecryptCbcAES128 + :: BS.ByteString -> BS.ByteString -> BS.ByteString +ivlDecryptCbcAES128 key ciphertext = + let (iv, cip) = BS.splitAt 16 (key <> ciphertext) + in loop iv mempty (BS.splitAt 16 cip) + where + loop !las !acc (b, bs) = + let dec = AES.decryptEcbAES128 key b + nacc = acc <> CU.fixedXor dec las + niv = b + in if BS.null bs + then nacc + else loop b nacc (BS.splitAt 16 bs) + +ivlVerifier :: BS.ByteString -> Either BS.ByteString Bool +ivlVerifier cip = loop pay where + pay = ivlDecryptCbcAES128 consistentKey cip + loop p = case BS.uncons p of + Nothing -> pure True + Just (b, bs) + | b > 127 -> Left pay + | otherwise -> loop bs