Fun crypto problem: Exploiting a vulnerable encryption scheme with Python 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. It's not a real encryption scheme or anything, I'm mainly interested in feedback regarding design and execution, the handling of bitwise operations, and the general layout and structure of the code. I tried implementing a class and a few things to make it more robust and useful, but I couldn't pull it off. Also, is it better organized as functions? Or doing the same thing in fewer lines, just defining and operating on variables, and declaring no functions at all.
The conversions to bit-strings, then ints and back to strings are unnecessary.  m and k are bytes, so the elements (e.g., m[0] or k[i]) are already integers in the range 0-255;  bitwise operators can be applied to the elements directly.

I think routines in the Crypto library return strings, so the encrypt() return value might need to be encoded before using it.

from Crypto.Cipher import DES

m = b'secret!!'
k = b'qwerty!!'
E = DES.new(k, DES.MODE_ECB)
C = E.encrypt(m)

C = C.encode()  # &lt;- needed if encrypt() returns a string (i.e., not bytes)

C_alpha = bytes(x^y for x,y in zip(C, m))

C_beta  = bytes(x^0xff for x in C)

C_omega = C_alpha + C_beta