Commit 97b0b713 authored by Pauli's avatar Pauli
Browse files

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: default avatarMatt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/7352)
parent 5b4cb385
Loading
Loading
Loading
Loading
+128 −1
Original line number Diff line number Diff line
@@ -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)
+60 −0
Original line number Diff line number Diff line
@@ -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