Diffie-Hellman Key Exchange
The Diffie-Hellman key exchange is a cryptographic protocol that allows two parties to establish a shared secret over an insecure channel. This shared secret can then be used for symmetric encryption. It's important to note that Diffie-Hellman is a key exchange algorithm, not an encryption algorithm itself.
Algorithm
The Diffie-Hellman key exchange protocol works as follows:
- Public Parameters Agreement:
- Alice and Bob agree on a large prime number $p$ and a generator $g$ (also called a primitive root) of the multiplicative group of integers modulo $p$. Both $p$ and $g$ are public and can be transmitted over an insecure channel.
- Private Key Selection:
- Alice chooses a secret integer $a$ (her private key).
- Bob chooses a secret integer $b$ (his private key).
- Public Key Calculation:
- Alice computes $A = g^a \pmod{p}$ and sends $A$ to Bob.
- Bob computes $B = g^b \pmod{p}$ and sends $B$ to Alice.
- Shared Secret Calculation:
- Alice computes the shared secret $s = B^a \pmod{p}$.
- Bob computes the shared secret $s = A^b \pmod{p}$.
Both Alice and Bob arrive at the same shared secret $s$, which can be used as a key for symmetric encryption. The security of Diffie-Hellman relies on the difficulty of the discrete logarithm problem.
Mathematics
The Diffie-Hellman key exchange is based on the properties of modular arithmetic and the discrete logarithm problem.
- Modular Arithmetic: All calculations are performed modulo $p$. This means we are only concerned with the remainder after division by $p$. The notation $a \equiv b \pmod{m}$ means that $a$ and $b$ have the same remainder when divided by $m$.
- Prime Number ($p$): A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself.
- Generator ($g$): A generator (or primitive root) $g$ of the multiplicative group of integers modulo $p$ is an integer such that every number from $1$ to $p-1$ can be expressed as $g^k \pmod{p}$ for some integer $k$.
- Discrete Logarithm Problem: Given $g$, $p$, and $A$, it is computationally difficult to find the integer $a$ such that $g^a \equiv A \pmod{p}$, especially when $p$ is a large prime number. This difficulty is the basis of the security of Diffie-Hellman.
Why the Shared Secret is the Same:
Alice computes $s = B^a \pmod{p}$.
Bob computes $s = A^b \pmod{p}$.
We know that $A = g^a \pmod{p}$ and $B = g^b \pmod{p}$.
So, Alice's calculation is $s = (g^b)^a \pmod{p} = g^{ab} \pmod{p}$.
And Bob's calculation is $s = (g^a)^b \pmod{p} = g^{ab} \pmod{p}$.
Therefore, both Alice and Bob arrive at the same shared secret $s = g^{ab} \pmod{p}$.
Time and Memory Complexity
- Time Complexity:
- Generating a large prime number $p$ typically involves probabilistic primality tests (e.g., Miller-Rabin), which have a time complexity of roughly $O(k \log^3 p)$, where $k$ is the number of iterations for the primality test.
- Finding a generator $g$ can be more complex, but in practice, a suitable $g$ is often chosen or found through trial and error. The complexity of this step depends on the method used.
- The modular exponentiation operations ($g^a \pmod{p}$, $g^b \pmod{p}$, $B^a \pmod{p}$, $A^b \pmod{p}$) using the square-and-multiply algorithm take $O(\log a \cdot (\log p)^2)$ and $O(\log b \cdot (\log p)^2)$ time, respectively. If $a$ and $b$ are on the order of $p$, this is approximately $O((\log p)^3)$.
- Memory Complexity:
- The memory complexity is dominated by storing the prime number $p$, the generator $g$, and the intermediate values $A$, $B$, and the shared secret $s$. These values are typically on the order of $\log p$ bits. Therefore, the memory complexity is relatively low, $O(\log p)$.
Easy Problems
Diffie-Hellman is a key exchange protocol, not a general-purpose algorithm for solving typical competitive programming problems. However, problems might involve implementing the core modular exponentiation operation or understanding the basic concepts.
Problem 1: Modular Exponentiation (Core Operation)
Problem Statement: Given integers $base$, $exponent$, and $modulus$, compute $base^{exponent} \pmod{modulus}$.
Input Example:
base = 5
exponent = 11
modulus = 17
Output Example:
13 (5^11 mod 17 = 48828125 mod 17 = 13)
C++ Code:
#include <iostream>
long long power(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp /= 2;
}
return res;
}
int main() {
long long base, exp, mod;
std::cin >> base >> exp >> mod;
std::cout << power(base, exp, mod) << std::endl;
return 0;
}
Problem 2: Simulate Diffie-Hellman Exchange (Small Numbers)
Problem Statement: Simulate a simplified Diffie-Hellman key exchange between Alice and Bob using small numbers. Given a prime $p$, a generator $g$, Alice's private key $a$, and Bob's private key $b$, compute the shared secret.
Input Example:
p = 23
g = 5
a = 6
b = 15
Output Example:
Shared Secret: 2
C++ Code:
#include <iostream>
long long power(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp /= 2;
}
return res;
}
int main() {
long long p = 23;
long long g = 5;
long long a = 6;
long long b = 15;
long long A = power(g, a, p);
long long B = power(g, b, p);
long long alice_secret = power(B, a, p);
long long bob_secret = power(A, b, p);
std::cout << "Shared Secret: " << alice_secret << std::endl;
std::cout << "Shared Secret: " << bob_secret << std::endl;
return 0;
}
Intermediate Problems
Intermediate problems might involve working with larger numbers or combining Diffie-Hellman concepts with other algorithms.
Problem 3: Diffie-Hellman with Large Numbers (String Input)
Problem Statement: Simulate Diffie-Hellman key exchange where the prime $p$, generator $g$, and private keys $a$ and $b$ are very large numbers represented as strings. You'll need a function to perform modular exponentiation with large numbers.
Input Example:
p = "479001599"
g = "7"
a = "12345678901234567890"
b = "98765432109876543210"
Output Example:
Shared Secret: (Result of g^ab mod p, calculated using large number arithmetic)
C++ Code:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
// Helper function to multiply two large numbers represented as strings
std::string multiplyStrings(const std::string& a, const std::string& b) {
std::string result(a.length() + b.length(), '0');
for (int i = a.length() - 1; i >= 0; --i) {
for (int j = b.length() - 1; j >= 0; --j) {
int product = (a[i] - '0') * (b[j] - '0') + (result[i + j + 1] - '0');
result[i + j + 1] = (product % 10) + '0';
result[i + j] += (product / 10);
}
}
size_t firstNonZero = result.find_first_not_of('0');
if (firstNonZero == std::string::npos) {
return "0";
}
return result.substr(firstNonZero);
}
// Helper function to add two large numbers represented as strings
std::string addStrings(const std::string& a, const std::string& b) {
std::string result = "";
int carry = 0;
int i = a.length() - 1;
int j = b.length() - 1;
while (i >= 0 || j >= 0 || carry > 0) {
int digitA = (i >= 0) ? a[i--] - '0' : 0;
int digitB = (j >= 0) ? b[j--] - '0' : 0;
int sum = digitA + digitB + carry;
carry = sum / 10;
result += std::to_string(sum % 10);
}
std::reverse(result.begin(), result.end());
return result;
}
// Function to perform modular exponentiation with large numbers
std::string largeModularExponentiation(const std::string& base, const std::string& exp, const std::string& mod) {
std::string result = "1";
std::string currentBase = base;
std::string currentExp = exp;
while (currentExp != "0") {
if (currentExp.back() % 2 == 1) { // Check if last digit is odd
result = multiplyStrings(result, currentBase);
// Perform modulo operation (simplified for demonstration)
// In a real scenario, a proper large number modulo function is needed
result = result.substr(result.length() - mod.length()); // Simplified modulo
}
currentBase = multiplyStrings(currentBase, currentBase);
// Perform modulo operation (simplified)
currentBase = currentBase.substr(currentBase.length() - mod.length());
currentExp.pop_back();
if (currentExp.empty()) currentExp = "0";
}
return result;
}
int main() {
std::string p = "479001599";
std::string g = "7";
std::string a = "12345678901234567890";
std::string b = "98765432109876543210";
std::string A = largeModularExponentiation(g, a, p);
std::string B = largeModularExponentiation(g, b, p);
std::string alice_secret = largeModularExponentiation(B, a, p);
std::string bob_secret = largeModularExponentiation(A, b, p);
std::cout << "Alice Shared Secret: " << alice_secret << std::endl;
std::cout << "Bob Shared Secret: " << bob_secret << std::endl;
return 0;
}
Problem 4: Man-in-the-Middle Attack Simulation
Problem Statement: Simulate a simplified Man-in-the-Middle (MITM) attack on a Diffie-Hellman key exchange. Assume a malicious party (Mallory) intercepts the communication between Alice and Bob. Mallory exchanges keys with both Alice and Bob, leading them to believe they have a shared secret, while Mallory knows both "shared secrets".
Input Example:
p = 23
g = 5
alice_private = 6
bob_private = 15
mallory_private_a = 8
mallory_private_b = 9
Output Example:
Alice's Shared Secret with Mallory: (g^(a*m_a) mod p)
Bob's Shared Secret with Mallory: (g^(b*m_b) mod p)
C++ Code:
#include <iostream>
long long power(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp /= 2;
}
return res;
}
int main() {
long long p = 23;
long long g = 5;
long long alice_private = 6;
long long bob_private = 15;
long long mallory_private_a = 8;
long long mallory_private_b = 9;
// Alice and Mallory exchange
long long A = power(g, alice_private, p);
long long Mallory_A = power(g, mallory_private_a, p);
// Bob and Mallory exchange
long long B = power(g, bob_private, p);
long long Mallory_B = power(g, mallory_private_b, p);
// Alice calculates shared secret with (what she thinks is) Bob
long long alice_shared_secret = power(Mallory_B, alice_private, p);
// Bob calculates shared secret with (what he thinks is) Alice
long long bob_shared_secret = power(Mallory_A, bob_private, p);
// Mallory calculates the shared secrets
long long mallory_alice_secret = power(A, mallory_private_a, p);
long long mallory_bob_secret = power(B, mallory_private_b, p);
std::cout << "Alice's Shared Secret with Mallory: " << alice_shared_secret << std::endl;
std::cout << "Bob's Shared Secret with Mallory: " << bob_shared_secret << std::endl;
std::cout << "Mallory's Shared Secret with Alice: " << mallory_alice_secret << std::endl;
std::cout << "Mallory's Shared Secret with Bob: " << mallory_bob_secret << std::endl;
return 0;
}
Hard Problem
A truly hard problem directly solvable by the core Diffie-Hellman algorithm on competitive programming platforms is unlikely, as the algorithm's security relies on the difficulty of the discrete logarithm problem with very large numbers. However, a hard problem might involve more complex number theory and modular arithmetic concepts related to Diffie-Hellman, or finding vulnerabilities in a modified version of the protocol.
Problem 5: Breaking Simplified Diffie-Hellman with Small Prime
Problem Statement: You are given a simplified Diffie-Hellman key exchange where the prime number $p$ is relatively small. You are given the public values $g$, $A = g^a \pmod{p}$, and $B = g^b \pmod{p}$. Your task is to find the shared secret by finding either $a$ or $b$ using an exhaustive search (since $p$ is small).
Input Example:
p = 13
g = 2
A = 8
B = 5
Output Example:
Shared Secret: 4
C++ Code:
#include <iostream>
long long power(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp /= 2;
}
return res;
}
long long breakSimplifiedDiffieHellman(long long p, long long g, long long A, long long B) {
long long a = -1;
for (long long i = 0; i < p; ++i) {
if (power(g, i, p) == A) {
a = i;
break;
}
}
if (a != -1) {
return power(B, a, p);
}
long long b = -1;
for (long long i = 0; i < p; ++i) {
if (power(g, i, p) == B) {
b = i;
break;
}
}
if (b != -1) {
return power(A, b, p);
}
return -1; // Could not break
}
int main() {
long long p = 13;
long long g = 2;
long long A = 8;
long long B = 5;
long long shared_secret = breakSimplifiedDiffieHellman(p, g, A, B);
if (shared_secret != -1) {
std::cout << "Shared Secret: " << shared_secret << std::endl;
} else {
std::cout << "Could not break Diffie-Hellman." << std::endl;
}
return 0;
}