Answer posted to: https://crypto.stackexchange.com/questions/8687/security-strength-of-rsa-in-relation-with-the-modulus-size
A moderator deleted it about a month later on the basis it wouldn't be useful to anyone due to mostly being a code port.
Making it available to public here in case it'd be useful for anyone else. Running JS or Rust is possibly more easily accessible than the two options in answers provided.
For anyone else curious and wanting to run the math via a language that is more available/accessible (eg a REPL via a web browser), I ported the answer by Charles to Javascript (ES6) below:
const a = 1/3
const b = 2/3
const l = (x) => Math.log(x)
const e = (x) => Math.exp(x)
function to_symmetric_bits(asymmetric_bits) {
const t = asymmetric_bits * l(2)
const m = l(t)
const n = e( l(m) * b )
const o = e( l(t) * a )
const p = (1.923 * o * n - 4.69) / l(2)
return p
}
const sizes = [256, 512, 1024, 2048, 3072, 4096, 7680, 8192, 15360, 16384]
console.log(`RSA Keylength to equivalent 'Bits of Security':`)
sizes.forEach((size) => {
console.log(`${size} == ${to_symmetric_bits(size)}`)
})
After which, I looked at the official cited NIST source on page 119 and noted that the math while valid seemed not to match the equation... So here's a more direct variant that should be easy to follow when comparing to the linked equation:
function to_symmetric_bits(keylength) {
const a = keylength * Math.log(2) // L * ln(2)
const b = Math.pow(Math.log(a), 2) // ln(L * ln(2))^2
// Top half (numerator) / ln(2) == x
const numerator = 1.923 * Math.cbrt(a) * Math.cbrt(b) - 4.69
const x = numerator / Math.log(2)
return x
}
const sizes = [256, 512, 1024, 2048, 3072, 4096, 7680, 8192, 15360, 16384]
console.log(`RSA Keylength to equivalent 'Bits of Security':`)
sizes.forEach((size) => {
console.log(`${size} == ${to_symmetric_bits(size)}`)
})
Output:
RSA Keylength to equivalent 'Bits of Security':
256 == 39.8983534444018
512 == 57.16312330388321
1024 == 79.99990535893299
2048 == 110.11760837749868
3072 == 131.97008244496166
4096 == 149.73076030163335
7680 == 196.25255668822675
8192 == 201.70630874279217
15360 == 262.6186131379134
16384 == 269.7522498627511
The document on page 119 also cites after the equation:
If the lengths of the Diffie-Hellman public and private keys are L and N, correspondingly, then the length y of a symmetric key of approximately the same strength can be computed as:
y = min(x, N / 2)
where x is computed as in formula (1) above
N
values are shared for some key lengths at the top of page 119, when divided by 2, you end up with the 112, 128, 192, 256 values. Which for the output shared above would result in 110(2048), 128(3072), 192(7680), 256(15360).
The document referenced has an initial release date of 2003, and was last updated in 2020. The equation doesn't appear to have changed from what has been shown in this thread from answers years ago however.
I didn't notice originally, but Charles initially ported the GNFS algorithm from Reid, and for whatever reason later modified it to adjust to the NIST algorithm. That is there shouldn't have been any a
, b
fractions or Math.exp()
? The GNFS equation also has Math.cbrt(64/9)
which is where the 1.923
value came from.
The following is JS equivalent, but you won't have success with it due to JS numbers being larger than supported (infinity starts from 2^1024):
// log2(exp(cbrt(64/9) * log(2^1024)^(1/3) * log(log(2^1024))^(2/3)))
function to_symmetric_bits(keylength) {
const n = Math.pow(2, keylength)
const a = Math.cbrt(64/9)
const b = Math.pow(Math.log(n),1/3)
const c = Math.pow(Math.log(Math.log(n)), 2/3)
const x = Math.log2(Math.exp(a * b * c))
return x
}
Turns out t
var from Charles approach produces the same desired output for the n
value in my code above. This avoids the ridiculously large number becoming infinity (which would affect most languages float numbers? Not just JS, I verified with Rust f64 type):
function to_symmetric_bits(keylength) {
const n = keylength * Math.log(2)
const a = Math.cbrt(64/9)
const b = Math.pow(n, 1/3)
const c = Math.pow(Math.log(n), 2/3)
const x = Math.log2(Math.exp(a * b * c))
return x
}
Rust:
fn main() {
let sizes: Vec<i32> = vec![256, 512, 1024, 2048, 3072, 4096, 7680, 8192];
println!("RSA Keylength to equivalent 'Bits of Security':");
for size in sizes.into_iter() {
println!("{} == {}", size, to_symmetric_bits(size as f64));
}
}
fn to_symmetric_bits(keylength: f64) -> f64 {
let n = (2_f64).ln() * keylength;
let a = f64::cbrt(64.0/9.0);
let b = n.powf(1.0/3.0);
let c = n.ln().powf(2.0/3.0);
let x = ((a * b * c).exp()).log2();
x
}
For anyone interested of the output for 256-bit and 512-bit RSA:
256 == 46.664579283290124
512 == 63.92934399904213
The linked NIST document received an update in 2021Q2, the referenced page 119 is now 121.
(64/9)^(1/3) == cbrt(64/9)
, that is forbase^power
, using a power of1/3
is equivalent result to the cube root.1/2
.2/3
, as the original algorithm usescbrt(a * b * c)
, wherec == ln( ln( n ) )^2 == x^2
. These two are equivalent:cbrt( x^2 ) == x^(2/3)
.Comparing the GNFS algorithm (with
log2()
for bits representation) to the NIST variant:log2( e^(a * b * c) ) == ( (a * b * c) / log(2) )
=>log2(e^x) == (x / log(2))
, the only difference with NIST is it also subtracts4.69
(or separate from the original symmetric bits result subtract:4.69 / log(2) == log2(exp(4.69)) == ~6.766
bits). That is what contributes to the delta between GNFS and NIST.This answer details that the value would have been defined to adjust the result from 86 symmetric bits for 1024-bit, to the desired 80 symmetric bits. While insightful answers here note that GNFS accuracy for such a symmetric strength equivalence is imprecise, so lowering results by a few bits adds some safety margin, and they round to the nearest byte (8-bit) boundary after the adjustment, with a little wiggle room (eg RSA-7680) to align with existing symmetric bit lengths like 192-bit.
Adding another resource, the NFS algorithm was cited in this document back in 2004, focused on determining symmetric strength. This notes like others that Diffie-Hellman is said to be a little bit stronger than the equivalent RSA size, noting 20-64x more difficult (4.3-6 bits), which aligns with rough estimates stated elsewhere (eg: 1024-bit DH being roughly 1200-bit RSA). It's possible that the offset NIST adds is related to this. Section 2.2 of the RFC 3766 document also notes the GNFS algorithm is calculating on the basis of single/simple operations as opposed to elementary operations/instructions that would actually be involved (in regards to relative strength, which this is often used for comparison, it's a non-issue), they shift the focus to MIPS years (MYs) and note that actual factorings at the time had proven to be far less effort than naively assumed.
Relative symmetric strength roughly holds up when comparing results with equivalent CPU core years.
Additional calculations to try match MIPS with the equation to the results from the RSA challenge results, by mixing in MIPS-year into the calculation as explained in RFC 3766 - Section 2.2: