Symmetric Cryptography
Structure of AES
KeyExpansion
In this phases, the 11 separate 128 bit roundkey are derived from our input key (with AES-128). Every one to be used in each AddRoundKey step.
AddRoundKey
The bytes of the first roundkey are XOR'd with the bytes of the initial state.
Round
Confusion
Keep others from knowing or understanding what happened.
SubBytes- each byte of the state is substituted for a different byte according to a lookup table (S-box).
According to Shannon's Information Theory, for the secure cipher, just with given ciphertext, we have no way to learn anything about the key. Otherwise, we can express the relationship between ciphertext and key. For example, the Caesar cipher with
ciphertext = plaintext + keyrelationship.For the linear transformations can be solved using techniques like Gaussian elimination. Even low-degree polynomials, e.g. an equation like \(x^4 + 51x^3 + x\) can be solved efficiently by algebraic methods. However, the high-degree of a polynomials can only be approximated by a larger and larger amount of linear functions.
Diffusion
Cause change of one bit in the plaintext to lead to a change as much as possible the bits of the ciphertext
ShiftRows- the last three rows of the state matrix are transposed -> shifted over a column or two or three.
MixColumns- matrix multiplication is performed on the columns of the state, combining the four bytes in each column. This is skipped in the final round.
AddRoundKey- the bytes of the current roundkey are XOR'd with the bytes of the state.
Encrypt Flow Chart
Decrypt Flow Chart
Padding
- If the length of plaintext can not devisible by blocksize(ex: 16 for AES128), padding with
plaintext + bytes(block_size - (length % block_size)) * (block_size - (length % block_size)) example: plaintext + '\x03\x03\x03'
- If the length of plaintext can divisible by blocksize, padding with:
bytes(block_size) * (block_size) example: '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10' for AES128
Modes of Operation
ECB
Property: Blocks encrypt/decrypt independently.
Weakness or Features?
Same input will produce same ouput.
Copy & Paste Attack
Prepend Oracle Attack
import os from Crypto.Cipher import AES # pycryptodome from secret import flag # server side flag key = os.urandom(16) aes = AES.new(key, AES.MODE_ECB) # PKCS #7 def pad(data): return data + bytes([-len(data) % 16] * (-len(data) % 16)) if __name__ == '__main__': print('=== Super Signer ===') while True: data = input('input you message (hex)> ') signed = aes.encrypt(pad(bytes.fromhex(data) + flag)) print(f'Signed: {signed.hex()}')
CBC
plaintext1 = IV ^ decrypt(CIPHERBLOCK 1)
Bit Flipping Attack
Prerequires: Known Block 1 and controlled IV.
Target: Forge the plaintext of the block2.
import os from Crypto.Cipher import AES from secret import flag key, iv = os.urandom(16), os.urandom(16) def encrypt(data): aes = AES.new(key, AES.MODE_CBC, iv) return aes.encrypt(data) def decrypt(data): aes = AES.new(key, AES.MODE_CBC, iv) return aes.decrypt(data) if __name__ == '__main__': chal = os.urandom(32) print(f'original message: {chal.hex()}') print(f'encrypted: {encrypt(chal).hex()}') shot = input('one shot> ') result = decrypt(bytes.fromhex(shot)) if b'this_is_a_secret' in result: print(flag) else: print('failed...')
plaintext2 = ciphertext1 ^ decrypt(ciphertext2)
new_iv = plaintext2 ^ ciphertext1 ^ fake_message
= decrypt(ciphertext2) ^ fake_message
Now, this new_iv will be used as 'ciphertext1' to send back to server. (new_iv + ciphertext2).
Padding Oracle Attack
Prerequires: User control the input IV.
Target: leak plaintext.
import os from Crypto.Cipher import AES from secret import flag key = os.urandom(16) def encrypt(iv, data): aes = AES.new(key, AES.MODE_CBC, iv) return aes.encrypt(data) def decrypt(iv, data): aes = AES.new(key, AES.MODE_CBC, iv) return aes.decrypt(data) def pad(data): return data + bytes([-len(data) % 16] * (-len(data) % 16)) def unpad(data): padlen = int(data[-1]) if not (bytes(data[-1]) * padlen == data[-padlen:]): raise ValueError else: return data[:-padlen] if __name__ == '__main__': cur_iv = os.urandom(16) print(f'encrypted flag: {cur_iv.hex() + encrypt(cur_iv, pad(flag)).hex()}') while True: msg = input('your message(hex)> ') msg = bytes.fromhex(msg) try: unpad(decrypt(msg[:16], msg[16:])) print('yes.') except ValueError: print('no.')
Solve:
from pwn import * # IMPORTANT: plaintext_i = iv(c_(i-1)) ^ dec(c_i) io = remote('localhost', 1337) io.recvuntil(b'flag: ') enc_flag = bytes.fromhex(io.recvline(drop=True)) iv, enc_flag = enc_flag[:16], enc_flag[16:] info(f'iv: {iv.hex()}, enc_flag: {enc_flag.hex()}') flag = b'' def padding_oracle(msg): io.sendine(msg.hex().encode()) return b'yes' in io.recv() for i in range(16): for j in range(256): cur_iv = iv[:16-i-1] + bytes([j]) + xor(xor(flag, iv[16-i:]), bytes([i+1]*(i))) if padding_oracle(cur_iv + enc_flag): flag = xor(xor(bytes([j]), iv[16-i-1]), bytes([i+1])) + flag break info(f'current flag: {flag}')
OFB (Stream Cipher)
The advantage of stream ciphers is that they can work plaintext of arbitrary length, with no padding required.
CTR (Stream Cipher)