'''
>>> B = 256
>>> k1 = MixCrypt(bits=B)
>>> k2 = MixCrypt(k=k1.k, bits=B)
>>> k3 = gen_multiple_key(k1, k2)
>>> N = 4
>>> clears = [random.StrongRandom().randint(1, B) for i in range(N)]
>>> cipher = [k3.encrypt(i) for i in clears]
>>> d = multiple_decrypt_shuffle(cipher, k1, k2)
>>> clears == d
False
>>> sorted(clears) == sorted(d)
True
>>> B = 256
>>> k1 = MixCrypt(bits=B)
>>> k1.setk(167,156,89,130) #doctest: +ELLIPSIS
<Crypto.PublicKey.ElGamal.ElGamalobj object at 0x...>
>>> k2 = MixCrypt(bits=B)
>>> k2.setk(167,156,53,161) #doctest: +ELLIPSIS
<Crypto.PublicKey.ElGamal.ElGamalobj object at 0x...>
>>> k3 = MixCrypt(bits=B)
>>> k3.k = ElGamal.construct((167, 156, 4717))
>>> k3.k.p, k3.k.g, k3.k.y
(167, 156, 4717)
>>> N = 4
>>> clears = [2,3,6,4]
>>> cipher = [(161, 109), (17, 101), (148, 163), (71, 37)]
>>> d = multiple_decrypt_shuffle(cipher, k2, k1)
>>> clears == d
False
>>> sorted(clears) == sorted(d)
True
'''
from pprint import pprint
from Crypto.PublicKey import ElGamal
from Crypto.Random import random
from Crypto import Random
from Crypto.Util.number import GCD
from Crypto.Cipher import AES
[docs]def rand(p):
"""
Generates a random integer k where GCD(k, p-1) == 1.
:param p: A prime number.
:type p: int
:return: A random integer k.
:rtype: int
"""
while True:
k = random.StrongRandom().randint(1, int(p) - 1)
if GCD(k, int(p) - 1) == 1:
break
return k
[docs]def gen_multiple_key(*crypts):
"""
Generates a combined MixCrypt object from multiple MixCrypt objects.
:param crypts: Variable number of MixCrypt objects.
:type crypts: tuple
:return: A new MixCrypt object with combined properties.
:rtype: MixCrypt
"""
k1 = crypts[0]
k = MixCrypt(k=k1.k, bits=k1.bits)
k.k.y = 1
for kx in crypts:
k.k.y *= kx.k.y
k.k.y = k.k.y % k.k.p
return k
[docs]def multiple_decrypt(c, *crypts):
"""
Sequentially decrypts a cipher using multiple MixCrypt objects.
:param c: The cipher to decrypt.
:type c: tuple
:param crypts: Variable number of MixCrypt objects for decryption.
:type crypts: tuple
:return: The decrypted message.
:rtype: int
"""
a, b = c
for k in crypts:
b = k.decrypt((a, b))
return b
[docs]def multiple_decrypt_shuffle(ciphers, *crypts):
"""
Sequentially decrypts and shuffles a list of ciphers using multiple MixCrypt objects.
:param ciphers: The list of ciphers to decrypt and shuffle.
:type ciphers: list
:param crypts: Variable number of MixCrypt objects for decryption and shuffling.
:type crypts: tuple
:return: The list of decrypted and shuffled messages.
:rtype: list
"""
b = ciphers
for i, k in enumerate(crypts):
last = i == len(crypts) - 1
b = k.shuffle_decrypt(b, last)
return b
[docs]def multiple_decrypt_shuffle2(ciphers, *crypts, pubkey=None):
"""
Decrypts and shuffles a list of ciphers using multiple MixCrypt objects.
:param ciphers: The list of ciphers to decrypt and shuffle.
:type ciphers: list
:param crypts: Variable number of MixCrypt objects for decryption and shuffling.
:type crypts: tuple
:param pubkey: Optional public key used in the shuffling process.
:type pubkey: tuple, optional
:return: The list of decrypted and shuffled messages.
:rtype: list
"""
'''
>>> B = 256
>>> k1 = MixCrypt(bits=B)
>>> k2 = MixCrypt(k=k1.k, bits=B)
>>> k3 = gen_multiple_key(k1, k2)
>>> pk = pubkey=(k3.k.p, k3.k.g, k3.k.y)
>>> N = 8
>>> clears = [random.StrongRandom().randint(1, B) for i in range(N)]
>>> cipher = [k3.encrypt(i) for i in clears]
>>> d = multiple_decrypt_shuffle2(cipher, k1, k2, pubkey=pk)
>>> clears == d
False
>>> sorted(clears) == sorted(d)
True
'''
b = ciphers.copy()
# shuffle
for k in crypts:
b = k.shuffle(b, pubkey)
# decrypt
for i, k in enumerate(crypts):
last = i == len(crypts) - 1
b = k.multiple_decrypt(b, last=last)
return b
[docs]class MixCrypt:
"""
A class for cryptographic operations based on the ElGamal encryption scheme.
:param k: Optional pre-existing key to initialize the MixCrypt.
:type k: ElGamal key, optional
:param bits: Bit size for the key generation.
:type bits: int
"""
[docs] def __init__(self, k=None, bits=256):
self.bits = bits
if k:
self.k = self.getk(k.p, k.g)
else:
self.k = self.genk()
[docs] def genk(self):
"""
Generates a new ElGamal key.
:return: The generated ElGamal key.
:rtype: ElGamal key
"""
self.k = ElGamal.generate(self.bits, Random.new().read)
return self.k
[docs] def getk(self, p, g):
"""
Constructs an ElGamal key from given parameters.
:param p: Prime number.
:type p: int
:param g: Generator number.
:type g: int
:return: The constructed ElGamal key.
:rtype: ElGamal key
"""
x = rand(p)
y = pow(g, x, p)
self.k = ElGamal.construct((p, g, y, x))
return self.k
[docs] def setk(self, p, g, y, x):
"""
Sets the ElGamal key with specified parameters.
:param p: Prime number.
:type p: int
:param g: Generator number.
:type g: int
:param y: Public key component.
:type y: int
:param x: Private key component.
:type x: int
:return: The set ElGamal key.
:rtype: ElGamal key
"""
self.k = ElGamal.construct((p, g, y, x))
return self.k
[docs] def encrypt(self, m, k=None):
"""
Encrypts a message using ElGamal encryption.
:param m: The message to encrypt.
:type m: int
:param k: Optional key to use for encryption. If not provided, use the instance's key.
:type k: ElGamal key, optional
:return: The encrypted message.
:rtype: tuple
"""
r = rand(self.k.p)
if not k:
k = self.k
a, b = k._encrypt(m, r)
return a, b
[docs] def decrypt(self, c):
"""
Decrypts an ElGamal encrypted message.
:param c: The encrypted message tuple.
:type c: tuple
:return: The decrypted message.
:rtype: int
"""
m = self.k._decrypt(c)
return m
[docs] def multiple_decrypt(self, msgs, last=True):
"""
Decrypts multiple messages, optionally leaving them in tuple form.
:param msgs: List of encrypted message tuples.
:type msgs: list
:param last: If True, return the clear message; if False, return a tuple with the original 'a' value and the decrypted 'b'.
:type last: bool
:return: List of decrypted messages or message tuples.
:rtype: list
"""
msgs2 = []
for a, b in msgs:
clear = self.decrypt((a, b))
if last:
msg = clear
else:
msg = (a, clear)
msgs2.append(msg)
return msgs2
[docs] def shuffle_decrypt(self, msgs, last=True):
"""
Shuffles and decrypts a list of encrypted messages.
:param msgs: The list of messages to shuffle and decrypt.
:type msgs: list
:param last: If True, only the decrypted message is returned; if False, a tuple is returned with the original 'a' value and the decrypted 'b'.
:type last: bool
:return: The shuffled and decrypted list of messages.
:rtype: list
"""
msgs2 = msgs.copy()
msgs3 = []
while msgs2:
n = random.StrongRandom().randint(0, len(msgs2) - 1)
a, b = msgs2.pop(n)
clear = self.decrypt((a, b))
if last:
msg = clear
else:
msg = (a, clear)
msgs3.append(msg)
return msgs3
[docs] def reencrypt(self, cipher, pubkey=None):
"""
Re-encrypts an encrypted message, optionally with a new public key.
:param cipher: The encrypted message to re-encrypt.
:type cipher: tuple
:param pubkey: Optional public key for re-encryption. If not provided, the instance's key is used.
:type pubkey: tuple, optional
:return: The re-encrypted message.
:rtype: tuple
"""
'''
>>> B = 256
>>> k = MixCrypt(bits=B)
>>> clears = [random.StrongRandom().randint(1, B) for i in range(5)]
>>> cipher = [k.encrypt(i) for i in clears]
>>> cipher2 = [k.reencrypt(i) for i in cipher]
>>> d = [k.decrypt(i) for i in cipher]
>>> d2 = [k.decrypt(i) for i in cipher2]
>>> clears == d == d2
True
>>> cipher != cipher2
True
'''
if pubkey:
p, g, y = pubkey
k = ElGamal.construct((p, g, y))
else:
k = self.k
a, b = map(int, cipher)
a1, b1 = map(int, self.encrypt(1, k=k))
p = int(k.p)
return ((a * a1) % p, (b * b1) % p)
[docs] def gen_perm(self, l):
"""
Generates a permutation of indices for a given length.
:param l: The length of the list to generate a permutation for.
:type l: int
:return: A list of permuted indices.
:rtype: list
"""
x = list(range(l))
for i in range(l):
d = random.StrongRandom().randint(0, i)
if i != d:
x[i] = x[d]
x[d] = i
return x
[docs] def shuffle(self, msgs, pubkey=None):
"""
Re-encrypts and shuffles a list of messages.
:param msgs: The list of messages to re-encrypt and shuffle.
:type msgs: list
:param pubkey: Optional public key for re-encryption. If not provided, the instance's key is used.
:type pubkey: tuple, optional
:return: The shuffled and re-encrypted list of messages.
:rtype: list
"""
msgs2 = msgs.copy()
perm = self.gen_perm(len(msgs))
for i, p in enumerate(perm):
m = msgs[p]
nm = self.reencrypt(m, pubkey)
msgs2[i] = nm
return msgs2
if __name__ == "__main__":
import doctest
doctest.testmod()