I came across a question asked here and here on the Crypto Stack Exchange site, and decided to analyze it as a kind of case study. To demonstrate the concept, I wrote a short python script depicting the proposed encryption scheme. It’s mostly list comprehensions or generator expressions and bitwise operations.

$ $ C=E_k(m)$ $ $ $ C_\alpha = C\oplus m$ $ $ $ C_\beta = \overline{C}$ $ $ $ C_\Omega = C_\alpha\ ^\frown\ C_\beta$ $

`#!/usr/bin/env python3 from Crypto.Cipher import DES m = b'secret!!' k = b'qwerty!!' E = DES.new(k, DES.MODE_ECB) C = E.encrypt(m) C_alpha = int('0b'+''.join([f"{x:08b}" for x in C]), 2) ^ \ int('0b'+''.join([f"{x:08b}" for x in m]), 2) C_beta = int('0b'+''.join([f"{x:08b}" for x in C]), 2) ^ \ int('0b' + '1' * len(''.join([f"{x:08b}" for x in C])), 2) C_omega = f"{C_alpha:064b}" + f"{C_beta:064b}" if __name__ == '__main__': print(C_omega) `

Then I ended up with this alternative version. If you want to try it out, save it as `bad_scheme.py`

so it can work properly with the next script:

`#!/usr/bin/env python3 from Crypto.Cipher import DES m = b'secret!!' k = b'qwerty!!' E = DES.new(k, DES.MODE_ECB) C = E.encrypt(m) def bitwise_xor(bits_a, bits_b): bits_out = ''.join([str(int(x)^int(y)) for x, y in zip(bits_a, bits_b)]) return(bits_out) def bitwise_complement(bits_in): bits_out = ''.join([str(~int(x)+2) for x in bits_in]) return(bits_out) def C_alpha(): bits_out = bitwise_xor(''.join([f"{x:08b}" for x in C]), ''.join([f"{x:08b}" for x in m])) return(bits_out) def C_beta(): bits_out = bitwise_complement(''.join([f"{x:08b}" for x in C])) return(bits_out) def C_omega(): bits_out = C_alpha() + C_beta() return(bits_out) if __name__ == '__main__': print(C_omega()) `

And here’s what is essentially a working exploit of the (hypothetical) proposed encryption scheme’s vulnerability; demonstrating the plaintext can be revealed without the key. It imports the final ciphertext and the bitwise functions from the first script, and works backwards, and complements $ C_\beta$ to get $ C$ (because $ C_\beta$ is essentially just $ \overline{C}$ ), then $ C \oplus C_\alpha$ to reveal $ m$ without requiring $ k$ . So:

$ $ \overline{C_\beta}=C$ $ $ $ C_\alpha \oplus C=m$ $

`#!/usr/bin/env python3 from bad_scheme import C_omega, bitwise_xor, bitwise_complement def C_alpha(): bits_out = C_omega()[:int(len(C_omega())/2)] return(bits_out) def C_beta: bits_out = C_omega()[int(len(C_omega())/2):] return(bits_out) def C(): bits_out = bitwise_complement(C_beta()) return(bits_out) def m(): bits_out = bitwise_xor(C_alpha(), C_beta()) return(bits_out) if __name__ == '__main__': print(''.join([chr(int(m()[i:i+8],2)) for i in range(0,len(m()),8)])) `

So, there it is. Just thought I’d put it out there and see what everyone thinks, which style is better, what’s good about it, what’s bad about it, and what I can do to improve it.