From 97b0b713fbeb74d7531fe88a2362250c6324655f Mon Sep 17 00:00:00 2001 From: Pauli Date: Fri, 5 Oct 2018 09:19:30 +1000 Subject: [PATCH] RSA security bits calculation NIST has updated their guidelines in appendix D of SP 800-56B rev2 (draft) providing a formula for the number of security bits it terms of the length of the RSA key. This is an implementation of this formula using fixed point arithmetic. For integers 1 .. 100,000 it rounds down to the next smaller 8 bit strength 270 times. It never errs to the high side. None of the rounded values occur near any of the commonly selected lengths. Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/7352) --- crypto/rsa/rsa_lib.c | 129 ++++++++++++++++++++++++++++++++++++++++++- test/rsa_test.c | 60 ++++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) diff --git a/crypto/rsa/rsa_lib.c b/crypto/rsa/rsa_lib.c index 49c34b7c36..3b99743281 100644 --- a/crypto/rsa/rsa_lib.c +++ b/crypto/rsa/rsa_lib.c @@ -163,6 +163,133 @@ void *RSA_get_ex_data(const RSA *r, int idx) return CRYPTO_get_ex_data(&r->ex_data, idx); } +/* + * Define a scaling constant for our fixed point arithmetic. + * This value must be a power of two because the base two logarithm code + * makes this assumption. The exponent must also be a multiple of three so + * that the scale factor has an exact cube root. Finally, the scale factor + * should not be so large that a multiplication of two scaled numbers + * overflows a 64 bit unsigned integer. + */ +static const unsigned int scale = 1 << 18; +static const unsigned int cbrt_scale = 1 << (2 * 18 / 3); + +/* Define some constants, none exceed 32 bits */ +static const unsigned int log_2 = 0x02c5c8; /* scale * log(2) */ +static const unsigned int log_e = 0x05c551; /* scale * log2(M_E) */ +static const unsigned int c1_923 = 0x07b126; /* scale * 1.923 */ +static const unsigned int c4_690 = 0x12c28f; /* scale * 4.690 */ + +/* + * Multiply two scale integers together and rescale the result. + */ +static ossl_inline uint64_t mul2(uint64_t a, uint64_t b) +{ + return a * b / scale; +} + +/* + * Calculate the cube root of a 64 bit scaled integer. + * Although the cube root of a 64 bit number does fit into a 32 bit unsigned + * integer, this is not guaranteed after scaling, so this function has a + * 64 bit return. This uses the shifting nth root algorithm with some + * algebraic simplifications. + */ +static uint64_t icbrt64(uint64_t x) +{ + uint64_t r = 0; + uint64_t b; + int s; + + for (s = 63; s >= 0; s -= 3) { + r <<= 1; + b = 3 * r * (r + 1) + 1; + if ((x >> s) >= b) { + x -= b << s; + r++; + } + } + return r * cbrt_scale; +} + +/* + * Calculate the natural logarithm of a 64 bit scaled integer. + * This is done by calculating a base two logarithm and scaling. + * The maximum logarithm (base 2) is 64 and this reduces base e, so + * a 32 bit result should not overflow. The argument passed must be + * greater than unity so we don't need to handle negative results. + */ +static uint32_t ilog_e(uint64_t v) +{ + uint32_t i, r = 0; + + /* + * Scale down the value into the range 1 .. 2. + * + * If fractional numbers need to be processed, another loop needs + * to go here that checks v < scale and if so multiplies it by 2 and + * reduces r by scale. This also means making r signed. + */ + while (v >= 2 * scale) { + v >>= 1; + r += scale; + } + for (i = scale / 2; i != 0; i /= 2) { + v = mul2(v, v); + if (v >= 2 * scale) { + v >>= 1; + r += i; + } + } + r = (r * (uint64_t)scale) / log_e; + return r; +} + +/* + * NIST SP 800-56B rev 2 Appendix D: Maximum Security Strength Estimates for IFC + * Modulus Lengths. + * + * E = \frac{1.923 \sqrt[3]{nBits \cdot log_e(2)} + * \cdot(log_e(nBits \cdot log_e(2))^{2/3} - 4.69}{log_e(2)} + * The two cube roots are merged together here. + */ +static uint16_t rsa_compute_security_bits(int n) +{ + uint64_t x; + uint32_t lx; + uint16_t y; + + /* Look for common values as listed in SP 800-56B rev 2 Appendix D */ + switch (n) { + case 2048: + return 112; + case 3072: + return 128; + case 4096: + return 152; + case 6144: + return 176; + case 8192: + return 200; + } + /* + * The first incorrect result (i.e. not accurate or off by one low) occurs + * for n = 699668. The true value here is 1200. Instead of using this n + * as the check threshold, the smallest n such that the correct result is + * 1200 is used instead. + */ + if (n >= 687737) + return 1200; + if (n < 8) + return 0; + + x = n * (uint64_t)log_2; + lx = ilog_e(x); + y = (uint16_t)((mul2(c1_923, icbrt64(mul2(mul2(x, lx), lx))) - c4_690) + / log_2); + return (y + 4) & ~7; +} + int RSA_security_bits(const RSA *rsa) { int bits = BN_num_bits(rsa->n); @@ -174,7 +301,7 @@ int RSA_security_bits(const RSA *rsa) if (ex_primes <= 0 || (ex_primes + 2) > rsa_multip_cap(bits)) return 0; } - return BN_security_bits(bits, -1); + return rsa_compute_security_bits(bits); } int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) diff --git a/test/rsa_test.c b/test/rsa_test.c index 2ad4de4734..25709e418c 100644 --- a/test/rsa_test.c +++ b/test/rsa_test.c @@ -329,10 +329,70 @@ err: return ret; } +static const struct { + int bits; + unsigned int r; +} rsa_security_bits_cases[] = { + /* NIST SP 800-56B rev 2 (draft) Appendix D Table 5 */ + { 2048, 112 }, + { 3072, 128 }, + { 4096, 152 }, + { 6144, 176 }, + { 8192, 200 }, + /* Older values */ + { 256, 40 }, + { 512, 56 }, + { 1024, 80 }, + /* Slightly different value to the 256 that NIST lists in their tables */ + { 15360, 264 }, + /* Some other values */ + { 8888, 208 }, + { 2468, 120 }, + { 13456, 248 } +}; + +static int test_rsa_security_bit(int n) +{ + static const unsigned char vals[8] = { + 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 + }; + RSA *key = RSA_new(); + const int bits = rsa_security_bits_cases[n].bits; + const int result = rsa_security_bits_cases[n].r; + const int bytes = (bits + 7) / 8; + int r = 0; + unsigned char num[2000]; + + if (!TEST_ptr(key) || !TEST_int_le(bytes, (int)sizeof(num))) + goto err; + + /* + * It is necessary to set the RSA key in order to ask for the strength. + * A BN of an appropriate size is created, in general it won't have the + * properties necessary for RSA to function. This is okay here since + * the RSA key is never used. + */ + memset(num, vals[bits % 8], bytes); + + /* + * The 'e' parameter is set to the same value as 'n'. This saves having + * an extra BN to hold a sensible value for 'e'. This is safe since the + * RSA key is not used. The 'd' parameter can be NULL safely. + */ + if (TEST_true(RSA_set0_key(key, BN_bin2bn(num, bytes, NULL), + BN_bin2bn(num, bytes, NULL), NULL)) + && TEST_uint_eq(RSA_security_bits(key), result)) + r = 1; +err: + RSA_free(key); + return r; +} + int setup_tests(void) { ADD_ALL_TESTS(test_rsa_pkcs1, 3); ADD_ALL_TESTS(test_rsa_oaep, 3); + ADD_ALL_TESTS(test_rsa_security_bit, OSSL_NELEM(rsa_security_bits_cases)); return 1; } #endif