When you want to recover N given some (plaintext, ciphertext) pairings
Scenario
Consider the case that that you know a set of (plaintext, ciphertext) pairings - this may be that you are provided them, or that you have access to some functionality that returns ciphertexts for provided plaintexts. If you do not know the modulus, but know the exponent used (note: this may be prone to a brute-force regardless), then given these pairings you can recover the modulus used.
What we know
Let the following be known:
plaintext && ciphertext pairings:
public exponent e (e.g. e = 65537 = 0x10001)
Process
The idea behind this attack is effectively finding common factors between pairings. Recall that, under general RSA encryption, we have:
and recall what modular arithmetic tells us about the relation between these terms, namely that:
This, rearranged, tells us that
What this means for our known pairings is that, given we know and , we can form the relationship:
Thus we can calculate for the value , though don't know either value individually - we want to somehow derive .
Observe that any two pairings will equate to such a value, both with as a factor. We can take the gcd of these two values, and it is probable that the resulting value will be our value, such that:
However, this is only true for the case that
i.e., both and are coprime. In the case that they are not, i.e. , we have that
In such a case, we don't have sufficient information to completely recover the modulus, and require more plaintext-ciphertext pairs to be successful. In general, the more pairings you have, the more confident you can be the value you calculate is . More specifically:
Thus:
Practical Notes
In reality, you're likely to only need two or three (plaintext, ciphertext) pairings (in the context of ctf challenges and exercises), and as such computations can be manual if needed, but shouldn't be too complex
As it's likely you'll be dealing with large numbers, overflows and precision errors may arise in code - using libraries like gmpy provide support for integers of (theoretically) infinite size, and some nice accompanying features too (like in-built gcd and efficient modular exponentiation)
Code Example
These two statements are mathematically equivalent, but one is easier to implement in code:
import gmpy2
"""
@param pairings
list: [(pt1, ct1), (pt2, ct2), ..., (ptk, ctk)]
@param e
int : encryption exponent
@return
int : recovered N
"""
def recover_n(pairings, e):
pt1, ct1 = pairings[0]
N = ct1 - pow(pt1, e)
# loop through and find common divisors
for pt,ct in pairings:
val = gmpy2.mpz(ct - pow(pt, e))
N = gmpy2.gcd(val, N)
return N
Common Modulus Attack
What to do when the same message is encrypted twice with the same modulus but a different public key?
Imagine we have Alice and Bob. Alice sends the SAME message to Bob more than once using the same public key. The internet being the internet, a problem may happen; a bit is flipped, and the public key changed while the modulus stayed the same.
What we know
Let be the following:
Boneh-Durfee Attack
What is Boneh-Durfee Attack
Boneh-Durfee attack is an extension of Wiener's attack. That is, it also attacks on low private component with a further relaxed condition. If satisfies:
Then we can use Boneh-Durfee attack to retrive
this, using a graphical directed point of view, can be seen as:
Proof of correctness
We now consider necessary for the successful description of an RSA ciphertext. The core of this result is due to which states
for all coprime integers and is .
As a reminder, we say two integers are coprime if they share no non-trivial factors. This is the same statement that .
RSA application
Tutorial for application with RSA. We are going to use openSSL, openSSH and pycryptodome for key generation, key extraction and some implementation with python
Pycryptodome:
Pycryptodome is a python library about cryptography, see the documentation below: There is an example of RSA key generation with pycryptodome:
RSA
Will be introduced in this page the fundamentals of RSA, mathematical requirement and also some application with python and openSSL.
This page is pretty long, probably could be split up
Edit: I haved deleted the last part, application with RSA, and i made a special part for this. Maybe we can do the same with the second part: Arithmetic for RSA.
m the message in plaintext
e1 the public key of the first ciphertext
c1 the first ciphertext
e2 the public key of the second ciphertext
c2 the second ciphertext
n the modulus that is common to both ciphertexts
All of these but m are essentially given to us.
Conditions of the attack
Because we are going to need to calculate inverses for this attack, we must first make sure that these inverses exist in the first place:
The math behind the attack
We know that RSA goes as follows:
From the conditions above we also know that e1 and e2 are co-prime. Thus using Bezout's Theorem we can get:
Using this, we can derive the original message m :
NB: all the calculations are done mod n
In general, Bezout's Theorem gives a pair of positive and negative numbers. We just need to adapt this equation a little to make it work for us. In this case, let's assume y is the negative number:
Now to truly recover the plaintext, we are actually doing:
for some kβZ. Combining this with Euler's theorem, we see that we recover mfrom the ciphertext
When the requirement gcd(m,n)=1does not hold, we can instead look at the equivalences modulo pand qrespectively. Clearly, when gcd(m,n)=n,we have that cβ‘mβ‘0modnand our correctness still holds. Now, consider the case m=kβ p,where we have that cβ‘mβ‘0modpand cdβ‘(me)d(modq).Since we have already excluded the case that gcd(m,q)=q,we can conclude that gcd(m,q)=1,as qis prime. This means that mβΟ(q)β‘1modqand by the multiplicative properties of the Οfunction, we determine that mβnβΟ(n)=mβΟ(q)β‘1modq.We conclude by invoking the Chinese Remainder theorem with
that meβ dβ‘mmodn.The case for m=kβ qfollows in a parallel manner.
OpenSSL is a robust, commercial-grade, and full-featured toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols. It is also a general-purpose cryptography library
from Crypto.Util.number import getPrime, bytes_to_long
def generate_keys():
e = 0x10001 #public exponent e, we generally use this one by default
while True:
p = getPrime(512)
q = getPrime(512)
phi = (p - 1) * (q - 1) #Euler's totient
d = pow(e, -1, phi) #Private exponent d
if d != -1:
break
n = p * q
public_key = (n, e)
private_key = (n, d)
return public_key, private_key
def encrypt(plaintext: int, public_key) -> int:
n, e = public_key
return pow(plaintext, e, n) #plaintext ** e mod n
def decrypt(ciphertext: int, private_key) -> int:
n, d = private_key
return pow(ciphertext, d, n) #ciphertext ** d mod n
message = bytes_to_long(b"super_secret_message")
public_key, private_key = generate_keys()
ciphertext = encrypt(message, public_key)
plaintext = decrypt(ciphertext, private_key)
Consider d<Nifor first, see that
As stated above, the RSA's totient function can be espressed as:
continuing with the equation, we see that
and if we decide to consider x=2N+1βand y=2p+qβ, we will have:
At this point, finding dis equivalent to find the 2 small solutions xand yto the congruence
now let s=β2p+qβand A=2N+1βthis will preserve the scomposed Ο(N)subtraction
consider e=NΞ±(with any Ξ±), we deduct that Ξ±must be really closed to 1because eis in the same order of the length of N(so e=NβΌ1β), we will get
RSA is a public-key cryptosystem that is widely used in the world today to provide a secure transmission system to millions of communications, is one of the oldest such systems in existence. The acronymRSA comes from the surnames of Ron Rivest, Adi Shamir, and Leonard Adleman, who publicly described the algorithm in 1977. An equivalent system was developed secretly, in 1973 at GCHQ (the British signals intelligence agency), by the English mathematician Clifford Cocks. That system was declassified in 1997.
All public-key systems are based on the concept of trapdoor functions, functions that are simple to compute in one direction but computationally hard to reverse without knowledge of some special information called the trapdoor. In RSA, the trapdoor function is based on the hardness of factoring integers. The function involves the use of a public keyNto encrypt data, which is (supposed to be) encrypted in such a way that the function cannot be reversed without knowledge of the prime factorisation ofN, something that should be kept private. Except in certain cases, there exists no efficient algorithm for factoring huge integers.
Formalize the introduction and include a discussion of the security based on the hardness of factoring integers.
II- Arithmetic for RSA
Before starting to introducing you RSA, a few arithmetic notions need to be introduce to understand perfectly other steps.
III- Key generation
We pick two primes p and q
Using p and q, we calculate modulus n=pΓq and its Euler's totient Ο(n)=(pβ1)Γ(qβ1)
Now, choose the public exponentesuch as
By using the Extended Euclidean algorithm, we compute the invert of : which is our private exponent.
Public key:
Private key:
Now, chose a message that you convert into integers
We can encrypt this plaintext and receive a ciphertext
We can decrypt a ciphertext with
IV- Signature
A digital signature is a proof of the authenticity of a message, i.e. a proof that the message has not been tampered with. RSA allows us to sign messages by "encrypting" them using the private key, the result being a signature that anyone can verify by "decrypting" it with the public key and comparing it to the associated message. Any attempt to tamper with the message will result in it no longer matching the signature, and vice-versa. Futhermore, a signature can only be generated using the private key, making it a secure and efficient method of confirming the authenticity of messages.
Say Alice wants to send a message to Bob, but does not want Mallory, who has established herself as a middleman, to make changes to the message or swap it out entirely. Fortunately, Bob knows Alice's public key, and since e and d are inverses such that edβ‘1modΟ(n), Alice can sign her message m by "encrypting" it with the private key such that sβ‘mdmodn, where s is the signature verifying that the message came from Alice. Alice can now send m and s to Bob, who is now able to check the authenticity of the message by checking if mβ‘semodn. If Mallory tries to change m, this congruence no longer holds, and since she does not have the private key, she is also unable to provide a maching sfor her tampered message.
V- Format
Wiener's Attack
Wiener's attack is an attack on RSA that uses continued fractions to find the private exponent when it's small (less than , where is the modulus). We know that when we pick the public exponent to be a small number and calcute its inverse
Wiener's theorem
Wiener's attack is based on the following theorem:
gcd(e,Ο(n))=1
d
emodn
dβ‘eβ1modΟ(n)
n,e
n,d
m
m
cβ‘memodn
c
mβ‘cdmodn
Low Private Component Attacks
Let
, with
. Let
. Given
and
with
, the attacker can efficiently recover
.
Some observations on RSA
In order to better understand Wiener's Attack, it may be useful to take note of certain properties of RSA:
We may start by noting that the congruence edβ‘1modΟ(n) can be written as the equality ed=kΟ(n)+1 for some value k, we may additionally note that Ο(n)=(pβ1)(qβ1)=pqβpβq+1, since both p and q are much shorter than pq=n, we can say that Ο(n)βn.
Dividing the former equation by dΟ(n) gives us Ο(n)eβ=dk+1β, and using the latter approximation, we can write this as neββdkβ. Notice how the left-hand side of this equation is composed entirely of public information, this will become important later.
It is possible to quickly factor n by knowing n and Ο(n). Consider the quadratic polynomial (xβq)(xβp), this polynomial will have the roots p and q. Expanding it gives us x2β(p+q)x+pq, and substituting for the variables we know we can write this as x2β(nβΟ(n)+1)x+n. Applying the quadratic formula gives us p and q: pβ§q=2aβbΒ±b2β4acββ, where a=1, b=nβΟ(n)+1, and c=n.
Wiener's attack works by expanding neβ to a continued fraction and iterating through the terms to check various approximations of dkβ. In order to make this checking process more efficient, we can make a few observations (this part is optional):
Since Ο(n) is even, and e and d are both by definition coprime to Ο(n), we know that d is odd.
Given the above equations and the values of e, n, d, and k, we can solve for Ο(n) with the equation Ο(n)=kedβ1β, thus we know that edβ1 has to be divisible by k.
If our is correct, the polynomial will have roots and , which we can verify by checking if .
The Attack
Suppose we have the public key (n,e), this attack will determine d
Convert the fraction neβ into a continued fraction [a0β;a1β,a2β,β¦,akβ2β,akβ1β,akβ]
Iterate over each convergent in the continued fraction: 1a0ββ,a0β+a1β1β,a0β+a1β+a2β1β1β,Β β¦,a0β+a1β+akβ2β+akβ1β1ββ±β1β,
Check if the convergent is dkβ by doing the following:
Set the numerator to be and denominator to be
Check if is odd, if not, move on to the next convergent
Check if , if not, move on to the next convergent
Set and find the roots of the polynomial
If the roots of the polynomial are integers, then we've found . (If not, move on to the next convergent)
If all convergents have been tried, and none of them work, then the given RSA parameters are not vulnerable to Wiener's attack.
Here's a sage implementation to play around with:
//TODO: Proof of Wiener's theorem
Automation
The Python module owiener simplifies the scripting process of Wiener's attack:
Here is a Wiener's attack template:
d
31β4nβ
n
e
dβ‘eβ1modΟ(n)
n=pq
q<p<2q
d<31β4nβ
n
e
edβ‘1modΟ(n)
d
from Crypto.Util.number import long_to_bytes
def wiener(e, n):
# Convert e/n into a continued fraction
cf = continued_fraction(e/n)
convergents = cf.convergents()
for kd in convergents:
k = kd.numerator()
d = kd.denominator()
# Check if k and d meet the requirements
if k == 0 or d%2 == 0 or e*d % k != 1:
continue
phi = (e*d - 1)/k
# Create the polynomial
x = PolynomialRing(RationalField(), 'x').gen()
f = x^2 - (n-phi+1)*x + n
roots = f.roots()
# Check if polynomial as two roots
if len(roots) != 2:
continue
# Check if roots of the polynomial are p and q
p,q = int(roots[0][0]), int(roots[1][0])
if p*q == n:
return d
return None
# Test to see if our attack works
if __name__ == '__main__':
n = 6727075990400738687345725133831068548505159909089226909308151105405617384093373931141833301653602476784414065504536979164089581789354173719785815972324079
e = 4805054278857670490961232238450763248932257077920876363791536503861155274352289134505009741863918247921515546177391127175463544741368225721957798416107743
c = 5928120944877154092488159606792758283490469364444892167942345801713373962617628757053412232636219967675256510422984948872954949616521392542703915478027634
d = wiener(e,n)
assert not d is None, "Wiener's attack failed :("
print(long_to_bytes(int(pow(c,d,n))).decode())
#!/usr/bin/env python3
import owiener
from Crypto.Util.number import long_to_bytes
#--------Data--------#
N = <N>
e = <e>
c = <c>
#--------Wiener's attack--------#
d = owiener.attack(e, N)
if d:
m = pow(c, d, N)
flag = long_to_bytes(m).decode()
print(flag)
else:
print("Wiener's Attack failed.")