Commutative Encryption with SHAKE-128/SHAKE-256 and PythonNIST chose Keccak as the standard for SHA-3. But, it’s all a bit confusing, as there are two main versions of this: Keccak and SHA-3. Many systems, such as Ethereum have adopted Keccak, while others go for SHA-3. The only real difference between them is a difference in the padding of data. An eXtendable-Output Function (XOF) produces a bit string that can be of any length. In fact, we can create an infinitely long bit string, if required. The main methods are SHAKE128, SHAKE256, BLAKE2XB and BLAKE2XS. With the SHA-3 hashing method, we have four different cryptographic hashing methods (SHA3-224, SHA3-256, SHA3-384, and SHA3-512) and two XOF functions (SHAKE128 and SHAKE256). With commutative encryption, we can decrypt with the keys in any order. Normally we would encrypt with Bob's key and then encrypt with Alice's key, and then we must decrypt with Alice's key and then Bob's. In commutative encryption, we can decrypt in any order. While our symmetric key block ciphers cannot be made commutative, we can use stream ciphers, as they perform an EX-OR function. In this example we will use the SHAKE128 or SHAKE256, and generate Bob and Alice's key. |
Theory
With commutative encryption, we can decrypt with the keys in any order. Normally we would encrypt with Bob's key and then encrypt with Alice's key, and then we must decrypt with Alice's key and then Bob's. In commutative encryption, we can decrypt in any order. With this, we can generate a key stream from Bob and ALice's password, and then EX-OR these with the plaintext and ciphertext. In the following, Bob encrypts, Alice encrypts, Bob decrypts, and the Alice decrypts, and use SHAKE-128 or SHAKE-256 to create an encryption key that will EX-OR with the message/cipher.
import libnum import sys import hashlib import numpy as np import binascii def xor_two_str(a,b): return ''.join([hex(ord(a[i%len(a)]) ^ ord(b[i%(len(b))]))[2:] for i in range(max(len(a), len(b)))]) M=b"hello" p1=b"bobpass" p2=b"alicepass" ty="SHAKE128" def exor(v1, v2): a = np.frombuffer(v1, dtype = np.uint8) b = np.frombuffer(v2, dtype = np.uint8) re=(a^b).tobytes() return(re) if (len(sys.argv)>1): M=str(sys.argv[1]).encode() if (len(sys.argv)>2): p1=str(sys.argv[2]).encode() if (len(sys.argv)>3): p2=str(sys.argv[3]).encode() if (len(sys.argv)>4): ty=str(sys.argv[4]) if (ty=="SHAKE128"): print("Using SHAKE128") key_bob = hashlib.shake_128() key_bob.update(p1) key_alice = hashlib.shake_128() key_alice.update(p2) else: print("Using SHAKE256") key_bob = hashlib.shake_256() key_bob.update(p1) key_alice = hashlib.shake_256() key_alice.update(p2) print("Message: ",M.decode()) print("\nBob key stream: \t",binascii.hexlify(key_bob.digest(len(M))).decode()) print("Alice key stream:\t",binascii.hexlify(key_alice.digest(len(M))).decode()) print("\nApplying Bob Key and then Alice") c1=exor(M,key_bob.digest(len(M))) c2=exor(c1,key_alice.digest(len(M))) print("Bob's key applied:\t",binascii.hexlify(c1).decode()) print("Alice's key applied:\t",binascii.hexlify(c2).decode()) print("\nRemoving Bob Key and then Alice") c3=exor(c2,key_bob.digest(len(M))) m=exor(c3,key_alice.digest(len(M))) print("After Bob's key applied:\t",binascii.hexlify(c3).decode()) print("After Alice's key applied:\t",m.decode())
And a sample run:
Using SHAKE128 Message: Hello Bob key stream: b66869ad27 Alice key stream: 78061b9d72 Applying Bob Key and then Alice Bob's key applied: fe0d05c148 Alice's key applied: 860b1e5c3a Removing Bob Key and then Alice After Bob's key applied: 306377f11d After Alice's key applied: Hello