Ejemplo n.º 1
0
def verifying(M: bytes, sign: tuple, publicKey: tuple = None):
    """
    Verify given signature of message M with corresponding public key's.
    """
    assert isinstance(M, (bytes, bytearray))

    from ..hashbased import hashFunctions as hashF

    if not publicKey:
        publicKey = it.extractKeyFromFile("public_key")

    p, g, h = publicKey
    size = it.getKeySize(publicKey)

    hm = hashF.sponge(M, size)

    hm = bm.bytes_to_int(bm.mult_to_bytes(hm))

    if not isinstance(sign, tuple):
        b64data = sign
        sign = it.getIntKey(b64data[1:], b64data[0])

    s1, s2 = sign

    if (0 < s1 < p) and (0 < s2 < p - 1):

        test1 = (ut.square_and_multiply(h, s1, p) *
                 ut.square_and_multiply(s1, s2, p)) % p
        test2 = ut.square_and_multiply(g, hm, p)

        if test1 == test2:
            return True
        return False

    raise ValueError
Ejemplo n.º 2
0
def inv(a: int, m: int, Verbose: bool = False):
    """
    Returns inverse of a mod m.

    If a and m are prime to each other, then there is an a^(-1) such that a^(-1) * a is congruent to 1 mod m.
    """

    if ut.euclid(a, m) != 1:
        if Verbose:
            print(
                f"gcd({a}, {m}) = {ut.euclid(a, m)} != 1 thus you cannot get an invert of {a}."
            )
        raise ValueError(
            f"gcd({a}, {m}) != 1 thus you cannot get an invert of {a}.")
        # a modular multiplicative inverse can be found directly

    elif a == 0:
        if Verbose:
            print(
                f"a = 0 and 0 cannot have multiplicative inverse ( 0 * nothing = 1 ) ."
            )
        raise ValueError("0 cannot have multiplicative inverse.")

    elif ut.millerRabin(m) and m % a != 0:
        # A simple consequence of Fermat's little theorem is that if p is prime and does not divide a
        # then a^−1 ≡ a^(p − 2) (mod p) is the multiplicative
        if Verbose:
            print(
                f"From Fermat's little theorem, because {m} is prime and does not divide {a} so: {a}^-1 = {a}^({m}-2) mod {m}"
            )
        u = ut.square_and_multiply(a, m - 2, m)

    elif ut.coprime(a, m) and m < (1 << 20):
        #From Euler's theorem, if a and n are coprime, then a^−1 ≡ a^(φ(n) − 1) (mod n).
        if Verbose:
            print(
                f"From Euler's theorem, because {a} and {m} are coprime -> {a}^-1 = {a}^(phi({m})-1) mod {m}"
            )

        u = ut.square_and_multiply(a, phi(m, 1, 1, Verbose) - 1, m)

    else:
        if Verbose:
            print(
                f"Modular inverse u solves the given equation: a.u+m.v=1.\n Let's use the euclid extended algorithm tho."
            )

        # Modular inverse u solves the given equation: a.u+m.v=1
        # n number of iterations
        _, u, _, _, _ = ut.euclid_ext(a, m, Verbose)

        if u < 0: u += m

    if Verbose:
        return u, f"u = {u} + {m}k, k in Z"
    else:
        return u
Ejemplo n.º 3
0
    def process_of_c1_c2(m):
        y = rd.randrange(1, p - 1)

        # shared secret -> g^xy
        c1 = ut.square_and_multiply(g, y, p)

        s = ut.square_and_multiply(h, y, p)
        c2 = (m * s) % p

        return (c1, c2)
Ejemplo n.º 4
0
def quadraticsResidues(n: int, sortedList=True):
    """
    For a given n a list of the quadratic residues modulo n may be obtained by simply squaring the numbers 0, 1, ..., n − 1.
    Because a2 ≡ (n − a)2 (mod n), the list of squares modulo n is symmetrical around n/2, and the list only needs to go that high. 
    
    N.B: 0 and 1 are always quadratics residues by definition.
    """

    if sortedList:
        return sorted(set([ut.square_and_multiply(e, 2, n) for e in range(n)]))
    else:
        return [ut.square_and_multiply(e, 2, n) for e in range(n)]
Ejemplo n.º 5
0
def chooseAndSend(accord: tuple,
                  secret=None,
                  n: int = 2048,
                  saving=False,
                  Verbose=False):
    """
    Choose a secret integer randomly and send a ciphered result to someone.

    n number of bits choosen for generating the integer randomly.
    """

    p, g = accord

    if not secret:
        secret_integer = rd.randrange(2, n)
        if Verbose:
            print(
                f"This is your secret integer, keep it safe: {secret_integer}")
    else:
        secret_integer = secret

    toSend = ut.square_and_multiply(g, secret_integer, p)

    if saving:
        toSend = it.writeKeytoFile(toSend, "dH_sendable")

    if Verbose:
        print("Here's what to send to the other one: ", end="")
        it.prGreen(toSend)

    return secret_integer
Ejemplo n.º 6
0
def verifying(M: bytes, sign: int, pK: tuple = None):
    """
    Verify given signature of message M with corresponding public key's.
    """

    assert isinstance(M, (bytes, bytearray))

    from ..hashbased import hashFunctions as hashF

    if not pK:
        pK = it.extractKeyFromFile("public_key")

    size = it.getKeySize(pK)

    hm = hashF.sponge(M, size)
    # base64 to int
    hm = bm.bytes_to_int(bm.mult_to_bytes(hm))

    # If the signature is in base64
    if not isinstance(sign, int):
        sign = it.getIntKey(sign)

    n, e = pK
    # raises the signature to the power of e (modulo n)
    # (as when encrypting a message)
    if sign > n:
        print("Signature > modulus")

    test = ut.square_and_multiply(sign, e, n)

    if test == (hm % n):
        return True

    return False
Ejemplo n.º 7
0
def signing(M: bytes, privateK: tuple = None, saving: bool = False, Verbose: bool = False):
    """
    Signing the message (M).
    You need to attach this signature to the message.
    """

    assert isinstance(M, bytes)

    from ..hashbased import hashFunctions as hashF

    if not privateK:
        privateK = it.extractKeyFromFile("private_key")

    size = it.getKeySize(privateK)  # Get key size

    if Verbose:
        print("Hashing in progress...")

    hm = hashF.sponge(M, size)
    # base64 to int
    hm = bm.bytes_to_int(bm.mult_to_bytes(hm))

    if Verbose:
        print(f"hm = {hm}")
        print("Hashing done.\n")

    # raises it to the power of d (modulo n)
    # same thing as decrypting
    n, d = privateK
    sign = ut.square_and_multiply(hm, d, n)

    if saving:
        sign = it.writeKeytoFile(sign, "RSA_signature")

    return sign
Ejemplo n.º 8
0
def findOtherGenerators(gen: int, mod: int, Verbose=False):
    """
    In a cyclic group of order n, with generator a, all subgroups are cyclic, generated (by definition) by some a^k, 
    and the order of a^k is equal to (n/gcd(n, k)).

    Therefore a^k mod n is another generator of the group if and only if k is coprime to n.
    """

    if gen == -1:
        return -1

    totient = phi(mod, 1, 1, Verbose)

    if Verbose:
        print(
            f"\nBased on the fact that {gen} is a generator of Z{mod}, the generators are {gen}^k with gcd(phi({mod}), k) = 1. "
        )
        print(
            f"Therefore the generators of Z{mod} are {gen}^k for k coprime with {totient}."
        )
        print(
            f"Or you can say: {gen}^k (with k elements from congruences classes of {totient}) are generators of Z{mod}."
        )

    return list(
        set([
            ut.square_and_multiply(gen, e, mod)
            for e in congruenceClasses(totient)
        ]))
Ejemplo n.º 9
0
def signing(M: bytes,
            privateK: tuple = None,
            saving: bool = False,
            Verbose: bool = False):
    """
    Signing a message M (bytes).
    """

    from ..hashbased import hashFunctions as hashF

    # y choosed randomly between 1 and p-2 with condition than y coprime to p-1
    if not privateK:
        privateK = it.extractKeyFromFile("private_key")

    p, g, x = privateK

    size = it.getKeySize(privateK)

    # M = bm.fileToBytes(M)
    # M = "Blablabla".encode()

    if Verbose:
        print("Hashing in progress...")

    hm = hashF.sponge(M, size)
    # #base64 to int
    hm = bm.bytes_to_int(bm.mult_to_bytes(hm))

    if Verbose:
        print("Hashing done.\n")

    p1 = p - 1

    k = rd.randrange(2, p - 2)

    while not ut.coprime(k, p1):
        k = rd.randrange(2, p - 2)

    if Verbose:
        print(f"Your secret integer is: {k}")

    s1 = ut.square_and_multiply(g, k, p)

    s2 = (multGroup.inv(k, p1) * (hm - x * s1)) % p1

    # In the unlikely event that s2 = 0 start again with a different random k.

    if s2 == 0:
        if Verbose:
            print("Unlikely, s2 is equal to 0. Restart signing...")
        signing(M, privateK, saving, Verbose)

    else:
        sign = (s1, s2)

        if saving:
            sign = it.writeKeytoFile(sign, "elG_signature")

        return sign
Ejemplo n.º 10
0
    def process(cipherT):
        c1, c2 = cipherT

        s1 = ut.square_and_multiply(c1, p - 1 - x, p)

        # This calculation produces the original message
        m = (c2 * s1) % p

        return bm.multitype_to_bytes(m)
Ejemplo n.º 11
0
        def multOrder1(a: int, t: tuple):

            q = None  # To avoid problem 'Possibly unbound"

            p, e = t
            m = ut.square_and_multiply(p, e)
            totient = phi(p, 1, e)  # phi(p^e)
            qs = [1]

            for elt, exp in ut.findPrimeFactors(totient, True).items():
                qs = [q * (elt**j) for j in range(1 + exp) for q in qs]

            qs.sort()

            for q in qs:
                if ut.square_and_multiply(a, q, m) == 1:
                    break

            return q
Ejemplo n.º 12
0
def compute(accord: tuple, L: list, saving=False):
    """
    accord <= common agreement
    L <= (secret_int, sended)
    """
    secret_int, sended = L

    shared_secret = ut.square_and_multiply(sended, secret_int, accord[0])

    if saving:
        return it.writeKeytoFile(shared_secret, "dH_shared_key")

    return shared_secret
Ejemplo n.º 13
0
def genSubGroup(n: int, m: int):
    """
    Returns subgroup of n in Zm* with multiplicative order q.
    E.g, genSubGroup(5, 10) = > [1, 5, 10] and q = 3.
    """

    res = []

    for e in range(m):
        t = ut.square_and_multiply(n, e, m)
        if t not in res:
            res.append(t)

    return sorted(res)
Ejemplo n.º 14
0
def reducedResidueSystem(n: int, g: int = None, Verbose: bool = False):
    """
    Return all elements of Zn* with generator g.
    """

    totient = phi(n, Verbose)
    if g == None:
        if Verbose:
            print("No generator given in input. Computing one now ..")
        g = primitiveRoot(n, totient, Verbose)
        if g == -1:
            return -1

    res = []
    # 1 , g , g^2 , ... , g ^ phi(n)-1
    for elt in range(totient):
        res.append(ut.square_and_multiply(g, elt, n))

    return sorted(set(res))
Ejemplo n.º 15
0
def generator(p: int, q: int, r: int = 2):
    """
    Find a generator g such as g order is q (Sophie Germain prime) with p = 2q + 1.
    That's a generator for Gq. Not Zp* and any other subgroup. Very important point.

    Shnorr group of r.
    https://en.wikipedia.org/wiki/Schnorr_group

    Avoid commons attacks.
    """
    assert p == r * q + 1

    while 1:

        # Choose a random quadratic residues, thus is multiplicative order will be q.
        # Without 0 and 1 to keep only legender symbol = 1 results

        e = rd.randrange(2, p)
        g = ut.square_and_multiply(e, r, p)

        # We must avoid g=2 because of Bleichenbacher's attack described
        # in "Generating ElGamal signatures without knowning the secret key",
        # 1996

        if g in (1, 2):
            continue

        #Discard g if intA divides p-1
        elif (p - 1) % g == 0:
            continue

        # g^{-1} must not divide p-1 because of Khadir's attack
        # described in "Conditions of the generator for forging ElGamal
        # signature", 2011

        elif (p - 1) % multGroup.inv(g, p) == 0:
            continue

        # Found a good candidate
        return g
Ejemplo n.º 16
0
def isGenerator(e: int,
                n: int,
                lagrangeWay: bool = True,
                printOther: bool = False,
                Verbose: bool = False):
    """
    Returns whether an element e is generator of Zn.
    A unit g ∈ Zn* is called a generator or primitive root of Zn* if for every a ∈ Zn* we have g^k = a for some integer k. 
    In other words, if we start with g, and keep multiplying by g eventually we see every element.
    By definition, g is a generator of Zn* if and only if that cycling does not occur before these n−1 iterations.

    lagrangeway is by default set to true.
    Base on the fact than as a consequence of Lagrange's theorem, ordn(e) always divides φ(n).
    If ordn(e) is actually equal to φ(n), and therefore as large as possible, then e is called a primitive root modulo n.
    """

    if lagrangeWay:

        totient = phi(n, 1, 1, Verbose)
        order = multiplicativeOrder(e, n, False, Verbose)

        if Verbose:
            print(
                f"\nIf phi({n}) is equal to mutliplicative order of {e} modulo {n} then it's a generator of {n}."
            )
            print(f"phi(n) = {totient} and order(e, n) = {order}.\n")

        if totient == order:

            if printOther:

                if Verbose:
                    print(f"There are {phi(phi(n))} generators in Z{n}.")
                    print(f"{e} is the a generator of Z{n}.\n")

                return True, findOtherGenerators(e, n, Verbose)

            return True
        else:
            return False

    else:
        if not ut.millerRabin(n):
            # Slowest way, keeped to verify results
            elements = []
            for i in range(1, n):
                t = ut.square_and_multiply(e, i, n)

                if t in elements:
                    continue
                else:
                    if Verbose:
                        print(f"\n{e}^{i} = {t} mod {n}")

                    #if cycling occurs
                    if t == 1 and t in elements:
                        return False
                    else:
                        elements.append(t)

            if printOther:
                if Verbose:
                    print(f"There are {phi(phi(n))} generators in Z{n}.")
                    print(
                        f"{e} is the a generator of Z{n} with elements: {elements}\n"
                    )

                return True, findOtherGenerators(e, n, Verbose)

            if Verbose:
                print(f"There are {phi(phi(n))} generators in Z{n}.")
                print(
                    f"{e} is the a generator of Z{n} with elements: {elements}\n"
                )

            return True

        elif e % n != 0:
            # We can test if some g not divisible by p is a generator of Zp*
            # by checking if g^k mod p != 1
            # with k = (p-1)/q for q each of prime factors of p-1

            L = ut.findPrimeFactors(n - 1)

            if Verbose:
                print(
                    f"{e} doesn't divide {n}, let's check if {e}^k mod {n} != 1."
                )
                print(
                    f"With k = ({n} -1 ) / q for q each of prime factors of {n}-1 ( = {L})."
                )

            for k in L:
                t = ut.square_and_multiply(e, k, n)
                if Verbose:
                    print(f"\n{e}^{k} = {t} mod {n}")
                if t == 1:
                    return False

            if printOther:
                if Verbose:
                    print(f"There are {phi(phi(n))} generators in Z{n}.")

                return True, findOtherGenerators(e, n, Verbose)

            return True
Ejemplo n.º 17
0
 def process(cipherT):
     un = ut.square_and_multiply(cipherT, d, n)
     return bm.mult_to_bytes(un)
Ejemplo n.º 18
0
def primitiveRoot(n: int, totient=None, Verbose: bool = False):
    """ 
    Returns primitive root modulo n.

    https://en.wikipedia.org/wiki/Fermat%27s_little_theorem
    """

    if totient == None:
        totient = phi(n, 1, 1, Verbose)

    if Verbose:
        print(f"Phi({n}) = {totient}\n")

    if n > 3 and ut.millerRabin(n):

        q = (n - 1) // 2

        if Verbose:
            print(f"\n{n} is a prime number.")

        # To deal with safe prime
        if ut.millerRabin(q):

            if Verbose:
                print(f"{n} is a Safe Prime! Because {q} prime too.")
                print("Choose random number between g [2, p-1]")

            import random as rd

            g = rd.randrange(2, n)

            if Verbose:
                print(
                    "To verify if g is a generator, you have to verify than g^2 and g^q are differents from 1."
                )

            while 1 in [
                    ut.square_and_multiply(g, 2, n),
                    ut.square_and_multiply(g, q, n)
            ]:
                g = rd.randrange(2, n)
            else:
                if Verbose:
                    print(f"{g} is a generator of Z{n}*.")
                return g

        else:
            if Verbose: print(f"Let's find all prime factors of {totient}:")
            s = ut.findPrimeFactors(totient)

            if Verbose:
                print("\n-----------------------------")
                print(f"{n} is prime and prime factors of totient are: {s} ")
                print(
                    f"Computing all g^(phi({n})/p_i) mod {n} with p_i prime factors."
                )
                print(
                    f"If all the calculated values are different from 1, then g is a primitive root."
                )
                print("-----------------------------")

            for e in range(2, totient + 1):

                # Iterate through all prime factors of phi.
                # and check if we found a power with value 1
                flag = False

                for it in s:
                    t = ut.square_and_multiply(e, totient // it, n)

                    if Verbose:
                        print(f" {e}^(phi({n})/{it}) mod {n} = {t} ")

                    if t == 1:
                        flag = True
                        break

                if not flag:
                    if Verbose:
                        print(f"Generator is: {e}")
                    return e
            # If no primitive root found
            return -1
    else:
        if Verbose:
            print(
                f"\nAccording to Euler's theorem, a is a primitive root mod {n} if and only if the multiplicative order of a is ϕ(n) = {totient}."
            )

        for e in range(1, n):
            o = multiplicativeOrder(e, n, Verbose)

            if Verbose:
                print(f"\nMultiplicative order({e}, {n}) = {o} \n")

            if o == totient:
                if Verbose:
                    print(f"Generator is: {e}")
                return e

        # If no primitive root found
        if Verbose:
            print(
                f"Since there is no number whose order is {totient}, there are no pritive roots modulo {n}."
            )
        return -1
Ejemplo n.º 19
0
def multiplicativeOrder(n: int,
                        p: int,
                        iterativeWay: bool = False,
                        Verbose: bool = False):
    """
    Returns the multiplicative order of n mod p.

    The minimum period of the sequence of powers of a is called the order of a.
    So a is a primitive root mod n if and only if the order of a is ϕ(n). 
    Order of n in p is the smallest number M or n^M = 1 mod p.

    Set iterative way to true if you want to use iterations.
    """
    if ut.euclid(n, p) != 1:
        raise ValueError(f"gcd({n},{p}) != 1")

    if not iterativeWay:
        if Verbose:
            print(
                f"\nBy the Chinese Remainder Theorem, it's enough to calculate the multiplicative order for each prime exponent p^k of {p}, "
            )
            print(
                f"and combine the results with the least common multiple operation."
            )

        from functools import reduce

        def multOrder1(a: int, t: tuple):

            q = None  # To avoid problem 'Possibly unbound"

            p, e = t
            m = ut.square_and_multiply(p, e)
            totient = phi(p, 1, e)  # phi(p^e)
            qs = [1]

            for elt, exp in ut.findPrimeFactors(totient, True).items():
                qs = [q * (elt**j) for j in range(1 + exp) for q in qs]

            qs.sort()

            for q in qs:
                if ut.square_and_multiply(a, q, m) == 1:
                    break

            return q

        pf = ut.findPrimeFactors(p, True)

        mofs = [multOrder1(n, (elt, exp)) for elt, exp in pf.items()]

        if Verbose:
            print(f"\nPrime factors of {p} has been generated : {pf}.")
            print(f"Now, we need to find least common multiple of {mofs}")

        # used to apply a particular function passed in its argument to all of the list elements mentioned in the sequence passed along.
        return reduce(ut.lcm, mofs, 1)

    else:
        k = 1

        if Verbose:
            print(f"m = {n**0} , iterations: {k}")

        for e in range(1, p):
            m = ut.square_and_multiply(n, e, p)

            if m == 1:
                break
            else:
                k += 1
                if Verbose:
                    print(f"m = {n}^{e} mod {p} = {m} , iterations: {k}")

        return k
Ejemplo n.º 20
0
 def process(m):
     return ut.square_and_multiply(m, e, n)
Ejemplo n.º 21
0
def phi(n: int, m: int = 1, k: int = 1, Verbose: bool = False):
    """
    Totient recurcive function for integer n.
    Can compute:
        phi(n*m) with phi(n, m).
        phi(p^k) with phi(p, 1, k)
    """

    if Verbose:
        print(f"\n----------- phi(n={n}, m={m}, k={k})--------------")
    ## Special cases ##
    twoN = n // 2

    if m != 1:

        d = ut.euclid(n, m)
        if Verbose:
            print(f"gcd({n}, {m}) = {d}")
            print(f"phi({n})*phi({m})*({d}/phi({d}))")
        return phi(n, 1, k, Verbose) * phi(m, 1, k, Verbose) * int(
            (d / phi(d, 1, k, Verbose)))

    elif k != 1:
        # phi(n^k) = n ^(k-1) * phi(n)

        mult = ut.square_and_multiply(n, k - 1)

        if Verbose:
            print(f"phi(n^k) = n ^(k-1) * phi(n) = {mult} * phi({n})")

        return mult * phi(n, 1, 1, Verbose)

    else:
        if n >= 0 and n <= 123:
            # Fastest results for common totients (sequence A000010 in the OEIS)
            totients = [
                0, 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18,
                8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24,
                12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32,
                24, 52, 18, 40, 24, 36, 28, 58, 16, 60, 30, 36, 32, 48, 20, 66,
                32, 44, 24, 70, 24, 72, 36, 40, 36, 60, 24, 78, 32, 54, 40, 82,
                24, 64, 42, 56, 40, 88, 24, 72, 44, 60, 46, 72, 32, 96, 42, 60,
                40, 100, 32, 102, 48, 48, 52, 106, 36, 108, 40, 72, 48, 112,
                36, 88, 56, 72, 58, 96, 32, 110, 60, 80, 60, 100, 36, 126, 64,
                84, 48, 130, 40, 108, 66, 72, 64, 136, 44, 138, 44, 138, 48,
                92, 70, 120
            ]

            r = totients[n]

            if Verbose:
                print(f"\nCommon totient phi({n}) = {r}")

            return r

        elif ut.millerRabin(n):
            # n is a prime number so phi(p) = (p-1)
            # p^(k-1) * phi(p) = p^(k-1) * (p-1)

            if Verbose:
                print(f"\n{n} is a prime number so phi(p) = (p-1)")

            return (n - 1)

        # If even:
        elif not twoN & 1:
            if Verbose:
                print(f"\nphi({n}) = phi(2*{twoN}) = 2 * phi({twoN}).")
            return 2 * phi(twoN, m, k, Verbose)

    ## Special cases ##
        else:

            if Verbose:
                print(f"\nLet's calculate phi({n}) with prime factors way.\n")

            tot = n
            if Verbose: print("Generating primes factors ...\n")
            for factor in ut.findPrimeFactors(n):
                if Verbose: print(f"Factor : {factor}")
                tot -= tot // factor

            return tot
Ejemplo n.º 22
0
def key_gen(n: int = 2048,
            primeFount=True,
            easyGenerator: bool = False,
            randomFunction=None,
            saving=False,
            Verbose=False) -> tuple:
    """
    ElGamal key generation.

    n: number of bits for safe prime generation.\n

    primeFount = prime number's fountain used, put tuple of primes into this variable.\n

    easyGenerator: generate appropriated safe prime number according to quadratic residues properties.\n

    randomFunction: prng choosen for random prime number generation (default = randbits from secrets module).

    saving: True if you want to save the private key to a file.
    """

    if not primeFount:

        if Verbose:
            print(f"Let's try to generate a safe prime number of {n} bits.")

        s = prng.safePrime(n, randomFunction, easyGenerator)

    else:
        primeFount = it.extract_safe_primes(n, False, Verbose)
        s = primeFount

    p, q = s  # safe_prime and Sophie Germain prime

    if Verbose:
        print(f"Let's find the generator for p: {p} , safe prime number.\n")

    # Generate an efficient description of a cyclic group G, of order q with generator g.
    if easyGenerator:
        # https://en.wikipedia.org/wiki/Quadratic_residue#Table_of_quadratic_residues
        if Verbose:
            print(
                f"Easy generator => gen = 12 (condition in prime generation) or (4, 9) (for every prime)"
            )

        gen = rd.choice([12, 4, 9])

    else:
        gen = generator(p, q)

    if Verbose: print(f"generator found : {gen}\n")

    # The description of the group is (g, q, p)
    # When p=2q+1, they are the exact same group as Gq.
    # The prime order subgroup has no subgroups being prime.
    # This means, by using Gq, you don't have to worry about an adversary testing if certain numbers are quadratic residues or not.

    # The message space MUST be restrained to this prime order subgroup.

    x = rd.randrange(1, p - 1)  # gcd(x, p) = 1

    h = ut.square_and_multiply(gen, x, p)

    # The private key consists of the values (G, x).
    private_key = (p, gen, x)

    if saving:
        it.writeKeytoFile(private_key, "private_key",
                          config.DIRECTORY_PROCESSING, ".kpk")

    if Verbose:
        print(f"\nYour private key has been generated Alice, keep it safe !")

    # The public key consists of the values (G, q, g, h).
    # But we saved only (p, g, h) cause q = (p-1)//2

    public_key = (p, gen, h)

    if saving:
        public_key = it.writeKeytoFile(public_key, "public_key",
                                       config.DIRECTORY_PROCESSING, ".kpk")

    if Verbose:
        print(f"\nThe public key has been generated too : ", end="")

        print(public_key)

    if not saving:
        return (public_key, private_key)