GCD/LCM Overflow — RSA Signatures Fail 0.1% of Keys
A 0.1% RSA signature failure caused by 64-bit overflow in GCD/LCM.
20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.
- Euclidean algorithm finds GCD by repeatedly replacing (a,b) with (b, a mod b) until b=0
- Extended GCD also finds coefficients x,y such that ax + by = GCD(a,b)
- LCM(a,b) = ab // GCD(a,b) — compute as (a//GCD)b to avoid overflow
- Time complexity: O(log min(a,b)) — at most 93 steps for 64-bit integers
- Production nightmare: overflow in LCM when using a*b before division in languages with fixed-width integers
- Biggest mistake: assuming GCD gives positive result for negative inputs — behavior varies across languages
The Euclidean algorithm for GCD is one of the oldest algorithms known — it appears in Euclid's Elements (300 BC). The insight: if you want to find the largest tile that fits perfectly in a 252×105 room, try 105×105 tiles first. You have a 42×105 leftover strip. Now find the largest tile for 105×42. Remainder is 21×42. Then 42×21 — perfect fit! GCD(252,105) = 21. The key insight: GCD(a,b) = GCD(b, a mod b).
The Euclidean algorithm is 2300 years old, appears in Euclid's Elements (Book VII, Proposition 2), and is still one of the fastest algorithms relative to input size that exists. For 64-bit numbers, it terminates in at most 93 steps. The RSA cryptographic system — which secures most HTTPS traffic — depends on extended Euclidean to compute private keys. Every time your browser establishes a TLS connection, a variant of this algorithm runs.
The extended Euclidean algorithm (Bézout's identity: ax + by = GCD(a,b)) is the computational backbone of modular inverses. Modular inverses are the backbone of modular arithmetic. Modular arithmetic is the backbone of RSA, Diffie-Hellman, elliptic curve cryptography, and the Chinese Remainder Theorem. The entire edifice of public-key cryptography rests on an algorithm Euclid described in 300 BC.
GCD, LCM, and the Euclidean Algorithm — The Math That Breaks RSA
The greatest common divisor (GCD) of two integers is the largest integer dividing both without remainder. The least common multiple (LCM) is the smallest positive integer divisible by both. The Euclidean algorithm computes GCD in O(log min(a,b)) steps by repeatedly replacing the larger number with the remainder of division: gcd(a,b) = gcd(b, a mod b). This is the oldest efficient algorithm still in daily use — and it's the foundation of RSA key generation.
Key property: for any two numbers, a × b = gcd(a,b) × lcm(a,b). This means you can compute LCM trivially once you have GCD. In practice, the Euclidean algorithm is the only practical way to compute GCD for large numbers — factoring is exponentially slower. The algorithm terminates because remainders shrink by at least half every two steps, guaranteeing logarithmic depth.
Use GCD whenever you need to reduce fractions, check coprimality (for RSA key generation), or compute modular inverses via the extended Euclidean algorithm. Use LCM when synchronizing periodic events or computing the least common denominator. In RSA, if p and q share a common factor (GCD > 1), the modulus n = p×q can be factored instantly — breaking the entire cryptosystem.
Euclidean Algorithm for GCD
Based on the identity: GCD(a, b) = GCD(b, a mod b). Repeat until b = 0 — then GCD = a.
abs().abs().abs() on inputs; beware of overflow in a % b if b == 0.math.gcd is implemented in C and is faster than pure Python.GCD Implementations Across Languages
The Euclidean algorithm is nearly identical in every language. The key differences are handling of negative numbers and integer overflow. Below we show the iterative implementation in six widely used languages: C++, Java, C#, JavaScript, C, and Python (for completeness). All follow the same logic: while b != 0, set (a, b) = (b, a % b).
BigInteger.gcd(). C# has BigInteger.GreatestCommonDivisor. JavaScript does not have a built-in GCD, but it's easy to write. Python's math.gcd is the fastest option.a % b is not a concern (the modulo operation always produces a result with magnitude less than |b|). However, in LCM calculations, multiplication after division is critical. In JavaScript, all numbers are 64-bit floats, so integers up to 2^53 are exact — beyond that, bitwise operations fail. Use BigInt for large numbers in JS.abs(). Always use abs() to ensure a correct, non-negative GCD.Visualizing the Euclidean Algorithm: Step by Step
Let's trace GCD(252, 105) step by step. The algorithm repeatedly replaces (a, b) with (b, a mod b) until b = 0. The final a is the GCD. We can see how quickly the numbers shrink — Lamé's theorem predicts at most 5×3 = 15 steps for 3-digit numbers, but we finish in just 4 steps.
Euclidean vs Binary GCD: Advantages and Disadvantages
Binary GCD (Stein's algorithm) is an alternative that replaces modulo with bit shifts and subtraction, which can be faster on processors with slow division. However, it is more complex and offers no advantage on modern hardware with fast modulo. Below is a comparison.
| Aspect | Euclidean (modulo) | Binary GCD (Stein) |
|---|---|---|
| Algorithmic steps | O(log min(a,b)) | O(log min(a,b)) |
| Worst-case steps | Fibonacci numbers (~93 for 64-bit) | Slightly higher constant |
| Core operation | a % b | >>, &, subtraction |
| Speed on modern CPUs | Very fast (modulo pipeline) | Comparable, no significant edge |
| Speed on slow division CPUs | Slower | Faster (no division) |
| Implementation complexity | Trivial | More complex (handle even cases, loops) |
| Handling negatives | Requires abs() on inputs | Requires abs() on inputs |
| Use cases | General purpose | Embedded systems, microcontrollers |
In most production systems, the standard Euclidean algorithm is preferred due to its simplicity and excellent performance. Binary GCD is reserved for environments where division is especially expensive.
Practice Problems on GCD and LCM
To solidify your understanding, work through these problems from competitive programming platforms. They cover GCD of arrays, coprime pairs, and Euler's totient, which directly build on the Euclidean algorithm.
- [GCD of an Array](https://cses.fi/problemset/task/1080) (CSES) – Compute GCD of a list of numbers. A straightforward application.
- [Coprime Pairs](https://www.codechef.com/problems/COPRIME) (CodeChef) – Count pairs in an array that are coprime. You'll need GCD(given pair) == 1.
- [Euler's Totient Function](https://cses.fi/problemset/task/1081) (CSES) – Compute φ(n) for each n in a list. Formula: φ(n) = n * ∏(1 - 1/p). Needs prime factors and GCD concepts.
- [LCM Sum](https://www.spoj.com/problems/LCMSUM/) (SPOJ) – Sum of LCMs from 1 to n. Requires efficient GCD and divisor enumeration.
- [GCD Extreme](https://www.spoj.com/problems/GCDEX/) (SPOJ) – Sum of GCD of all pairs for numbers up to n. Uses Euler's totient and prefix sums.
- [Modular Inverse](https://www.hackerrank.com/challenges/modular-inverse/problem) (HackerRank) – Compute x such that a*x ≡ 1 mod m. Direct extended GCD.
- [RSA Key Generation](https://www.cryptool.org/en/cto/highlights/rsa-step-by-step) (CrypTool) – Practice key generation involving GCD and modular inverse.
- [Binomial Coefficients and GCD](https://atcoder.jp/contests/abc156/tasks/abc156_d) (AtCoder) – Uses GCD for fraction simplification.
The Extended Euclidean Algorithm: Your Backdoor Into RSA
GCD alone is useful. The Extended Euclidean Algorithm is where you earn your paycheck. It doesn't just compute gcd(a, b). It finds integers x and y such that ax + by = gcd(a, b).
Why should you care? Because that x is the modular inverse of a modulo b when a and b are coprime. Modular inverses are the skeleton key for RSA key generation, the Chinese Remainder Theorem, and every asymmetric cryptosystem in production.
You run the standard Euclidean steps, but you also track the coefficients. When the recursion unwinds, you back-substitute to get x and y. The math is simple recursion. In practice, you'll hit stack limits on large numbers. Use the iterative version. Don't learn it in an interview; know it because your crypto library's bottleneck is here.
BigInteger.modInverse() uses the iterative binary extended Euclidean. Steel that pattern.LCM from GCD: Don't Multiply Then Divide
Everyone knows LCM(a, b) = (a * b) / gcd(a, b). That formula works. Until it doesn't.
In production, a and b are often large. 32-bit integers overflow when you multiply them. 64-bit too if you're working with crypto-sized numbers. The fix is trivial: divide before you multiply. Compute a / gcd(a, b) first, then multiply by b. The result stays in range as long as the LCM itself fits.
This isn't academic. I've seen a payment processing pipeline silently corrupt order totals because an intern used the naive formula. The fraction (a/gcd) gives you the reduced factor. Multiply last. Every library that matters does this: BigInteger, std::lcm in C++17, Python's math.lcm. Follow their lead.
For arrays, compute pairwise: lcm = gcdReduce(lcmSoFar, next). Don't compute product of entire array — that's asking for overflow.
Incorrect GCD in RSA Key Generation Caused Signature Failures
e * d ≡ 1 mod φ(n) was always satisfied because they used the extended Euclidean algorithm.lcm = (a b) // gcd using 64-bit integers. For some large primes used in RSA, the intermediate product a b overflowed before the division, producing a truncated LCM. This led to an incorrect modular inverse for the private exponent d.lcm = (a // gcd) * b, ensuring the division happens first. Applied the same fix to all LCM uses in the codebase. Added unit tests with large prime test vectors.- Always compute LCM as (a // gcd) * b to avoid overflow — a single multiplication after division is safe.
- Test cryptographic functions with known test vectors (NIST, RFC 8017).
- Never assume fixed-width integer math is safe for large intermediate values.
- In production, use language-provided arbitrary precision integers (Python's int, Java's BigInteger) or verify overflow limits.
abs(gcd) or ensure both numerator and denominator are non-negative.a and m are coprime: gcd(a, m) == 1. If not, no inverse exists. Also check that a is reduced modulo m before calling extended GCD.math.lcm as a reference.functools.reduce(lambda a,b: a//gcd(a,b)*b, numbers) and monitor intermediate size. If numbers are large, consider using arbitrary-precision integers or stopping early if LCM exceeds a threshold.python -c "import math; print(math.gcd(-15, 25))"python -c "def ngcd(a,b): return math.gcd(abs(a),abs(b)); print(ngcd(-15,25))"abs() on both arguments.Key takeaways
Common mistakes to avoid
4 patternsAssuming GCD handles negative numbers the same way across languages
abs() to both arguments before passing to GCD. For modular inverse, reduce a modulo m first, then apply x % m to the result.Overflow in LCM by multiplying before dividing
lcm = (a // gcd(a,b)) * b. In languages without arbitrary precision, use BigInteger or similar.Forgetting to check GCD == 1 before computing modular inverse
gcd(a, m) == 1 before proceeding. If not, the inverse does not exist. For RSA key generation, verify that the chosen e and φ(n) are coprime.Using recursive Euclidean algorithm without depth limit awareness
while b: a,b = b, a%b) instead of recursive. Python's recursion limit can be increased, but iterative is safer in production.Practice These on LeetCode
Interview Questions on This Topic
What is the time complexity of the Euclidean algorithm and why?
a by at least half. Lamé's theorem proves the number of iterations is at most 5 times the number of decimal digits in the smaller number. For 64-bit integers, the limit is ~93 steps.Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Lessons pulled from things that broke in production.
That's Number Theory. Mark it forged?
5 min read · try the examples if you haven't