Nothing in this appendix is exam material. It contains the arithmetic behind the algorithms described in the notes, for anyone who wants to see the steps rather than take them on faith. The numbers are small enough to check by hand or with a calculator.
RSA Key Generation with Small Numbers
Real RSA uses primes of several hundred digits. These are two primes small enough to follow.
Step 1. Choose two primes. Let \(p = 61\) and \(q = 53\). Both are prime, and in a real system they would be generated at random and tested for primality.
Step 2. Compute the modulus. \(n = p \times q = 61 \times 53 = 3233\). The modulus is public and appears in both keys.
Step 3. Compute Euler’s totient. \(\varphi(n) = (p-1)(q-1) = 60 \times 52 = 3120\). This counts the integers below \(n\) that share no factor with it. Its value depends on knowing \(p\) and \(q\), which is why factoring the modulus breaks the key.
Step 4. Choose the public exponent. Any \(e\) between 1 and \(\varphi(n)\) that shares no common factor with \(\varphi(n)\) will work. Take \(e = 17\). The factors of 3120 are 2, 3, 5, and 13, and 17 is prime and none of these, so the two share no factor.
Real implementations almost always use \(e = 65537\). It is prime, and its binary form is 1 followed by fifteen zeros and a 1, so raising a number to that power takes only sixteen squarings and one multiplication. Very small exponents such as 3 have been the source of attacks when padding was done poorly.
Step 5. Compute the private exponent. \(d\) is the multiplicative inverse of \(e\) modulo \(\varphi(n)\), meaning the value that satisfies \(17d \equiv 1 \pmod{3120}\). The extended Euclidean algorithm finds it: \(d = 2753\). Checking, \(17 \times 2753 = 46801\), and \(46801 = 15 \times 3120 + 1\), so the remainder is 1 as required.
The public key is \((e, n) = (17, 3233)\) and the private key is \((d, n) = (2753, 3233)\). The values \(p\), \(q\), and \(\varphi(n)\) must now be destroyed or kept as secret as \(d\), since any of them yields \(d\) immediately.
Encrypting and Decrypting
Take the message \(P = 123\), which must be less than \(n\).
Encryption raises the message to the public exponent:
\[C = 123^{17} \bmod 3233 = 855\]
Decryption raises the ciphertext to the private exponent:
\[P = 855^{2753} \bmod 3233 = 123\]
The exponents look enormous, and the computation is not. Modular exponentiation squares and reduces at each step, so raising a number to the 2753rd power modulo 3233 takes about a dozen multiplications on numbers smaller than 3233, not 2753 of them.
Signing and Verifying
Signing uses the same arithmetic with the exponents exchanged. Signing the value 123 gives:
\[S = 123^{2753} \bmod 3233 = 2746\]
Verification recovers it with the public exponent:
\[2746^{17} \bmod 3233 = 123\]
Anyone with the public key can perform the second step, and only the holder of \(d\) can perform the first. In a real signature the value being transformed is a digest, and it is padded and randomized first, for the reasons given in the notes. This bare version is the model, not the implementation.
A Diffie-Hellman Exchange with Small Numbers
Both parties agree publicly on a prime \(p = 23\) and a base \(g = 5\).
The first party picks a secret exponent \(a = 6\) and computes:
\[A = 5^{6} \bmod 23 = 15625 \bmod 23 = 8\]
The second party picks a secret exponent \(b = 15\) and computes:
\[B = 5^{15} \bmod 23 = 19\]
They exchange 8 and 19 in the clear. Each raises what arrived to their own secret exponent:
\[B^{a} \bmod p = 19^{6} \bmod 23 = 2\] \[A^{b} \bmod p = 8^{15} \bmod 23 = 2\]
Both hold the value 2, which neither transmitted and neither chose alone. An adversary who saw 5, 23, 8, and 19 would have to solve \(5^{x} \equiv 8 \pmod{23}\) to recover \(a\). With a modulus of 23 that takes a few seconds by trial. A real exchange uses a prime of 2048 bits or more, or an elliptic curve, and the search is infeasible.
Length Extension in Detail
The attack in the notes depends on the shape of a hash function built by chaining, and following the mechanics makes clear why it works and why HMAC stops it.
A hash function of this kind processes the message in fixed-size blocks, 64 bytes for SHA-256, carrying a running value from one block to the next. Before processing, it appends padding: a single 1 bit, enough 0 bits to fill out the block, and the length of the message in bits. The running value left after the last block is the digest.
Suppose a service computes authentication tags as \(H(k \mathbin{\Vert} m)\) and an adversary holds one message \(m\) and its tag, and knows the length of \(k\) but not its contents.
The adversary can compute the padding the function would have applied to \(k \mathbin{\Vert} m\), because padding depends only on the total length, which is known. Call that padding \(pad\). The adversary now sets the hash function’s running value to the tag they already hold, feeds in any blocks they like, and reads out the result. That result is exactly:
\[H(k \mathbin{\Vert} m \mathbin{\Vert} pad \mathbin{\Vert} m')\]
for the appended data \(m'\) of their choosing, and it is a correct tag for the message \(m \mathbin{\Vert} pad \mathbin{\Vert} m'\) under the secret they never learned. The padding bytes appear in the middle of the extended message, which sometimes makes the result unusable. It often does not, since many formats ignore stray bytes, and a parameter list that takes the last value of a repeated key will happily accept an appended override.
The attack requires nothing but the digest, the length of the secret, and a hash function of this construction. It does not require any weakness in the hash function itself. SHA-3 is not vulnerable, because it reveals only part of its internal value, so its digest is not a state that can be resumed.
Why HMAC Uses Two Pads
HMAC computes:
\[HMAC_k(m) = H\big((k \oplus opad) \mathbin{\Vert} H((k \oplus ipad) \mathbin{\Vert} m)\big)\]
The constant \(ipad\) is the byte 0x36 repeated to the block length, and \(opad\) is the byte 0x5c repeated the same way. A key shorter than the block length is padded with zeros to reach it. A longer key is hashed first, and the resulting digest is then padded with zeros the same way.
The nesting stops length extension. An adversary can still resume the calculation from the tag, because the tag is the output of an ordinary hash, but what that produces is the digest of \((k \oplus opad) \mathbin{\Vert} H(\ldots) \mathbin{\Vert} pad \mathbin{\Vert} m'\). No verifier ever computes a value of that shape. Forging a tag for a longer message would require extending the inner hash instead, and the inner digest is never revealed.
The two constants produce two different keys from one. Exclusive-or with 0x36 and with 0x5c yields two values that differ in four bits of every byte, so the inner and outer hashes run under keys that are related but not equal. Using the same key twice would weaken the security argument that ties HMAC’s strength to the strength of the underlying hash function.
HMAC was designed in 1996, when the available hash functions were MD5 and SHA-1, and the design deliberately avoided assuming that the underlying function was collision resistant. That judgment proved correct. HMAC-MD5 was never broken by the MD5 collision attacks that destroyed MD5 for signatures, although it is no longer used in new systems.