For cryptography, we often need large prime numbers. But it takes a lot of computation to check if a number is prime by trying to factor it.
Instead of trying to factor a number, we can use a Monte Carlo method to quickly check if a number is prime. The Miller-Rabin primality test is a probabilistic algorithm that can quickly check if a number is prime.
Algorithm
def miller_rabin(n: int, trials: int) -> bool: if n & 1 == 0: return False if n < 2: return False if n in (2, 3): return True s = 0 d = n - 1 while d & 1 == 0: s += 1 d >>= 1 for _ in range(trials): a = rng.randint(2, n - 2) x = pow(a, d, n) for _ in range(s): y = pow(x, 2, n) if y == 1 and x != 1 and x != n - 1: return False x = y if x != 1: return False return True
Let’s test this by computing the first few Mersenne primes.
for i in range(600): p = 2 ** i - 1 if miller_rabin(p, 10): print(f"2^{i} - 1")
2^2 - 1 2^3 - 1 2^5 - 1 2^7 - 1 2^13 - 1 2^17 - 1 2^19 - 1 2^31 - 1 2^61 - 1 2^89 - 1 2^107 - 1 2^127 - 1 2^521 - 1
Looks like it works! Feel free to compare with the list of Mersenne primes.
Now let’s try to generate some random primes.
Generating random primes
def n_bit_prime(n: int) -> int: low = 2 ** (n - 1) high = 2 ** n - 1 while True: p = rng.randint(low, high) p |= 1 # make sure it's odd if miller_rabin(p, 10): return p n_bit_prime(64)
18332724092051638669
To see if it’s really prime, we can try to factor it. This can be done easily with the factor
command on Linux.
run("factor 69420")
69420: 2 2 3 5 13 89
Now let’s try to factor our random prime.
run(f"factor {n_bit_prime(64)}")
13978716916633231963: 13978716916633231963
Looks like it’s really prime!
Generating safe primes
For some use cases, we might need “safe” primes. A safe prime is a prime number p
such that (p - 1) / 2
is also prime.
You can read more about safe primes on the Wikipedia page for Safe and Sophie Germain primes.
def safe_prime(n: int) -> int: while True: p = n_bit_prime(n) q = p * 2 + 1 if miller_rabin(q, 10): return q result = safe_prime(64) run(f"factor {result}")
19936227763520711003: 19936227763520711003
Since this is a safe prime, (p - 1) / 2
is supposed to be prime as well. Let’s check that.
run(f"factor {(result - 1) // 2}")
9968113881760355501: 9968113881760355501
It is indeed prime! We have successfully generated a safe prime.