Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
* [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py)
* [Morse Code](ciphers/morse_code.py)
* [Onepad Cipher](ciphers/onepad_cipher.py)
* [Paillier Cryptosystem](ciphers/paillier_cryptosystem.py)
* [Permutation Cipher](ciphers/permutation_cipher.py)
* [Playfair Cipher](ciphers/playfair_cipher.py)
* [Polybius](ciphers/polybius.py)
Expand Down
334 changes: 334 additions & 0 deletions ciphers/paillier_cryptosystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
"""
Paillier cryptosystem was introduced in 1999 by Pascal Paillier. It is
an asymmetric algorithm for public key cryptography.
----Public-Key Cryptosystems Based on Composite Degree Residuosity Classes
----https://link.springer.com/content/pdf/10.1007/3-540-48910-X_16.pdf

It is famous for its Homomorphic Properties present between cypher and
message space. i.e.
E(m1 + m2) = E(m1) * E(m2)
where:
E is encryption scheme
m1, m2 are messages
+ is addition operation in message space
* is multiplication operation in cypher space

Due to its Homomorphic properties it is highly studied in fields like:
Electronic voting
Electronic cash
Electronic auction
Zero Knowledge Proofs

Read more : https://en.wikipedia.org/wiki/Paillier_cryptosystem
"""


# Implementation details:
# Utils:
# primeGenerator() ++
# GCD() ++
# LCM() ++
# getInverse() ++
# L() ++
# Zn:
# __init__() ++
# sample() ++
# __contains__() ++
# Zn_star:
# __init__() ++
# sample() ++
# __contains__() ++
# Zn2_star:
# __init__() ++
# sample() ++
# __contains__() ++
# PaillierCryptosystem:
# __init__() ++
# keyGen() ++
# getPublicKey() ++
# getPrivateKey() ++
# encrypt() ++
# decrypt() ++
# homomorphicADD() ++
# homomorphicMUL() ++

import sympy
import random


class Utils:

Check failure on line 59 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (I001)

ciphers/paillier_cryptosystem.py:55:1: I001 Import block is un-sorted or un-formatted
def primeGenerator(bit_size):

Check failure on line 60 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

ciphers/paillier_cryptosystem.py:60:9: N802 Function name `primeGenerator` should be lowercase

Check failure on line 60 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N805)

ciphers/paillier_cryptosystem.py:60:24: N805 First argument of a method should be named `self`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function primeGenerator

Please provide return type hint for the function: primeGenerator. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: primeGenerator

Please provide type hint for the parameter: bit_size

"""
Uses sympy.randprime() for genearing a prime number
of a particular bit-size
"""
low = 2 ** (bit_size - 1)
high = (2**bit_size) - 1
return sympy.randprime(low, high)

def GCD(a, b):

Check failure on line 69 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

ciphers/paillier_cryptosystem.py:69:9: N802 Function name `GCD` should be lowercase

Check failure on line 69 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N805)

ciphers/paillier_cryptosystem.py:69:13: N805 First argument of a method should be named `self`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function GCD

Please provide return type hint for the function: GCD. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: a

Please provide type hint for the parameter: a

Please provide descriptive name for the parameter: b

Please provide type hint for the parameter: b

"""
Returns the Greatest Common Divisor of 2 numbers
aka Highest Common Factor
"""
while b > 0:
a, b = b, a % b
return a

def LCM(a, b):

Check failure on line 78 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

ciphers/paillier_cryptosystem.py:78:9: N802 Function name `LCM` should be lowercase

Check failure on line 78 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N805)

ciphers/paillier_cryptosystem.py:78:13: N805 First argument of a method should be named `self`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function LCM

Please provide return type hint for the function: LCM. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: a

Please provide type hint for the parameter: a

Please provide descriptive name for the parameter: b

Please provide type hint for the parameter: b

"""
Returns Least Common Multiple of 2 numbers
"""
return a * b // Utils.GCD(a, b)

def getInverse(a, n):

Check failure on line 84 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

ciphers/paillier_cryptosystem.py:84:9: N802 Function name `getInverse` should be lowercase

Check failure on line 84 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N805)

ciphers/paillier_cryptosystem.py:84:20: N805 First argument of a method should be named `self`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function getInverse

Please provide return type hint for the function: getInverse. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: getInverse

Please provide descriptive name for the parameter: a

Please provide type hint for the parameter: a

Please provide descriptive name for the parameter: n

Please provide type hint for the parameter: n

"""
Returns number 'b' such that
a*b == 1 (mod n)
"""
return pow(a, -1, n)

def L(x, n):

Check failure on line 91 in ciphers/paillier_cryptosystem.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

ciphers/paillier_cryptosystem.py:91:9: N802 Function name `L` should be lowercase

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the function: L

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function L

Please provide return type hint for the function: L. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: x

Please provide type hint for the parameter: x

Please provide descriptive name for the parameter: n

Please provide type hint for the parameter: n

"""
As defined in original paper by Pascal Paillier
L(x) = (x-1)/n
"""
mu = x - 1
assert mu % n == 0
mu = mu // n
return mu

class Zn:
"""
Model of set of Whole numbers less than n.
"""

def __init__(self, n):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: __init__. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: n

Please provide type hint for the parameter: n

# n :: n = p*q -> p,q are prime
self.n = n

def sample(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function sample

Please provide return type hint for the function: sample. If the function does not return a value, please provide the type hint as: def function() -> None:

return random.randint(0, self.n - 1)

def __contains__(self, item): # in operator
return (0 <= item) and (item < self.n)

class Zn_star:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class names should follow the CamelCase naming convention. Please update the following name accordingly: Zn_star

"""
Model of set of Natural numbers less than n
which are invertible in (mod n) system.
"""

def __init__(self, n):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: __init__. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: n

Please provide type hint for the parameter: n

self.n = n

def sample(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function sample

Please provide return type hint for the function: sample. If the function does not return a value, please provide the type hint as: def function() -> None:

while True:
temp = random.randint(1, self.n - 1)
if Utils.GCD(temp, self.n) == 1:
return temp

def __contains__(self, item): # in operator
if (1 <= item) and (item < self.n):
if Utils.GCD(item, self.n) == 1:
return True
return False

class Zn2_star:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class names should follow the CamelCase naming convention. Please update the following name accordingly: Zn2_star

"""
Model of set of Natural numbers less than n^2
which are invertible in (mod n^2) system.
"""

def __init__(self, n):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: __init__. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide descriptive name for the parameter: n

Please provide type hint for the parameter: n

# n :: n = p*q -> p,q are prime
self.n2 = n**2
self.p = p
self.q = q

def sample(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function sample

Please provide return type hint for the function: sample. If the function does not return a value, please provide the type hint as: def function() -> None:

while True:
temp = random.randint(1, self.n2 - 1)
if Utils.GCD(temp, self.n2) == 1:
return temp

def __contains__(self, item): # in operator
if (1 <= item) and (item < self.n2):
if Utils.GCD(item, self.n2) == 1:
return True
return False


class PaillierCryptosystem:
def __init__(self, bit_size=256):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide return type hint for the function: __init__. If the function does not return a value, please provide the type hint as: def function() -> None:

Please provide type hint for the parameter: bit_size

self.bitSize = bit_size

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: bitSize

self.p = None
self.q = None
self.n = None
self.n2 = None
self.g = None
self.l = None
self.lcm = None

self.privateKey = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: privateKey

self.publicKey = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: publicKey


def keyGen(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function keyGen

Please provide return type hint for the function: keyGen. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: keyGen

"""
This function is resoponsible for:
initialising p, q
setting n, n^2
setting lambda
setting mu ( lambda^-1 - TRAPDOOR)
setting PUBLIC and PRIVATE KEY
"""
p = Utils.primeGenerator(self.bitSize)
while True:
q = Utils.primeGenerator(self.bitSize)
if q != p:
break
self.p = p
self.q = q
self.n = p * q
self.n2 = self.n**2
self.g = self.n + 1
self.l = (p - 1) * (q - 1)
self.lcm = Utils.LCM((p - 1), (q - 1))

self.mu = Utils.getInverse(
self.lcm, self.n
) # mu = lambda^(-1) (mod n) -->> TRAPDOOR

self.publicKey = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: publicKey

"n": self.n, # Main PUBLIC KEY
"n2": self.n2, # Defining System - Computational Ease
"g": self.g, # Main PUBLIC KEY
}

self.privateKey = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: privateKey

"n": self.n, # Defining System - Computational Ease - Available from PUBLIC KEY
"n2": self.n2, # Defining System - Computational Ease - Available from PUBLIC KEY
"mu": self.mu, # Main PRIVATE KEY
"lambda": self.lcm, # Main PRIVATE KEY
}

def getPublicKey(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function getPublicKey

Please provide return type hint for the function: getPublicKey. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: getPublicKey

"""
Function to access PUBLIC KEY
"""
return self.publicKey

def getPrivateKey(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file ciphers/paillier_cryptosystem.py, please provide doctest for the function getPrivateKey

Please provide return type hint for the function: getPrivateKey. If the function does not return a value, please provide the type hint as: def function() -> None:

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: getPrivateKey

"""
Function to access PRIVATE KEY
"""
return self.privateKey

def encrypt(pubKey: dict, msg: int):
"""
Function to encrypt message using PRIVATE KEY.

It is a static method to ensure it has no access to
additional information other than provided PUBLIC KEY.

msg belongs to Zn (all Whole numbers less than n)
"""
n = pubKey["n"]
n2 = pubKey["n2"]
g = pubKey["g"]

zn = Utils.Zn(n)
zn_star = Utils.Zn_star(n)

# c = (g**x)(r**n) (mod n^2)
assert msg in zn
r = zn_star.sample()
assert r in zn_star

a = pow(g, msg, n2)
b = pow(r, n, n2)
return (a * b) % n2

def decrypt(prvKey: dict, cypher: int):
"""
Function to decrypt cypher using PRIVATE KEY.

It is a static method to ensure it has no access to
additional information other than provided PRIVATE KEY.

cypher belongs to Z(n^2) (all Naturals which are
invertible in (mod n^2) system)
"""
n = prvKey["n"]
n2 = prvKey["n2"]
mu = prvKey["mu"]
lmbda = prvKey["lambda"]

t = pow(cypher, lmbda, n2)
t = Utils.L(t, n) # L(cypher ^ lambda) == lambda * message (mod n)

msg = (
(t * mu) % n
) # (lambda * message) * mu == (lambda * message) * (lambda^-1) == msg (mod n)
return msg

def homomorphicADD(pubKey: dict, cypher1: int, cypher2: int):
"""
cypher1 * cypher2 -->> Encryption( msg1 + msg2 )
Both the messages are encrypted and do not
require decryption to perform Binary Operation (ADD)

E(m1) = (g^m1)*(r1^n) (mod n^2)
E(m2) = (g*m2)*(r2^n) (mod n^2)

E(m1+m2) = E(m1)*E(m2) = (g^(m1+m2))*((r1*r2)^n) (mod n^2)
"""
n2 = pubKey["n2"]
return (cypher1 * cypher2) % n2

def homomorphicMUL(pubKey: dict, cypher1: int, msg2: int):
"""
cypher1 ** msg2 -->> Encryption( msg1 * msg2 )
It requires 2nd msg to be decrypted to perfrom
Binary Operation (MUL).

E(m1) = (g^m1)*(r1^n) (mod n^2)
m2 in Zn

E(m1*m2) = E(m1)^m2 (mod n^2)
"""
n2 = pubKey["n2"]
return pow(cypher1, msg2, n2)


if __name__ == "__main__":
# base = 256
for i in range(5, 9):
base = 2**i
print(f"TESTING BITSIZE : {base}")
crypto = PaillierCryptosystem(base)
crypto.keyGen()

pub = crypto.getPublicKey()
msg = 233
print(f"Msg : {msg}")
c = PaillierCryptosystem.encrypt(pub, msg)
print(f"Cypher : {c}")

prv = crypto.getPrivateKey()
d = PaillierCryptosystem.decrypt(prv, c)
print(f"Decryption: {d}")
assert msg == d

msg2 = 232
c2 = PaillierCryptosystem.encrypt(pub, msg2)
c3 = PaillierCryptosystem.homomorphicADD(pub, c, c2)
d3 = PaillierCryptosystem.decrypt(prv, c3)
assert d3 == (msg + msg2)
print("Homomorphic Add SUCCESSFUL")

c4 = PaillierCryptosystem.homomorphicMUL(pub, c, msg2)
d4 = PaillierCryptosystem.decrypt(prv, c4)
assert d4 == (msg * msg2)
print("Homomorphic Mul SUCCESSFUL")
print()