commit 6f6e9b9d3b4bfee48d3874df10d7cc854d05bfc5
parent 5ba2137db9ed643b499a3ce25b56ca55398a1467
Author: Jack Sangdahl <jack@pngu.org>
Date: Tue, 28 Oct 2025 09:23:35 -0600
challenge 27: recover the key from cbc with iv=key
Diffstat:
1 file changed, 66 insertions(+), 0 deletions(-)
diff --git a/src/27_recover_key_cbc.c b/src/27_recover_key_cbc.c
@@ -0,0 +1,66 @@
+// Recover the key from CBC with IV=Key
+#include "aes.h"
+#include "random.h"
+
+/*
+ * take your code from the cbc exercise and modify so that it repurposes the key
+ * for cbc encryption as the iv. applications sometimes use the key as an iv on
+ * the auspices that both the sender and the receiver have to know the key
+ * already, and can save some space by using it as both a key and an iv. using
+ * the key as an iv is insecure; an attacker can modify ciphertext in flight can
+ * get the receiver to decrypt a value that will reveal the key. the cbc code
+ * from exercise 16 encrypts a url string. verify each byte of the plaintext for
+ * ascii compliacne (ie. look for high-ascii values). noncompliant messages
+ * should raise an exception or return an error that includes the decrypted
+ * plaintext (this happens all the time in real systems, for what it's worth).
+ * use your code to encrypt a message that is at least 3 blocks long:
+ * 'aes_cbc(p1, p2, p3) -> c1, c2, c3'. modify the message (you are now the
+ * attacker): 'c1, c2, c3 -> c1, 0, c1'. decrypt the message (you are now the
+ * receiver) and raise the appropriate error if high-ascii is found. as the
+ * attacker, recovering the plaintext from the error, extract the key: 'p1 ^
+ * p3'.
+ */
+
+unsigned char *key;
+unsigned char *iv;
+
+struct bytes validate(struct bytes input) {
+ struct bytes decrypted = aes_decrypt_cbc(input.data, input.len, key, 16, iv);
+ if (!decrypted.data || !decrypted.len)
+ return (struct bytes){NULL, 0};
+ return decrypted;
+}
+
+int main(void) {
+ srand(time(NULL));
+ key = iv = (unsigned char *)"ITSNOTCOMINGHOME";
+ while (true) {
+ unsigned char *rand_data = random_bytes(16);
+ unsigned char attack_blocks[48];
+ struct bytes payload = {attack_blocks, sizeof(attack_blocks)};
+ memcpy(attack_blocks, rand_data, AES_BLOCK_SIZE);
+ memset(attack_blocks + AES_BLOCK_SIZE, 0, AES_BLOCK_SIZE);
+ memcpy(attack_blocks + AES_BLOCK_SIZE * 2, rand_data, AES_BLOCK_SIZE);
+ struct bytes valid = validate(payload);
+ if (!valid.data) {
+ free(rand_data);
+ continue;
+ }
+ if (valid.len < AES_BLOCK_SIZE * 3) {
+ free(valid.data);
+ free(rand_data);
+ continue;
+ }
+ unsigned char recovered_key[17] = {0};
+ for (size_t i = 0; i < AES_BLOCK_SIZE; i++)
+ recovered_key[i] = valid.data[i] ^ valid.data[i + AES_BLOCK_SIZE * 2];
+ printf("Recovered key: ");
+ for (size_t i = 0; i < 16; i++)
+ printf("%c", recovered_key[i]);
+ printf("\n");
+ free(valid.data);
+ free(rand_data);
+ break;
+ }
+ return 0;
+}