This project implements fundamental cryptographic primitives and number theory algorithms from scratch in C. It includes demonstrations of key concepts such as modular arithmetic, public-key cryptography, and stream ciphers. The system is fully modular, with each algorithm implemented in its own header (.h
) and source (.c
) files.
- Greatest Common Divisor
- Modular Inverse
- Chinese Remainder Theorem
- Modular Exponentiation
- Primality Testing
- Diffie Hellman Key Exchange
- Elliptic Curve Arithmetic
- Discrete Logarithm Solvers
- Rivest–Shamir–Adleman (RSA)
- Digital Signature Algorithm (DSA)
- RC4 Stream Cipher
This module provides functions to compute the greatest common divisor (GCD) and perform the Extended Euclidean Algorithm.
The Euclidean algorithm computes the GCD of two numbers by repeated division:
Example:
The Extended Euclidean Algorithm also finds integers
void scan_for_gcd(int *a, int *b);
Prompts the user to input two integers for GCD computation.
Parameters:
int *a
: Pointer to the first integer.int *b
: Pointer to the second integer.
void print_gcd(int64_t a, int64_t b);
Uses the Extended Euclidean Algorithm to compute and print
Parameters:
int64_t a
: First integer.int64_t b
: Second integer.
uint64_t gcd(uint64_t a, uint64_t b);
Recursively computes:
Parameters:
uint64_t a
: First non-negative integer.uint64_t b
: Second non-negative integer.
Returns:
-
uint64_t
: The greatest common divisor$\gcd(a, b)$ .
void extended_gcd(int64_t a, int64_t b, int64_t *gcd, int64_t *x, int64_t *y);
Finds integers
Parameters:
int64_t a
: First integer.int64_t b
: Second integer.int64_t *gcd
: Output pointer to store the computed GCD.int64_t *x
: Output pointer to store the coefficient ofa
.int64_t *y
: Output pointer to store the coefficient ofb
.
This module provides functions to compute the modular inverse of an integer, i.e., finding an integer
This is only possible when
The modular inverse of
That is,
To compute
When
If
void scan_for_inverse(int *a, int *n)
Prompts the user to input two integers
Parameters:
int a
: Pointer to store the base integerint n
: Pointer to store the modulus
void print_inverse(int a, int n)
Computes and prints the modular inverse of
Parameters:
int a
: The base integerint n
The modulus
int64_t *inverse(int64_t a, int64_t n)
Computes the modular inverse of a
modulo n
, i.e., finds an integer x
such that:
This is done using the Extended Euclidean Algorithm.
If no inverse exists (i.e., when NULL
.
Parameters:
int64_t a
: The number for which the inverse is to be found.int64_t n
: The modulus.
Returns:
int64_t *
: Pointer to the inverse value if it exists, orNULL
if no inverse exists.
This module provides a way to solve systems of linear congruences using the Chinese Remainder Theorem (CRT). It includes a data structure to represent each modular equation and functions to compute the unique solution when the moduli are pairwise coprime.
The CRT solves a system of congruences of the form:
If the moduli
To construct the solution:
- For each
$i$ , compute$N_i = \frac{N}{n_i}$ - Find the modular inverse
$y_i = N_i^{-1} \pmod {n_i}$ - Then the solution is:
This
typedef struct ModularEquation {
// Represents x ≡ b (mod n)
uint64_t b;
uint64_t n;
} ModularEquation;
ModularEquation *initModularEquation(uint64_t b, uint64_t n);
Initializes a ModularEquation
with the form
Parameters:
int b
: The remainderint n
: The modulus
Returns:
ModularEquation *
: Pointer to the createdModularEquation
instance.
void printModularEquation(ModularEquation *modularEquation);
Prints the modular equation in the format
Parameters:
modularEquation *
: Pointer to the modular equation to print
ModularEquation *crt(ModularEquation *modularEquations[], size_t size);
Solves a system of congruences using the Chinese Remainder Theorem.
Parameters:
-
ModularEquation *modularEquations[]
: An array of pointers toModularEquation
structs, each representing an equation of the form$x \equiv b_i \pmod n_i$ -
size_t size
: The number of equations in the system
Returns:
A pointer to a new ModularEquation
representing the combined solution
Assumes all moduli
$n_i$ are pairwise coprime. If this condition is not met, the result is undefined.
Suppose we want to solve the following system:
Here's how you can represent and solve this using the CRT module:
#include "crt.h"
ModularEquation *eq1 = initModularEquation(2, 5);
ModularEquation *eq2 = initModularEquation(3, 7);
ModularEquation *eq3 = initModularEquation(10, 11);
ModularEquation *equations[] = { eq1, eq2, eq3 };
size_t size = sizeof(equations) / sizeof(equations[0]);
ModularEquation *result = crt(equations, size);
printModularEquation(result);
free(equations[0]);
free(equations[1]);
free(equations[2]);
free(result);
Output: x ≡ 87 (mod 385)
This means
This module implements efficient exponentiation of the form
Modular exponentiation computes
-
$\text{Let } e_{10} \text{ in decimal be } (e_0 e_1 \ldots e_k)_2 \text{ in binary}$ $\text{Initialize } result = 1$ -
$\text{For each } e_i \text{ in } {e_0} \text{ to } e_{k}$ :- Square:
$result = ({result}^2) \mod p$ -
$\text{if } e_i = 1:$ - Multiply:
$result = (result \cdot x) \mod p$
- Multiply:
- Square:
$\text{return result}$
This allows efficient exponentiation even for very large
Let
Let
Let
op | result | value of result | |
---|---|---|---|
Square | |||
Multiply | |||
Square | |||
Multiply | |||
Square | |||
Square | |||
Multiply |
void print_for_exponent(int x, int e, int p);
Computes and prints the result of
Parameters:
int x
: The baseint e
: The exponentint p
: The modulus
uint64_t exponent(uint64_t x, uint64_t y, uint64_t mod);
Performs modular exponentiation using the square-and-multiply method.
Parameters:
uint64_t x
: The baseuint64_t y
: The exponentuint64_t mod
: The modulus
Returns:
-
uint64_t
: The computed value of$x^y \pmod p$ as a 64-bit unsigned integer.
This module implements probabilistic primality testing based on Fermat's little theorem and a simplified version of the Miller–Rabin test. It also includes Carmichael number detection and utilities for generating random prime numbers.
Primality testing relies on number-theoretic properties that primes satisfy and composites usually do not.
If
This gives a basic test: if
These are composite numbers like
For all
Example:
Try
This makes 561 appear prime in Fermat's test, even though it is not.
Probabilistic algorithm used to determine whether a number is likely to be prime. It builds upon Fermat’s Little Theorem, which states:
If
However, as we seen some composite numbers called Carmichael Numbers can pass this test for all bases
Let
To extract this form, we repeatedly divide
Now, pick a random base
The base
$x \equiv 1 \pmod{n}$ -
$x \equiv -1 \pmod{n}$ at some step during repeated squaring, i.e.,$$a^{2^j m} \equiv -1 \pmod{n} \quad \text{for some } 0 \le j < r$$
If neither condition holds, then
If
bool is_carmichael_number(int n)
Checks if the number is a Carmichael number by verifying whether
Parameters:
int n
: The number to check
Returns:
-
bool
: True if$n$ is a Carmichael number; otherwise false.
bool single_test(uint32_t m, uint32_t n);
Performs a single round of the Miller–Rabin test for a random base
Parameters:
-
int m
: The odd part of$n - 1$ , i.e.,$n - 1 = 2^r \cdot m$ -
int n
: The number being tested for primality
Returns:
bool
: True if
bool check_primility(uint32_t n);
Performs 20 iterations of the Miller–Rabin test to determine whether a number is probably prime.
Parameters:
int n
: The number to test
Returns:
-
bool
: True if$n$ is probably prime; otherwise false.
uint16_t rand_16_bits_number(void);
Generates a random 16-bit odd number with the first and last bit set to 1.
Returns:
uint16_t
: A random 16-bit odd number.
uint16_t generate_16bits_prime(int *attempts);
Generates a 16-bit prime number using probabilistic testing.
Parameters:
int *attempts
: Pointer to an integer that will store how many attempts it took to find a prime (can beNULL
)
Returns:
int uint16_t
: A 16-bit prime number.
uint32_t rand_32_bits_number(void);
Generates a random 32-bit odd number with the first and last bit set to 1.
Returns:
int uint32_t
: A random 32-bit odd number.
uint32_t generate_32bits_prime(int *attempts);
Generates a 32-bit prime number using probabilistic testing.
Parameters:
int *attempts
: A pointer to an integer that will store how many attempts it took to find a prime (can beNULL
)
Returns:
uint32_t
: A 32-bit prime number.
This module implements a basic version of the Diffie Hellman key exchange protocol, including key pair generation, encryption, and decryption.
Diffie–Hellman allows two parties to agree on a shared secret over an insecure channel.
Given:
- A large prime number
$p$ - A generator
$\alpha$ (primitive root modulo$p$ )
Each party picks a private key (say
$A = \alpha^a \pmod p$ $B = \alpha^b \pmod p$
The shared secret is then computed as:
- Party A:
$K = B^a \pmod p$ - Party B:
$K = A^b \pmod p$
Since
This shared secret can then be used to encrypt/decrypt messages.
uint64_t DH_generate_keyPair(int64_t p, int alpha);
Generates a public key using a random private key and the generator α modulo a large prime
Parameters:
int64_t p
: A large prime number used as the modulus.int alpha
: A primitive root modulo $p` (the generator).
Returns:
uint64_t
: The generated public key.
void DH_encrypt(int64_t p, int alpha, int64_t Kpub, int message, uint64_t *ephemeral_key, int64_t *cipher);
Encrypts a message using the recipient's public key and a randomly generated ephemeral key.
Parameters:
int64_t p
: The shared large prime modulus.int alpha
: The generator used in key exchange.int64_t Kpub
: The recipient's public key.int message
: The message to encrypt.uint64_t *ephemeral_key
: Output parameter for the generated ephemeral key.int64_t *cipher
: Output parameter for the encrypted message.
void DH_decrypt(int64_t p, int alpha, int64_t Keph, int64_t cipher, int64_t *message);
Decrypts a message using the received ephemeral key and the receiver's private key.
Parameters:
int64_t p
: The shared large prime modulus.int alpha
: The generator used in key exchange.int64_t Keph
: The received ephemeral key.int64_t cipher
: The encrypted message.int64_t *message
: Output parameter for the decrypted message.
This module implements elliptic curve arithmetic over a finite field. It supports point initialization, printing, and point addition using modular arithmetic.
An elliptic curve over a finite field
The set of points
To add two points
- If
$P = Q$ , use the tangent method:$$s = \frac{3x_1^2 + a}{2y_1} \mod p$$ - If
$P \ne Q$ , use the chord method:$$s = \frac{y_2 - y_1}{x_2 - x_1} \mod p$$
Then compute the resulting point
$x_3 = s^2 - x_1 - x_2 \pmod p$ $y_3 = s(x_1 - x_3) - y_1 \pmod p$
All divisions are performed using modular inverses.
Elliptic curves are widely used in cryptography, particularly in schemes like ECDH and ECDSA, because they offer strong security with smaller key sizes.
typedef struct EllipticCurve {
int a;
int b;
int m;
} EllipticCurve;
Represents an elliptic curve defined over a finite field modulo m
, using the equation:
Fields:
-
int a
: Coefficient of the linear term$a \cdot x$ -
int b
: Constant term$b$ -
int m
: Modulus for the finite field arithmetic
typedef struct EllipticCurvePoint {
int x;
int y;
} EllipticCurvePoint;
Represents a point
Fields:
int x
: x-coordinateint y
: y-coordinate
EllipticCurve *initEllipticCurve(int a, int b, int m);
Initializes a new elliptic curve defined by the equation:
Parameters:
int a
: coefficient of the linear term.int b
: constant term.int m
: modulus of the finite field.
Returns:
EllipticCurve *
: A pointer to a newly allocatedEllipticCurve
structure.
EllipticCurvePoint *initEllipticCurvePoint(int x, int y);
Creates and returns a point on the elliptic curve.
Parameters:
int x
: x-coordinate.int y
: y-coordinate.
Returns:
EllipticCurvePoint *
: A pointer to a newEllipticCurvePoint
structure.
EllipticCurvePoint *copyEllipticCurvePoint(EllipticCurvePoint *point);
Returns a copy of the given point.
Parameters:
EllipticCurvePoint *point
: pointer to an existingEllipticCurvePoint
.
Returns:
EllipticCurvePoint *
: New dynamically allocated point with the same coordinates.
EllipticCurvePoint *add_two_point_on_curve(EllipticCurvePoint *point1, EllipticCurvePoint *point2, EllipticCurve *curve);
Adds two points on an elliptic curve using modular arithmetic.
Parameters:
EllipticCurvePoint *point1
: first point.EllipticCurvePoint *point2
: second point.EllipticCurve *curve
: the elliptic curve over which the addition is performed.
Returns:
EllipticCurvePoint *
: Pointer to the resulting point, orNULL
if the denominator is zero or the result is the point at infinity.
bool isPointsEqual(EllipticCurvePoint *point1, EllipticCurvePoint *point2);
Checks if two points have the same coordinates.
Parameters:
EllipticCurvePoint *point1
: first point.EllipticCurvePoint *point2
: second point.
Returns:
bool
: true
if the points are equal, false
otherwise.
void printEllipticCurve(EllipticCurve *curve);
Prints the equation of the elliptic curve in readable form.
Parameters:
EllipticCurve *curve
: pointer to the elliptic curve.
void printEllipticCurvePoint(EllipticCurvePoint *point);
Prints the coordinates of a point.
Parameters:
EllipticCurvePoint *point
: pointer to the elliptic curve point.
This module provides algorithms to solve the Discrete Logarithm Problem (DLP) using Baby-Step Giant-Step (BSGS) and Pollard's Rho methods. It also includes a helper for solving linear congruences.
The DLP is:
Given integers
Let
Then any exponent
Which motivates the algorithm to:
- Compute all baby steps:
$g^j \pmod p \text{ for } j = 0 \text{ to } m-1$ - Compute all giant steps:
$y * (g^{-m})^i \pmod p \text{ for } i = 0 \text{ to } m-1$ . - Match values to recover
$x = im + j$ .
-
Time complexity:
$O(\sqrt{p})$ -
Space complexity:
$O(\sqrt{p})$
Pollard’s Rho is a probabilistic method for solving:
It generates a sequence of the form:
The sequence is partitioned into 3 regions based on (a, b)
differently:
- If
r % 3 == 0
:a += 1
- If
r % 3 == 1
:b += 1
- If
r % 3 == 2
:a *= 2
,b *= 2
When a cycle is detected (like Floyd’s algorithm), we solve:
-
Time complexity:
$O(\sqrt p)$ -
Space complexity:
$O(1)$
int64_t *linearCongruence(int64_t a, int64_t b, int64_t n, int64_t *size);
Solves the linear congruence:
Parameters:
int64_t a
: multiplier on xint64_t b
: target valueint64_t n
: modulusint64_t *size
: output parameter for number of solutions
Returns:
int64_t *
: array of all solutions (length given by*size
)
uint64_t *BSGS_solve(int g, int y, int p);
Solves the discrete logarithm using Baby-Step Giant-Step:
Parameters:
int g
: generatorint y
: target valueint p
: prime modulus
Returns:
uint64_t *
: pointer tox
if a solution exists, otherwiseNULL
int64_t *pollard_solve(int g, int y, int p);
Solves the discrete logarithm using Pollard’s Rho:
Parameters:
int g
: generatorint y
: target valueint p
: prime modulus
Returns:
int64_t *
: pointer tox
if a solution is found, otherwiseNULL
This module implements RSA key generation, encryption, and decryption using modular exponentiation, modular inverse, and the Chinese Remainder Theorem for optimization.
RSA is based on the difficulty of factoring large numbers. The steps are:
- Choose two distinct large primes:
$p$ and$q$ - Compute
$n = p \cdot q$ - Compute
$\varphi(n) = (p - 1)(q - 1)$ -
Euler’s Totient Function
$\varphi(n)$ counts the number of integers in${1, 2, ..., n - 1}$ that are coprime to$n$ . i.e.$\gcd(\varphi(i),n) = 1$ . - If
$p$ is prime, then$\varphi(p)$ is calculated to be$p - 1$
-
Euler’s Totient Function
- Choose public key
$e$ such that$\gcd(e, \varphi(n)) = 1$ - Compute private key
$d$ such that:$$d \cdot e \equiv 1 \pmod{\varphi(n)}$$
Encryption:
Given public key
Decryption:
Using private key
To improve efficiency, decryption is performed using the Chinese Remainder Theorem (CRT):
Then combine
void RSA_generate_keyPair(uint32_t *e, uint32_t *n);
Generates RSA key pair: public exponent
Parameters:
-
uint32_t *e
: output parameter for the public exponent -
uint32_t *n
: output parameter for the modulus$n = p \cdot q$
void RSA_encrypt(uint64_t n, uint64_t Kpub, uint64_t message, uint64_t *cipher);
Encrypts a message using RSA public key.
Parameters:
uint64_t n
: RSA modulusuint64_t Kpub
: RSA public exponent (e)uint64_t message
: plaintext message to encryptuint64_t *cipher
: output parameter to store the ciphertext
void RSA_decrypt(int64_t Kpub, uint64_t cipher, int64_t *message);
Decrypts the RSA ciphertext using the stored primes and Chinese Remainder Theorem (CRT).
Parameters:
int64_t Kpub
: public exponent (used to compute private key)uint64_t cipher
: ciphertext to decryptint64_t *message
: output parameter to store the decrypted message
This module implements the DSA key generation, signature, and verification process over a finite field using modular arithmetic.
DSA is used to produce and verify digital signatures. It is based on the hardness of the discrete logarithm problem.
To set up:
- Choose a safe prime
$p$ and a prime$q$ such that:$$p = 2q + 1$$ - Choose a generator
$\alpha$ of the subgroup of order$q$ in$\mathbb{Z}_p^*$ - Generate a private key
$d \in {1, ..., q - 1}$ - Compute the public key:
$$\beta = \alpha^d \mod p$$
Signature Generation for a message
- Select a random ephemeral key
$k \in {1, ..., q - 1}$ - Compute:
$r = (\alpha^k \mod p) \mod q$ - Compute:
$s = k^{-1}(\text{message} + d \cdot r) \mod q$
Signature Verification for
- Compute:
$w = s^{-1} \mod q$ - Compute:
$u_1 = \text{message} \cdot w \mod q$ $u_2 = r \cdot w \mod q$
- Compute:
$v = ((\alpha^{u_1} \cdot \beta^{u_2}) \mod p) \mod q$ - The signature is valid if:
$v = r$
void DSA_sign_and_verify(uint64_t p, uint64_t q, int alpha, int message);
Generates keypair, signs the message, and verifies the signature.
Parameters:
uint64_t p
: safe prime modulusuint64_t q
: prime such thatp = 2q + 1
int alpha
: generator of subgroup of orderq
int message
: message to sign
uint64_t DSA_generate_keyPair(uint64_t p, uint64_t q, int alpha);
Generates a random private key and returns the corresponding public key.
Parameters:
uint64_t p
: safe primeuint64_t q
: prime divisor ofp-1
int alpha
: generator
Returns:
uint64_t
: public key (beta = alpha^d mod p)
void DSA_sign(uint64_t p, uint64_t q, int alpha, int message, uint64_t *r, uint64_t *s);
Signs a message using the DSA private key.
Parameters:
uint64_t p
: prime modulusuint64_t q
: prime divisorint alpha
: generatorint message
: message to signuint64_t *r
: output parameter for signature part ruint64_t *s
: output parameter for signature part s
bool DSA_verify(uint64_t p, uint64_t q, int alpha, uint64_t public_key, uint64_t r, uint64_t s, int message);
Verifies a DSA signature.
Parameters:
-
uint64_t p
: prime modulus -
uint64_t q
: prime divisor -
int alpha
: generator -
uint64_t public_key
: public key$\beta$ -
uint64_t r
: signature component -
uint64_t s
: signature component -
int message
: original message
Returns:
bool
:true
if signature is valid, otherwisefalse
This module implements the RC4 stream cipher, a symmetric key encryption algorithm known for its simplicity and speed. It includes key scheduling, keystream generation, encryption, and decryption.
RC4 generates a keystream by initializing a permutation of the numbers
Encryption and decryption are symmetric and use the XOR operation:
-
Encryption:
$C_i = P_i \oplus K_i$ -
Decryption:
$P_i = C_i \oplus K_i$
Where:
-
$C_i$ is the ciphertext byte -
$P_i$ is the plaintext byte -
$K_i$ is the keystream byte -
$\oplus$ denotes bitwise XOR
void print_key_stream(uint8_t *keystream);
Prints the 256-byte keystream as hexadecimal values.
Parameters:
uint8_t *keystream
: pointer to the keystream array.
uint8_t *RC4_init(char *key);
Initializes the RC4 cipher and generates a 256-byte keystream using the provided key.
Parameters:
char *key
: null-terminated string used as the key.
Returns:
uint8_t *
: pointer to a dynamically allocated keystream array.
uint8_t *RC4_encrypt(char *string, uint8_t *keyStream);
Encrypts a string using the RC4 keystream.
Parameters:
char *string
: null-terminated plaintext string.uint8_t *keyStream
: the 256-byte keystream.
Returns:
uint8_t *
: pointer to the dynamically allocated ciphertext.
char *RC4_decrypt(char *cipher, uint8_t *keyStream);
Decrypts a ciphertext using the RC4 keystream. This is functionally the same as encryption.
Parameters:
char *cipher
: ciphertext data (as char pointer).uint8_t *keyStream
: the 256-byte keystream.
Returns:
char *
: pointer to the decrypted plaintext.