Repository: pyca/cryptography

Issue:
[RSA] Add functions to recover (p, q) given (n, e, d)
Right now we require (p, q, d, dmp1, dmq1, iqmp, e, n). We provide functions to generate the CRT coefficients, but they assume the user has p & q. To support other valid key material sources we need functions that recover p & q given (n, e, d).

The preferred algorithm to perform this task can be found in Appendix C of [SP-800-56B](http://csrc.nist.gov/publications/nistpubs/800-56B/sp800-56B.pdf).

[RSA] Add functions to recover (p, q) given (n, e, d)
Right now we require (p, q, d, dmp1, dmq1, iqmp, e, n). We provide functions to generate the CRT coefficients, but they assume the user has p & q. To support other valid key material sources we need functions that recover p & q given (n, e, d).

The preferred algorithm to perform this task can be found in Appendix C of [SP-800-56B](http://csrc.nist.gov/publications/nistpubs/800-56B/sp800-56B.pdf).



Discussion:
Are there any standards that actually use keys without the primes?

On 28 April 2014 05:15:28 Paul Kehrer notifications@github.com wrote:

> Right now we require (p, q, d, dmp1, dmq1, iqmp, e, n). We provide 
> functions to generate the CRT coefficients, but they assume the user has p 
> & q. To support other valid key material sources we need functions that 
> recover p & q given (n, e, d).
> 
> The preferred algorithm to perform this task can be found in Appendix C of 
> [SP-800-56B](http://csrc.nist.gov/publications/nistpubs/800-56B/sp800-56B.pdf).
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/975

> 1.     The order of the recovered prime factors p and q may be the reverse of the order in which 
>    the factors were generated originally. 

Do we care that we can't get `p` and `q` the right way around?

I don't know of any standards that do this, but since (n, e, d) is an acceptable minimum to do signing I would imagine we will eventually run into a system that operates using only that set. It doesn't seem like p/q being reversed would be a particularly big deal since they're only being recovered to generate CRT coefficients essentially.

p and q are just interchangeable labels, you definitely never care which is which :)

That makes sense. I am a little worried that since they do have names 
something out there will rely on that being a stable mapping somehow. This 
is no less vague a thought than actually finding a system that doesn't have 
p or q though I suppose :-)

On 28 April 2014 21:20:33 Laurens Van Houtven notifications@github.com wrote:

> p and q are just interchangeable labels, you definitely never care which is 
> which :)
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/975#issuecomment-41608121

conch seems to care to some extent https://github.com/racker/python-twisted-conch/blob/master/twisted/conch/ssh/keys.py#L232 Also several patents mention RSA cryptosystems with p less than q. I have no idea if anyone ever defines them to be the opposite way around so far.

I wrote the code to recover p and q from n, e, d. How can I give you the code?

I put it here: https://gist.github.com/ddddavidee/b34c2b67757a54ce75cb
it will, probably, need some refactoring to meet your code rules.

FWIW, PyCrypto can also do something like this.  All it needs is n, e & d, and it will recover p & q automatically.  I can't vouch for whether this is actually a good idea to do automatically (timing attacks weren't on my radar when I merged that code), but the code is there.

Python implementation: https://github.com/dlitz/pycrypto/blob/7acba5f3a6ff10f1424c309d0d34d2b713233019/lib/Crypto/PublicKey/_slowmath.py#L100
C implementation: https://github.com/dlitz/pycrypto/blob/7acba5f3a6ff10f1424c309d0d34d2b713233019/src/_fastmath.c#L612

Are there any standards that actually use keys without the primes?

On 28 April 2014 05:15:28 Paul Kehrer notifications@github.com wrote:

> Right now we require (p, q, d, dmp1, dmq1, iqmp, e, n). We provide 
> functions to generate the CRT coefficients, but they assume the user has p 
> & q. To support other valid key material sources we need functions that 
> recover p & q given (n, e, d).
> 
> The preferred algorithm to perform this task can be found in Appendix C of 
> [SP-800-56B](http://csrc.nist.gov/publications/nistpubs/800-56B/sp800-56B.pdf).
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/975

> 1.     The order of the recovered prime factors p and q may be the reverse of the order in which 
>    the factors were generated originally. 

Do we care that we can't get `p` and `q` the right way around?

I don't know of any standards that do this, but since (n, e, d) is an acceptable minimum to do signing I would imagine we will eventually run into a system that operates using only that set. It doesn't seem like p/q being reversed would be a particularly big deal since they're only being recovered to generate CRT coefficients essentially.

p and q are just interchangeable labels, you definitely never care which is which :)

That makes sense. I am a little worried that since they do have names 
something out there will rely on that being a stable mapping somehow. This 
is no less vague a thought than actually finding a system that doesn't have 
p or q though I suppose :-)

On 28 April 2014 21:20:33 Laurens Van Houtven notifications@github.com wrote:

> p and q are just interchangeable labels, you definitely never care which is 
> which :)
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/975#issuecomment-41608121

conch seems to care to some extent https://github.com/racker/python-twisted-conch/blob/master/twisted/conch/ssh/keys.py#L232 Also several patents mention RSA cryptosystems with p less than q. I have no idea if anyone ever defines them to be the opposite way around so far.

I wrote the code to recover p and q from n, e, d. How can I give you the code?

I put it here: https://gist.github.com/ddddavidee/b34c2b67757a54ce75cb
it will, probably, need some refactoring to meet your code rules.

FWIW, PyCrypto can also do something like this.  All it needs is n, e & d, and it will recover p & q automatically.  I can't vouch for whether this is actually a good idea to do automatically (timing attacks weren't on my radar when I merged that code), but the code is there.

Python implementation: https://github.com/dlitz/pycrypto/blob/7acba5f3a6ff10f1424c309d0d34d2b713233019/lib/Crypto/PublicKey/_slowmath.py#L100
C implementation: https://github.com/dlitz/pycrypto/blob/7acba5f3a6ff10f1424c309d0d34d2b713233019/src/_fastmath.c#L612


Resolving patch:
diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
--- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -4,6 +4,8 @@
 
 from __future__ import absolute_import, division, print_function
 
+from fractions import gcd
+
 import six
 
 from cryptography import utils
@@ -119,6 +121,55 @@ def rsa_crt_dmq1(private_exponent, q):
     return private_exponent % (q - 1)
 
 
+# Controls the number of iterations rsa_recover_prime_factors will perform
+# to obtain the prime factors. Each iteration increments by 2 so the actual
+# maximum attempts is half this number.
+_MAX_RECOVERY_ATTEMPTS = 1000
+
+
+def rsa_recover_prime_factors(n, e, d):
+    """
+    Compute factors p and q from the private exponent d. We assume that n has
+    no more than two factors. This function is adapted from code in PyCrypto.
+    """
+    # See 8.2.2(i) in Handbook of Applied Cryptography.
+    ktot = d * e - 1
+    # The quantity d*e-1 is a multiple of phi(n), even,
+    # and can be represented as t*2^s.
+    t = ktot
+    while t % 2 == 0:
+        t = t // 2
+    # Cycle through all multiplicative inverses in Zn.
+    # The algorithm is non-deterministic, but there is a 50% chance
+    # any candidate a leads to successful factoring.
+    # See "Digitalized Signatures and Public Key Functions as Intractable
+    # as Factorization", M. Rabin, 1979
+    spotted = False
+    a = 2
+    while not spotted and a < _MAX_RECOVERY_ATTEMPTS:
+        k = t
+        # Cycle through all values a^{t*2^i}=a^k
+        while k < ktot:
+            cand = pow(a, k, n)
+            # Check if a^k is a non-trivial root of unity (mod n)
+            if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1:
+                # We have found a number such that (cand-1)(cand+1)=0 (mod n).
+                # Either of the terms divides n.
+                p = gcd(cand + 1, n)
+                spotted = True
+                break
+            k *= 2
+        # This value was not any good... let's try another!
+        a += 2
+    if not spotted:
+        raise ValueError("Unable to compute factors p and q from exponent d.")
+    # Found !
+    q, r = divmod(n, p)
+    assert r == 0
+
+    return (p, q)
+
+
 class RSAPrivateNumbers(object):
     def __init__(self, p, q, d, dmp1, dmq1, iqmp,
                  public_numbers):


Test patch:
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1698,3 +1698,30 @@ def test_private_numbers_ne(self):
             1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3)
         )
         assert num != object()
+
+
+class TestRSAPrimeFactorRecovery(object):
+    @pytest.mark.parametrize(
+        "vector",
+        _flatten_pkcs1_examples(load_vectors_from_file(
+            os.path.join(
+                "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"),
+            load_pkcs1_vectors
+        ))
+    )
+    def test_recover_prime_factors(self, vector):
+        private, public, example = vector
+        p, q = rsa.rsa_recover_prime_factors(
+            private["modulus"],
+            private["public_exponent"],
+            private["private_exponent"]
+        )
+        # Unfortunately there is no convention on which prime should be p
+        # and which one q. The function we use always makes p < q, but the
+        # NIST vectors are not so consistent. Accordingly we verify we've
+        # recovered the proper (p, q) by sorting them and asserting on that.
+        assert sorted([p, q]) == sorted([private["p"], private["q"]])
+
+    def test_invalid_recover_prime_factors(self):
+        with pytest.raises(ValueError):
+            rsa.rsa_recover_prime_factors(34, 3, 7)