commit 1ac28233b57d87caa99986e645d5a6a31615ee48
parent 892e529ea8cd73b8c927125c0aae1ab7c842230d
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 6 Aug 2023 19:43:19 -0230
4.26, plus some misc other fixes.
Diffstat:
5 files changed, 110 insertions(+), 9 deletions(-)
diff --git a/docs/s2.md b/docs/s2.md
@@ -240,10 +240,10 @@ chaining) mode proceeds as follows:
xor operator +
let c_0 = IV
- c_1 = enc_k(c_0 + m_1)
- c_2 = enc_k(c_1 + m_2)
+ c_1 = enc_k(c_0 + p_1)
+ c_2 = enc_k(c_1 + p_2)
..
- c_l = enc_k(c_{l-1} + m_l)
+ c_l = enc_k(c_{l-1} + p_l)
in ciphertext c = (c_0, c_1, c_2, .., c_l)
@@ -289,9 +289,10 @@ return the following, given the malicious input:
cf39e6549b264c0eb44340b5f0e3ebdc %20a%20pound%20o
214abfcb615d8c63406ee84093538051 fbacon__________
-We can see that 0x3c and 0x37 in ciphertext block 2 (i.e. the third, counting
-the IV) need to be changed. Some calculation shows what we need to XOR them
-by:
+We can see that 0x3c and 0x37 in ciphertext block 2 (i.e. the third,
+counting the IV) need to be changed. The bytes are at index 37 and 43 in
+the raw ciphertext, respectively; some calculation shows what we need to
+XOR them by:
> showHex (ord '!' `B.xor` ord ';') mempty
"1a"
@@ -299,13 +300,28 @@ by:
and
> showHex (ord '!' `B.xor` ord '=') mempty
- "`c"
+ "1c"
so we replace 0x3c in `c_2` with `0x3c + 0x1a` and 0x37
with `0x37 + 0x1c` and pass the resulting munged ciphertext
through the ";admin=true;" substring checker found in
`Cryptopals.Block.Attacks.bfcChecker`:
+ > let cip = bfcEncrypter "AAAAA!admin!true"
+ > :{
+ ghci| let munge = loop 0 mempty where
+ ghci| loop j acc !bs = case BS.uncons bs of
+ ghci| Nothing -> acc
+ ghci| Just (b, etc)
+ ghci| | j == 37 -> let nex = BS.snoc acc (0x3c `B.xor` 0x1a)
+ ghci| in loop (succ j) nex etc
+ ghci| | j == 43 -> let nex = BS.snoc acc (0x37 `B.xor` 0x1c)
+ ghci| in loop (succ j) nex etc
+ ghci| | otherwise -> loop (succ j) (BS.snoc acc b) etc
+ ghci| :}
+ > let munged = munge cip
+ > bfcChecker cip
+ False
> bfcChecker munged
True
diff --git a/docs/s4.md b/docs/s4.md
@@ -19,8 +19,63 @@ Cryptopals.Stream.Attacks.rawrCtrAttack implements such an "attack":
pure $ ks `CU.fixedXor` cip
-Which gives the expected plaintext:
+which gives the expected plaintext:
> fmap (BS.take 33) rawrCtrAttack
"I'm back and I'm ringin' the bell"
+#### 4.26
+
+After making the necessary adjustments, we can generate an analogous
+ciphertext via:
+
+ > -- Cryptopals.Stream.Attacks.bfcEncrypter
+ > let cip = bfcEncrypter "AAAAA!admin!true"
+
+Encoded as hex, this one looks like:
+
+ 911197ace68288173aeea79803ba30ff comment1=cooking
+ 7a45f6350fefac4ff9b4f1277500e130 %20MCs;userdata=
+ 843d67e24f8af8305928b16352b943e9 AAAAA!admin!true
+ 39e33d0551e309a752c2bb658a8ba9ee ;comment=%20like
+ dda6c8de9da59acc48f4d83f0d63fb8b %20a%20pound%20o
+ 4969b8aac52e78b701c8da0f253efe59 fbacon__________
+
+Here we want to target the desired bytes themselves, not the bytes in
+a previous block as was done for the attack on CBC mode. Since we know
+the ciphertext and plaintext byte values, we can trivially recover the
+keystream at those bytes (i.e. at indices 37 and 43). Then it's just a
+matter of replacing the ciphertext bytes by the keystream bytes XOR'd by
+the desired plaintext:
+
+ > -- Cryptopals.Stream.Attacks.bfcEncrypter
+ > let cip = bfcEncrypter "AAAAA!admin!true"
+ > :{
+ ghci| let munge cip = loop 0 mempty cip where
+ ghci| p = fi (C.ord '!')
+ ghci| s = fi (C.ord ';')
+ ghci| e = fi (C.ord '=')
+ ghci| loop j acc !bs = case BS.uncons bs of
+ ghci| Nothing -> acc
+ ghci| Just (b, etc)
+ ghci| | j == 37 -> let c = BS.index cip j
+ ghci| k = c `B.xor` p
+ ghci| vil = k `B.xor` s
+ ghci| nex = BS.snoc acc vil
+ ghci| in loop (succ j) nex etc
+ ghci| | j == 43 -> let c = BS.index cip j
+ ghci| k = c `B.xor` p
+ ghci| vil = k `B.xor` e
+ ghci| nex = BS.snoc acc vil
+ ghci| in loop (succ j) nex etc
+ ghci| | otherwise -> loop (succ j) (BS.snoc acc b) etc
+ ghci| :}
+ > let munged = munge cip
+ > bfcChecker cip
+ False
+ > bfcChecker munged
+ True
+
+#### 4.27
+
+
diff --git a/lib/Cryptopals/Block/Attacks.hs b/lib/Cryptopals/Block/Attacks.hs
@@ -205,6 +205,7 @@ attackProxy oracle input = loop where
else pure $ BS.drop 16 target
-- bitflipping CBC
+
bfcEncrypter :: BS.ByteString -> BS.ByteString
bfcEncrypter input = AES.encryptCbcAES128 iv consistentKey padded where
iv = BS.replicate 16 0
@@ -359,3 +360,15 @@ rnBest s = loop (0, 1 / 0, s) 0 where
| sc < asc -> loop (b, sc, xo) (succ b)
| otherwise -> loop acc (succ b)
+-- 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
+
+
diff --git a/lib/Cryptopals/Stream/Attacks.hs b/lib/Cryptopals/Stream/Attacks.hs
@@ -133,3 +133,20 @@ rawrCtrAttack = do
pure $ ks `CU.fixedXor` cip
+
+-- bitflipping CTR
+
+bfcEncrypter :: BS.ByteString -> BS.ByteString
+bfcEncrypter input = AES.encryptCtrAES128 n k padded where
+ n = consistentNonce
+ k = consistentKey
+ filtered = BS.filter (`notElem` (BS.unpack ";=")) input
+ plaintext = "comment1=cooking%20MCs;userdata=" <> filtered <>
+ ";comment2=%20like%20a%20pound%20of%20bacon"
+ padded = CU.lpkcs7 plaintext
+
+bfcChecker :: BS.ByteString -> Bool
+bfcChecker ciphertext = target /= mempty where
+ plaintext = AES.decryptCtrAES128 consistentNonce consistentKey ciphertext
+ (_, target) = BS.breakSubstring ";admin=true;" plaintext
+
diff --git a/src/AES.hs b/src/AES.hs
@@ -117,7 +117,7 @@ aes Args {..} = do
SE.exitFailure
Right iv ->
- out $ AES.decryptCbcAES128 k v
+ out $ AES.decryptCbcAES128 k (iv <> v)
CTR -> case argsNonce of
Nothing -> do