Skip to content

Commit 7b672ca

Browse files
committed
Merge pull request #1633 from reaperhulk/fix-975
recover (p, q) given (n, e, d)
2 parents 2ca4a77 + 65637eb commit 7b672ca

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Changelog
88

99
* :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can
1010
now load elliptic curve public keys.
11+
* Added
12+
:func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_prime_factors`
1113

1214
0.7.2 - 2015-01-16
1315
~~~~~~~~~~~~~~~~~~

docs/hazmat/primitives/asymmetric/rsa.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,20 @@ this without having to do the math themselves.
391391
Computes the ``dmq1`` parameter from the RSA private exponent and prime
392392
``q``.
393393

394+
.. function:: rsa_recover_prime_factors(n, e, d)
395+
396+
.. versionadded:: 0.8
397+
398+
Computes the prime factors ``(p, q)`` given the modulus, public exponent,
399+
and private exponent.
400+
401+
.. note::
402+
403+
When recovering prime factors this algorithm will always return ``p``
404+
and ``q`` such that ``p < q``.
405+
406+
:return: A tuple ``(p, q)``
407+
394408

395409
.. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
396410
.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography

src/cryptography/hazmat/primitives/asymmetric/rsa.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from __future__ import absolute_import, division, print_function
66

7+
from fractions import gcd
8+
79
import six
810

911
from cryptography import utils
@@ -119,6 +121,55 @@ def rsa_crt_dmq1(private_exponent, q):
119121
return private_exponent % (q - 1)
120122

121123

124+
# Controls the number of iterations rsa_recover_prime_factors will perform
125+
# to obtain the prime factors. Each iteration increments by 2 so the actual
126+
# maximum attempts is half this number.
127+
_MAX_RECOVERY_ATTEMPTS = 1000
128+
129+
130+
def rsa_recover_prime_factors(n, e, d):
131+
"""
132+
Compute factors p and q from the private exponent d. We assume that n has
133+
no more than two factors. This function is adapted from code in PyCrypto.
134+
"""
135+
# See 8.2.2(i) in Handbook of Applied Cryptography.
136+
ktot = d * e - 1
137+
# The quantity d*e-1 is a multiple of phi(n), even,
138+
# and can be represented as t*2^s.
139+
t = ktot
140+
while t % 2 == 0:
141+
t = t // 2
142+
# Cycle through all multiplicative inverses in Zn.
143+
# The algorithm is non-deterministic, but there is a 50% chance
144+
# any candidate a leads to successful factoring.
145+
# See "Digitalized Signatures and Public Key Functions as Intractable
146+
# as Factorization", M. Rabin, 1979
147+
spotted = False
148+
a = 2
149+
while not spotted and a < _MAX_RECOVERY_ATTEMPTS:
150+
k = t
151+
# Cycle through all values a^{t*2^i}=a^k
152+
while k < ktot:
153+
cand = pow(a, k, n)
154+
# Check if a^k is a non-trivial root of unity (mod n)
155+
if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
156+
# We have found a number such that (cand-1)(cand+1)=0 (mod n).
157+
# Either of the terms divides n.
158+
p = gcd(cand + 1, n)
159+
spotted = True
160+
break
161+
k *= 2
162+
# This value was not any good... let's try another!
163+
a += 2
164+
if not spotted:
165+
raise ValueError("Unable to compute factors p and q from exponent d.")
166+
# Found !
167+
q, r = divmod(n, p)
168+
assert r == 0
169+
170+
return (p, q)
171+
172+
122173
class RSAPrivateNumbers(object):
123174
def __init__(self, p, q, d, dmp1, dmq1, iqmp,
124175
public_numbers):

tests/hazmat/primitives/test_rsa.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,3 +1698,30 @@ def test_private_numbers_ne(self):
16981698
1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)
16991699
)
17001700
assert num != object()
1701+
1702+
1703+
class TestRSAPrimeFactorRecovery(object):
1704+
@pytest.mark.parametrize(
1705+
"vector",
1706+
_flatten_pkcs1_examples(load_vectors_from_file(
1707+
os.path.join(
1708+
"asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"),
1709+
load_pkcs1_vectors
1710+
))
1711+
)
1712+
def test_recover_prime_factors(self, vector):
1713+
private, public, example = vector
1714+
p, q = rsa.rsa_recover_prime_factors(
1715+
private["modulus"],
1716+
private["public_exponent"],
1717+
private["private_exponent"]
1718+
)
1719+
# Unfortunately there is no convention on which prime should be p
1720+
# and which one q. The function we use always makes p < q, but the
1721+
# NIST vectors are not so consistent. Accordingly we verify we've
1722+
# recovered the proper (p, q) by sorting them and asserting on that.
1723+
assert sorted([p, q]) == sorted([private["p"], private["q"]])
1724+
1725+
def test_invalid_recover_prime_factors(self):
1726+
with pytest.raises(ValueError):
1727+
rsa.rsa_recover_prime_factors(34, 3, 7)

0 commit comments

Comments
 (0)