<p>I put in the effort to dig through <a href="https://crypto.stanford.edu/%7Edabo/papers/RSA-survey.pdf" rel="nofollow noreferrer">Boneh's paper</a>. The &quot;algorithm&quot; for deriving <code>(p, q)</code> from <code>(n, d)</code> is buried at the end of §1.1, coded in maths jargon, and left as an exercise for the reader to render out of his (rather terse) <em>proof</em> that it's <em>efficient</em> to do so.</p>
<blockquote>
<p>Let 〈<i>N</i>, <i>e</i>〉 be an RSA public key. Given the private key <i>d</i>, one can efficiently factor the modulus <i>N</i> = <i>pq</i>.</p>
<p><strong>Proof.</strong> Compute <i>k</i> = <i>de</i> − 1. By definition of <i>d</i> and <i>e</i> we know that <i>k</i> is a multiple of <i>φ</i>(<i>N</i>). Since <i>φ</i>(<i>N</i>) is even, <i>k</i> = 2<sup><i>t</i></sup><i>r</i> with <i>r</i> odd and <i>t</i> ≥ 1. We have <i>g</i><sup><i>k</i></sup> = 1 for every <i>g</i> ∈ ℤ<sub><i>N</i></sub><sup>×</sup>, and therefore <em>g</em><sup><em>k</em>/2</sup> is a square root of unity modulo <i>N</i>. By the Chinese Remainder Theorem, 1 has four square roots modulo <i>N</i> = <i>pq</i>. Two of these square roots are ±1. The other two are ±<i>x</i> where <i>x</i> satisfies <i>x</i> = 1 mod <i>p</i> and <i>x</i> = −1 mod <i>q</i>. Using either one of these last two square roots, the factorization of <i>N</i> is revealed by computing gcd(<i>x</i> − 1, <i>N</i>). A straightforward argument shows that if <i>g</i> is chosen at random from ℤ<sub><i>N</i></sub><sup>×</sup> then with probability at least 1/2 (over the choice of <i>g</i>) one of the elements in the sequence <i>g</i><sup><i>k</i>/2</sup>, <i>g</i><sup><i>k</i>/4</sup>, …, <i>g</i><sup><i>k</i>/2<sup><i>t</i></sup></sup> mod <i>N</i> is a square root of unity that reveals the factorization of <i>N</i>. All elements in the sequence can be efficiently computed in time <i>O</i>(<i>n</i><sup>3</sup>) where <i>n</i> = log<sub>2</sub>(<i>N</i>).</p>
</blockquote>
<p>Obviously, this is pretty close to meaningless for anyone who doesn't know what <a href="https://math.stackexchange.com/q/4496759/188128#comments-4496759"><code>$Z_N^\ast$</code></a> is, and has a pretty nonlinear structure that takes a good deal of time to twist into a linear algorithm.</p>
<p>So here is the worked solution:</p>
<pre class="lang-py prettyprint-override"><code>from random import randrange
from math import gcd


def ned_to_pqe(secret_key):
    &quot;&quot;&quot;
    https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf#:~:text=Given%20d%2C,reveals%20the%20factorization%20of%20N%2E
    &quot;&quot;&quot;
    n, e, d = secret_key
    k = d * e - 1
    t = bit_scan1(k)
    trivial_sqrt1 = {1, n - 1}
    while True:
        g = randrange(2, n - 1)
        for j in range(1, t + 1):
            x = pow(g, k &gt;&gt; j, n)
            if pow(x, 2, n) == 1:
                if x in trivial_sqrt1: continue
                p = gcd(x - 1, n)
                q = n // p
                if q &gt; p: p, q = q, p
                return p, q, e


def pqe_to_ned(secret_key):
    p, q, e = secret_key
    n = p * q
    l = (p - 1) * (q - 1)
    d = pow(e, -1, l)
    return n, e, d


def bit_scan1(i):
    &quot;&quot;&quot;
    https://gmpy2.readthedocs.io/en/latest/mpz.html#mpz.bit_scan1
    &quot;&quot;&quot;
    # https://stackoverflow.com/a/63552117/1874170
    return (i &amp; -i).bit_length() - 1


def test():
    secret_key = (
        # https://en.wikipedia.org/wiki/RSA_numbers#RSA-100
        # Should take upwards of an hour to factor on a consumer desktop ca. 2022
        1522605027922533360535618378132637429718068114961380688657908494580122963258952897654000350692006139,
        65537,
        1435319569480661473883310243084583371347212233430112391255270984679722445287591616684593449660400673
    )
    if secret_key != pqe_to_ned(ned_to_pqe(secret_key)):
        raise ValueError


if __name__ == '__main__':
    test()
    print(&quot;Self-test OK&quot;)

</code></pre>
<p>Live demo (JS):</p>
<p><div class="snippet" data-lang="js" data-hide="true" data-console="true" data-babel="false">
<div class="snippet-code snippet-currently-hidden">
<pre class="snippet-code-js lang-js prettyprint-override"><code>function ned_to_pqe({n, e, d}) {
  // https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf#:~:text=Given%20d%2C,reveals%20the%20factorization%20of%20N%2E
  let k = d * e - 1n;
  let t = scan1(k);
  let trivial_sqrt1 = new Set([1n, n - 1n]);
  while (true) {
    let g = insecure_randrange(2n, n - 1n);
    for ( let j = t ; j &gt; 0 ; --j ) {
      let x = bn_powMod(g, k &gt;&gt; j, n);
      if (bn_powMod(x, 2n, n) === 1n) {
        if (trivial_sqrt1.has(x)) continue;
        let p = gcd(x - 1n, n), q = n/p;
        if (q &gt; p) [p, q] = [q, p];
        return {p, q, e};
      }
    }
  }
}


function pqe_to_ned({p, q, e}) {
  let n = p * q;
  let l = (p - 1n) * (q - 1n);
  let d = bn_modInv(e, l);
  return {n, e, d};
}


function bn_powMod(x, e, m) {
  // h/t https://umaranis.com/2018/07/12/calculate-modular-exponentiation-powermod-in-javascript-ap-n/
  if (m === 1n) return 0n;
  let y = 1n;
  x = x % m;
  while (e &gt; 0n) {
    if (e % 2n === 1n)  //odd number
      y = (y * x) % m;
    e = e &gt;&gt; 1n;  //divide by 2
    x = (x * x) % m;
  }
  return y;
}


function bn_modInv(x, m) {
  // TOY IMPLEMENTATION
  // DO NOT USE IN GENERAL-PURPOSE CODE
  // h/t https://rosettacode.org/wiki/Modular_inverse#C
  let m0 = m, t, q;
  let x0 = 0n, y = 1n;
  if (m === 1n) return 1n;
  while (x &gt; 1n) {
    q = x / m;
    t = m;
    m = x % m;
    x = t;
    t = x0;
    x0 = y - q * x0;
    y = t;
  }
  if (y &lt; 0n) y += m0;
  return y;
}


function gcd(a, b) {
  // h/t https://stackoverflow.com/a/17445304/1874170
  while (b) {
    [a, b] = [b, a % b];
  }
  return a;
}


function scan1(i) {
  // https://gmplib.org/manual/Integer-Logic-and-Bit-Fiddling#mpz_scan1
  let k = 0n;
  if ( i !== 0n ) {
    while( (i &amp; 1n) === 0n ) {
      i &gt;&gt;= 1n;
      k += 1n;
    }
  }
  return k;
}


function insecure_randrange(a, b) {
  // h/t https://arxiv.org/abs/1304.1916
  let numerator = 0n;
  let denominator = 1n;
  let n = (b - a);
  while (true) {
    numerator &lt;&lt;= 1n;
    denominator &lt;&lt;= 1n;
    numerator |= BigInt(Math.random()&gt;1/2);
    if (denominator &gt;= n) {
      if (numerator &lt; n)
        return a + numerator;
      numerator -= n;
      denominator -= n;
    }
  }
}</code></pre>
<pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;form action="javascript:" onsubmit="(({target:form,submitter:{value:action}})=&gt;{eval(action)(form)})(event)"&gt;
&lt;p&gt;
&lt;label for="p"&gt;p=&lt;/label&gt;&lt;input name="p" value="37975227936943673922808872755445627854565536638199" /&gt;&lt;br /&gt;
&lt;label for="q"&gt;q=&lt;/label&gt;&lt;input name="q" value="40094690950920881030683735292761468389214899724061" /&gt;&lt;br /&gt;
&lt;label for="n"&gt;n=&lt;/label&gt;&lt;input name="n" /&gt;&lt;br /&gt;
&lt;label for="e"&gt;e=&lt;/label&gt;&lt;input name="e" placeholder="65537" /&gt;&lt;br /&gt;
&lt;label for="d"&gt;d=&lt;/label&gt;&lt;input name="d" /&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;button type="submit" value="pqe2nd"&gt;Get (n,d) from (p,q,e)&lt;/button&gt;&lt;br /&gt;
&lt;button type="submit" value="delpq"&gt;Forget (p,q)&lt;/button&gt;&lt;br /&gt;
&lt;button type="submit" value="ned2pq"&gt;Get (p,q) from (n,e,d)&lt;/button&gt;
&lt;/form&gt;

&lt;script&gt;
function pqe2nd({elements}) {
  if (!elements['e'].value) elements['e'].value = elements['e'].placeholder;
  let p = BigInt(elements['p'].value||undefined);
  let q = BigInt(elements['q'].value||undefined);
  let e = BigInt(elements['e'].value||undefined);
  let {n, d} = pqe_to_ned({p,q,e});
  elements['n'].value = n.toString();
  elements['d'].value = d.toString();
}
function ned2pq({elements}) {
  if (!elements['e'].value) elements['e'].value = elements['e'].placeholder;
  let n = BigInt(elements['n'].value||undefined);
  let e = BigInt(elements['e'].value||undefined);
  let d = BigInt(elements['d'].value||undefined);
  let {p, q} = ned_to_pqe({n,e,d});
  elements['p'].value = p.toString();
  elements['q'].value = q.toString();
}
function delpq({elements}) {
  elements['p'].value = null;
  elements['q'].value = null;
}
&lt;/script&gt;</code></pre>
</div>
</div>
</p>
<hr />
<p>To answer the question as-stated in the title: factoring <code>N</code> entails <em>finding</em> <code>N</code>. But <a href="https://crypto.stackexchange.com/a/81620/8287">you cannot, in the general case, derive <code>N</code> from <code>(e, d)</code></a>. Therefore, you cannot, in the general case, derive the factors of <code>N</code> from <code>(e, d)</code>; QED.</p>
<blockquote>
<p>finding <em>n</em> from (<em>e</em>, <em>d</em>) is computationally feasible with fair probability, or even certainty, for a <strong>small</strong> but observable fraction of RSA keys of practical interest</p>
</blockquote>
<p>If you want to try to do so anyway, you'll need to be able to factorize <code>e * d - 1</code> (if I understand the above-linked answer correctly):</p>
<pre class="lang-py prettyprint-override"><code>from itertools import permutations


def ed_to_pq(e, d):
    # NOT ALWAYS POSSIBLE -- the number e*d-1 must be small enough to factorize
    # h/t https://crypto.stackexchange.com/a/81620/8287
    factors = factorize(e * d - 1)
    factors.sort()
    # Unimplemented optimization:
    #   if two factors are larger than (p * q).bit_length()//4
    #   and the greater of (p, q) is not many times bigger than the lesser,
    #   then you can safely assume that the large factors belong to (p-1) and (q-1)
    #   and thereby reduce the number of iterations in the following loops
    # Unimplemented optimization:
    #   permutations are overkill for this partitioning scheme;
    #   a clever mathematician could come up with something more efficient
    # Unimplemented optimization:
    #   prune permutations based on &quot;sanity&quot; factor of logarithm knapsacking
    l = len(factors)
    for arrangement in permutations(factors):
        for l_pm1 in range(1, l - 1):
            for l_qm1 in range(1, l_pm1):
                pm1 = prod(arrangement[:l_pm1])
                qm1 = prod(arrangement[l_pm1:l_pm1+l_qm1])
                try:
                    if pow(e, -1, pm1 * qm1) == d:
                        return (pm1 + 1, qm1 + 1)
                except Exception:
                    pass


from functools import reduce
from operator import mul


def prod(l):
    return reduce(mul, l)
</code></pre>
How to factor RSA modulus given the public and private exponent?
<p>I have a RSA private key with modulus <code>m</code>, public exponent <code>e</code> and private exponent <code>d</code>, but the program I am using needs the modulus's prime factors <code>p</code> and <code>q</code>.</p>

<p>Is it possible to use <code>e</code> and <code>d</code> to get <code>p</code> and <code>q</code>?</p>