From b3da780ed5fbfc0cc542b12b51fc2a7b8445f7f3 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Sat, 18 Jan 2025 08:24:34 -0500 Subject: [PATCH] When computing modular inverses distingush which case we are in There are several different scenarios where we need to compute a modular inverse * A secret value modulo a secret prime (eg RSA-CRT setup) * A (potentially) secret value modulo a public prime (eg inversion modulo an DL group order) * A secret value modulo a public value that is not prime (RSA blinding setup) * RSA secret exponent calculation, where e and phi(n) are relatively prime but phi(n) is not prime, and e is public * The general case where we have no idea as to the structure of the modulus, and we don't know if it is public so must treat it as secret Previously all of these went through `inverse_mod` which prevented any possible optimizations based on cases. Add a new (internal) header which directly exposes the various cases and apply them within the codebase. The only new algorithm implemented (so far) is Arazi's algorithm for inversion of a prime modulo a non-prime. Specifically this is a special case for computing the RSA secret exponent when `e=65537`. (Would also work for `e=3`/`e=17`/etc but this is not implemented) --- src/cli/math.cpp | 7 +- src/cli/timing_tests.cpp | 3 +- src/lib/ffi/ffi_mp.cpp | 5 +- src/lib/math/bigint/divide.cpp | 24 +++ src/lib/math/bigint/divide.h | 13 ++ src/lib/math/numbertheory/info.txt | 1 + src/lib/math/numbertheory/mod_inv.cpp | 170 ++++++++++++++---- src/lib/math/numbertheory/mod_inv.h | 116 ++++++++++++ src/lib/math/numbertheory/monty.cpp | 10 -- src/lib/math/numbertheory/monty.h | 4 - src/lib/prov/pkcs11/p11_rsa.cpp | 3 +- src/lib/pubkey/dh/dh.cpp | 1 - src/lib/pubkey/dl_group/dl_group.cpp | 5 +- src/lib/pubkey/dsa/dsa.cpp | 2 +- src/lib/pubkey/ec_group/ec_inner_data.h | 3 +- .../ec_group/legacy_ec_point/ec_point.cpp | 2 +- src/lib/pubkey/rsa/rsa.cpp | 13 +- src/scripts/test_cli.py | 2 +- src/tests/test_bigint.cpp | 80 +++++++-- 19 files changed, 387 insertions(+), 77 deletions(-) create mode 100644 src/lib/math/numbertheory/mod_inv.h diff --git a/src/cli/math.cpp b/src/cli/math.cpp index 77770ec53bc..ea66208b5f5 100644 --- a/src/cli/math.cpp +++ b/src/cli/math.cpp @@ -9,6 +9,7 @@ #if defined(BOTAN_HAS_NUMBERTHEORY) #include + #include #include #include @@ -26,7 +27,11 @@ class Modular_Inverse final : public Command { const Botan::BigInt n(get_arg("n")); const Botan::BigInt mod(get_arg("mod")); - output() << Botan::inverse_mod(n, mod) << "\n"; + if(auto inv = Botan::inverse_modulo_general(n, mod)) { + output() << *inv << "\n"; + } else { + output() << "No modular inverse exists\n"; + } } }; diff --git a/src/cli/timing_tests.cpp b/src/cli/timing_tests.cpp index e735628dee5..e4c0d134d59 100644 --- a/src/cli/timing_tests.cpp +++ b/src/cli/timing_tests.cpp @@ -35,6 +35,7 @@ #if defined(BOTAN_HAS_NUMBERTHEORY) #include + #include #endif #if defined(BOTAN_HAS_ECC_GROUP) @@ -360,7 +361,7 @@ uint64_t Invmod_Timing_Test::measure_critical_function(const std::vector #include #include +#include extern "C" { @@ -212,7 +213,9 @@ int botan_mp_rshift(botan_mp_t out, const botan_mp_t in, size_t shift) { } int botan_mp_mod_inverse(botan_mp_t out, const botan_mp_t in, const botan_mp_t modulus) { - return BOTAN_FFI_VISIT(out, [=](auto& o) { o = Botan::inverse_mod(safe_get(in), safe_get(modulus)); }); + return BOTAN_FFI_VISIT(out, [=](auto& o) { + o = Botan::inverse_modulo_general(safe_get(in), safe_get(modulus)).value_or(Botan::BigInt::zero()); + }); } int botan_mp_mod_mul(botan_mp_t out, const botan_mp_t x, const botan_mp_t y, const botan_mp_t modulus) { diff --git a/src/lib/math/bigint/divide.cpp b/src/lib/math/bigint/divide.cpp index d4c39c1c763..1314d387aec 100644 --- a/src/lib/math/bigint/divide.cpp +++ b/src/lib/math/bigint/divide.cpp @@ -114,6 +114,30 @@ void ct_divide_word(const BigInt& x, word y, BigInt& q_out, word& r_out) { q_out = q; } +word ct_mod_word(const BigInt& x, word y) { + BOTAN_ARG_CHECK(x.is_positive(), "The argument x must be positive"); + BOTAN_ARG_CHECK(y != 0, "Cannot divide by zero"); + + const size_t x_bits = x.bits(); + + word r = 0; + + for(size_t i = 0; i != x_bits; ++i) { + const size_t b = x_bits - 1 - i; + const bool x_b = x.get_bit(b); + + const auto r_carry = CT::Mask::expand_top_bit(r); + + r *= 2; + r += x_b; + + const auto r_gte_y = CT::Mask::is_gte(r, y) | r_carry; + r = r_gte_y.select(r - y, r); + } + + return r; +} + BigInt ct_modulo(const BigInt& x, const BigInt& y) { if(y.is_negative() || y.is_zero()) { throw Invalid_Argument("ct_modulo requires y > 0"); diff --git a/src/lib/math/bigint/divide.h b/src/lib/math/bigint/divide.h index e7f258aa4a2..8175625003d 100644 --- a/src/lib/math/bigint/divide.h +++ b/src/lib/math/bigint/divide.h @@ -66,6 +66,19 @@ inline BigInt ct_divide(const BigInt& x, const BigInt& y) { BOTAN_TEST_API void ct_divide_word(const BigInt& x, word y, BigInt& q, word& r); +/** +* BigInt word modulo, const time variant +* +* This runs with control flow independent of the values of x/y. +* Warning: the loop bounds still leaks the size of x. +* +* @param x a positive integer +* @param y a non-zero word +* @return r the remainder of x divided by y +*/ +BOTAN_TEST_API +word ct_mod_word(const BigInt& x, word y); + /** * BigInt modulo, const time variant * diff --git a/src/lib/math/numbertheory/info.txt b/src/lib/math/numbertheory/info.txt index f6b8b5afe6b..89840225ce2 100644 --- a/src/lib/math/numbertheory/info.txt +++ b/src/lib/math/numbertheory/info.txt @@ -13,6 +13,7 @@ reducer.h +mod_inv.h monty.h monty_exp.h primality.h diff --git a/src/lib/math/numbertheory/mod_inv.cpp b/src/lib/math/numbertheory/mod_inv.cpp index 3328718dfe0..fcb9d2e92f0 100644 --- a/src/lib/math/numbertheory/mod_inv.cpp +++ b/src/lib/math/numbertheory/mod_inv.cpp @@ -1,11 +1,12 @@ /* -* (C) 1999-2011,2016,2018,2019,2020 Jack Lloyd +* (C) 1999-2011,2016,2018,2019,2020,2025 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ -#include +#include +#include #include #include #include @@ -17,10 +18,10 @@ namespace { BigInt inverse_mod_odd_modulus(const BigInt& n, const BigInt& mod) { // Caller should assure these preconditions: - BOTAN_DEBUG_ASSERT(n.is_positive()); - BOTAN_DEBUG_ASSERT(mod.is_positive()); - BOTAN_DEBUG_ASSERT(n < mod); - BOTAN_DEBUG_ASSERT(mod >= 3 && mod.is_odd()); + BOTAN_ASSERT_NOMSG(n.is_positive()); + BOTAN_ASSERT_NOMSG(mod.is_positive()); + BOTAN_ASSERT_NOMSG(n < mod); + BOTAN_ASSERT_NOMSG(mod >= 3 && mod.is_odd()); /* This uses a modular inversion algorithm designed by Niels Möller @@ -98,10 +99,7 @@ BigInt inverse_mod_odd_modulus(const BigInt& n, const BigInt& mod) { bigint_cnd_add(odd_u, u_w, mp1o2, mod_words); } - auto a_is_0 = CT::Mask::set(); - for(size_t i = 0; i != mod_words; ++i) { - a_is_0 &= CT::Mask::is_zero(a_w[i]); - } + const auto a_is_0 = CT::all_zeros(a_w, mod_words); auto b_is_1 = CT::Mask::is_equal(b_w[0], 1); for(size_t i = 1; i != mod_words; ++i) { @@ -176,32 +174,28 @@ BigInt inverse_mod_pow2(const BigInt& a1, size_t k) { } // namespace -BigInt inverse_mod(const BigInt& n, const BigInt& mod) { - if(mod.is_zero()) { - throw Invalid_Argument("inverse_mod modulus cannot be zero"); - } - if(mod.is_negative() || n.is_negative()) { - throw Invalid_Argument("inverse_mod: arguments must be non-negative"); - } - if(n.is_zero() || (n.is_even() && mod.is_even())) { - return BigInt::zero(); +std::optional inverse_modulo_general(const BigInt& x, const BigInt& mod) { + BOTAN_ARG_CHECK(x > 0, "x must be greater than zero"); + BOTAN_ARG_CHECK(mod > 0, "mod must be greater than zero"); + BOTAN_ARG_CHECK(x < mod, "x must be less than m"); + + // Easy case where gcd > 1 so no inverse exists + if(x.is_even() && mod.is_even()) { + return std::nullopt; } if(mod.is_odd()) { - /* - Fastpath for common case. This leaks if n is greater than mod or - not, but we don't guarantee const time behavior in that case. - */ - if(n < mod) { - return inverse_mod_odd_modulus(n, mod); + BigInt z = inverse_mod_odd_modulus(x, mod); + if(z.is_zero()) { + return std::nullopt; } else { - return inverse_mod_odd_modulus(ct_modulo(n, mod), mod); + return z; } } - // If n is even and mod is even we already returned 0 - // If n is even and mod is odd we jumped directly to odd-modulus algo - BOTAN_DEBUG_ASSERT(n.is_odd()); + // If x is even and mod is even we already returned 0 + // If x is even and mod is odd we jumped directly to odd-modulus algo + BOTAN_ASSERT_NOMSG(x.is_odd()); const size_t mod_lz = low_zero_bits(mod); BOTAN_ASSERT_NOMSG(mod_lz > 0); @@ -210,7 +204,12 @@ BigInt inverse_mod(const BigInt& n, const BigInt& mod) { if(mod_lz == mod_bits - 1) { // In this case we are performing an inversion modulo 2^k - return inverse_mod_pow2(n, mod_lz); + auto z = inverse_mod_pow2(x, mod_lz); + if(z.is_zero()) { + return std::nullopt; + } else { + return z; + } } if(mod_lz == 1) { @@ -229,12 +228,12 @@ BigInt inverse_mod(const BigInt& n, const BigInt& mod) { */ const BigInt o = mod >> 1; - const BigInt n_redc = ct_modulo(n, o); + const BigInt n_redc = ct_modulo(x, o); const BigInt inv_o = inverse_mod_odd_modulus(n_redc, o); // No modular inverse in this case: if(inv_o == 0) { - return BigInt::zero(); + return std::nullopt; } BigInt h = inv_o; @@ -250,19 +249,22 @@ BigInt inverse_mod(const BigInt& n, const BigInt& mod) { */ const BigInt o = mod >> mod_lz; - const BigInt n_redc = ct_modulo(n, o); - const BigInt inv_o = inverse_mod_odd_modulus(n_redc, o); - const BigInt inv_2k = inverse_mod_pow2(n, mod_lz); + const BigInt x_mod_o = ct_modulo(x, o); + const BigInt inv_o = inverse_mod_odd_modulus(x_mod_o, o); + const BigInt inv_2k = inverse_mod_pow2(x, mod_lz); // No modular inverse in this case: if(inv_o == 0 || inv_2k == 0) { - return BigInt::zero(); + return std::nullopt; } const BigInt m2k = BigInt::power_of_2(mod_lz); // Compute the CRT parameter const BigInt c = inverse_mod_pow2(o, mod_lz); + // This should never happen; o is odd so gcd is 1 and inverse mod 2^k exists + BOTAN_ASSERT_NOMSG(!c.is_zero()); + // Compute h = c*(inv_2k-inv_o) mod 2^k BigInt h = c * (inv_2k - inv_o); const bool h_neg = h.is_negative(); @@ -277,4 +279,100 @@ BigInt inverse_mod(const BigInt& n, const BigInt& mod) { return h; } +BigInt inverse_modulo_secret_prime(const BigInt& x, const BigInt& p) { + BOTAN_ARG_CHECK(x.is_positive() && p.is_positive(), "Parameters must be positive"); + BOTAN_ARG_CHECK(x < p, "x must be less than p"); + BOTAN_ARG_CHECK(p.is_odd() and p > 1, "Primes are odd integers greater than 1"); + + // TODO possibly use FLT, or the algorithm presented for this case in + // Handbook of Elliptic and Hyperelliptic Curve Cryptography + + return inverse_mod_odd_modulus(x, p); +} + +BigInt inverse_modulo_public_prime(const BigInt& x, const BigInt& p) { + return inverse_modulo_secret_prime(x, p); +} + +BigInt inverse_modulo_rsa_public_modulus(const BigInt& x, const BigInt& n) { + BOTAN_ARG_CHECK(n.is_positive() && n.is_odd(), "RSA public modulus must be odd and positive"); + BOTAN_ARG_CHECK(x.is_positive() and x < n, "Input must be positive and less than RSA modulus"); + BigInt z = inverse_mod_odd_modulus(x, n); + BOTAN_ASSERT(!z.is_zero(), "Accidentally factored the public modulus"); // whoops + return z; +} + +BigInt compute_rsa_secret_exponent(const BigInt& e, const BigInt& phi_n, const BigInt& p, const BigInt& q) { + /* + * Both p - 1 and q - 1 are chosen to be relatively prime to e. Thus + * phi(n), the least common multiple of p - 1 and q - 1, is also + * relatively prime to e. + */ + BOTAN_DEBUG_ASSERT(gcd(e, phi_n) == 1); + + if(e == 65537) { + /* + Arazi's algorithm for inversion of prime x modulo a non-prime + + "GCD-Free Algorithms for Computing Modular Inverses" + Marc Joye and Pascal Paillier, CHES 2003 (LNCS 2779) + https://marcjoye.github.io/papers/JP03gcdfree.pdf + + This could be extended to cover other cases such as e=3 or e=17 but + these days 65537 is the standard RSA public exponent + */ + + const word phi_mod_e = ct_mod_word(phi_n, 65537); + + // Compute phi^-1 mod e using FLT + + const word inv_phi_mod_e = [&]() { + // Need 64-bit here as accum*accum exceeds 32-bit if accum=0x10000 + uint64_t accum = 1; + for(size_t i = 0; i != 16; ++i) { + accum = (accum * accum) % 65537; + accum = (accum * phi_mod_e) % 65537; + } + return static_cast(accum); + }(); + + BOTAN_DEBUG_ASSERT((inv_phi_mod_e * phi_mod_e) % 65537 == 1); + + const word neg_inv_phi_mod_e = (65537 - inv_phi_mod_e); + + BigInt inv_mod_phi; + word rem_mod_phi; + ct_divide_word((phi_n * neg_inv_phi_mod_e) + 1, 65537, inv_mod_phi, rem_mod_phi); + BOTAN_UNUSED(rem_mod_phi); + return inv_mod_phi; + } else { + // TODO possibly do something else taking advantage of the special structure here + + BOTAN_UNUSED(p, q); + if(auto d = inverse_modulo_general(e, phi_n)) { + return *d; + } else { + throw Internal_Error("Failed to compute RSA secret exponent"); + } + } +} + +BigInt inverse_mod(const BigInt& n, const BigInt& mod) { + if(mod.is_zero()) { + throw Invalid_Argument("inverse_mod modulus cannot be zero"); + } + if(mod.is_negative() || n.is_negative()) { + throw Invalid_Argument("inverse_mod: arguments must be non-negative"); + } + if(n.is_zero() || (n.is_even() && mod.is_even())) { + return BigInt::zero(); + } + + if(n >= mod) { + return inverse_mod(ct_modulo(n, mod), mod); + } + + return inverse_modulo_general(n, mod).value_or(BigInt::zero()); +} + } // namespace Botan diff --git a/src/lib/math/numbertheory/mod_inv.h b/src/lib/math/numbertheory/mod_inv.h new file mode 100644 index 00000000000..709999ede6a --- /dev/null +++ b/src/lib/math/numbertheory/mod_inv.h @@ -0,0 +1,116 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_MOD_INV_H_ +#define BOTAN_MOD_INV_H_ + +#include +#include + +namespace Botan { + +/** +* Compute the inverse of x modulo some integer m +* +* Returns nullopt if no such integer exists eg if gcd(x, m) > 1 +* +* This algorithm is const time with respect to x, aside from its +* length. It also avoids leaking information about the modulus m, +* except that it does leak which of 3 categories the modulus is in: +* +* - An odd integer +* - A power of 2 +* - Some even number not a power of 2 +* +* And if the modulus is even, it leaks the power of 2 which divides +* the modulus. +* +* @param x a positive integer less than m +* @param m a positive integer +* +* Throws Invalid_Argument if x or m are negative +*/ +std::optional BOTAN_TEST_API inverse_modulo_general(const BigInt& x, const BigInt& m); + +/** +* Compute the inverse of x modulo a secret prime p +* +* This algorithm is constant time with respect to x and p, aside from +* leaking the length of p. (In particular it should not leak the +* length of x, if x is shorter) +* +* @param x a positive integer less than p +* @param p an odd prime +* @return y such that (x*y) % p == 1 +* +* This always returns a result since any integer in [1,p) +* has an inverse modulo a prime p. +* +* This function assumes as a precondition that p truly is prime; the +* results may not be correct if this does not hold. +* +* Throws Invalid_Argument if x is less than or equal to zero, +* or if p is even or less than 3. +*/ +BigInt BOTAN_TEST_API inverse_modulo_secret_prime(const BigInt& x, const BigInt& p); + +/** +* Compute the inverse of x modulo a public prime p +* +* This algorithm is constant time with respect to x. The prime +* p is assumed to be public. +* +* @param x a positive integer less than p +* @param p an odd prime +* @return y such that (x*y) % p == 1 +* +* This always returns a result since any integer in [1,p) +* has an inverse modulo a prime p. +* +* This function assumes as a precondition that p truly is prime; the +* results may not be correct if this does not hold. +* +* Throws Invalid_Argument if x is less than or equal to zero, +* or if p is even or less than 3. +*/ +BigInt BOTAN_TEST_API inverse_modulo_public_prime(const BigInt& x, const BigInt& p); + +/** +* Compute the inverse of x modulo a public RSA modulus n +* +* This algorithm is constant time with respect to x. The RSA +* modulus is assumed to be public. +* +* @param x a positive integer less than n +* @param n a RSA public modulus +* @return y such that (x*y) % n == 1 +* +* This always returns a result since any integer in [1,n) has an inverse modulo +* a RSA public modulus n, unless you have happened to guess one of the factors +* at random. In the unlikely event of this occuring, Internal_Error will be thrown. +*/ +BigInt inverse_modulo_rsa_public_modulus(const BigInt& x, const BigInt& n); + +/** +* Compute the RSA private exponent d +* +* This algorithm is constant time with respect to phi_n, p, and q, +* aside from leaking their lengths. It may leak the public exponent e. +* +* @param e the public exponent +* @param phi_n is lcm(p-1, q-1) +* @param p is the first secret prime +* @param q is the second secret prime +* @return d inverse of e modulo phi_n +*/ +BigInt BOTAN_TEST_API compute_rsa_secret_exponent(const BigInt& e, + const BigInt& phi_n, + const BigInt& p, + const BigInt& q); + +} // namespace Botan + +#endif diff --git a/src/lib/math/numbertheory/monty.cpp b/src/lib/math/numbertheory/monty.cpp index cb2489846dd..e831c42916a 100644 --- a/src/lib/math/numbertheory/monty.cpp +++ b/src/lib/math/numbertheory/monty.cpp @@ -48,11 +48,6 @@ Montgomery_Params::Montgomery_Params(const BigInt& p) { m_r3 = mod_p.multiply(m_r1, m_r2); } -BigInt Montgomery_Params::inv_mod_p(const BigInt& x, secure_vector& ws) const { - // TODO use Montgomery inverse here? - return this->mul(inverse_mod(x, p()), this->R3(), ws); -} - BigInt Montgomery_Params::redc(const BigInt& x, secure_vector& ws) const { const size_t output_size = m_p_words + 1; @@ -435,11 +430,6 @@ Montgomery_Int Montgomery_Int::cube(secure_vector& ws) const { return Montgomery_Int(m_params, m_params->sqr(m_v, ws), false); } -Montgomery_Int Montgomery_Int::multiplicative_inverse() const { - secure_vector ws; - return Montgomery_Int(m_params, m_params->inv_mod_p(m_v, ws), false); -} - Montgomery_Int Montgomery_Int::additive_inverse() const { return Montgomery_Int(m_params, m_params->p()) - (*this); } diff --git a/src/lib/math/numbertheory/monty.h b/src/lib/math/numbertheory/monty.h index 7e23305c9d3..bd3186410bc 100644 --- a/src/lib/math/numbertheory/monty.h +++ b/src/lib/math/numbertheory/monty.h @@ -109,8 +109,6 @@ class BOTAN_TEST_API Montgomery_Int final { Montgomery_Int& square_this_n_times(secure_vector& ws, size_t n); - Montgomery_Int multiplicative_inverse() const; - Montgomery_Int additive_inverse() const; Montgomery_Int& mul_by_2(secure_vector& ws); @@ -187,8 +185,6 @@ class BOTAN_TEST_API Montgomery_Params final { void square_this(BigInt& x, secure_vector& ws) const; - BigInt inv_mod_p(const BigInt& x, secure_vector& ws) const; - private: BigInt m_p; BigInt m_r1; diff --git a/src/lib/prov/pkcs11/p11_rsa.cpp b/src/lib/prov/pkcs11/p11_rsa.cpp index 1c77ced553e..f6eae1e0819 100644 --- a/src/lib/prov/pkcs11/p11_rsa.cpp +++ b/src/lib/prov/pkcs11/p11_rsa.cpp @@ -16,6 +16,7 @@ #include #include #include + #include #include namespace Botan::PKCS11 { @@ -118,7 +119,7 @@ class PKCS11_RSA_Decryption_Operation final : public PK_Ops::Decryption { m_key.get_n(), rng, [this](const BigInt& k) { return power_mod(k, m_key.get_e(), m_key.get_n()); }, - [this](const BigInt& k) { return inverse_mod(k, m_key.get_n()); }) { + [this](const BigInt& k) { return inverse_modulo_rsa_public_modulus(k, m_key.get_n()); }) { m_bits = m_key.get_n().bits() - 1; } diff --git a/src/lib/pubkey/dh/dh.cpp b/src/lib/pubkey/dh/dh.cpp index f293ff0ec85..380d5681e64 100644 --- a/src/lib/pubkey/dh/dh.cpp +++ b/src/lib/pubkey/dh/dh.cpp @@ -125,7 +125,6 @@ class DH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF { BigInt powermod_x_p(const BigInt& v) const { return group().power_b_p(v, m_key->private_key(), m_key_bits); } std::shared_ptr m_key; - std::shared_ptr m_monty_p; const size_t m_key_bits; Blinder m_blinder; }; diff --git a/src/lib/pubkey/dl_group/dl_group.cpp b/src/lib/pubkey/dl_group/dl_group.cpp index 87fb524432a..0122ad7dbc9 100644 --- a/src/lib/pubkey/dl_group/dl_group.cpp +++ b/src/lib/pubkey/dl_group/dl_group.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -479,7 +480,7 @@ size_t DL_Group::exponent_bits() const { BigInt DL_Group::inverse_mod_p(const BigInt& x) const { // precompute?? - return inverse_mod(x, get_p()); + return inverse_modulo_public_prime(x, get_p()); } BigInt DL_Group::mod_p(const BigInt& x) const { @@ -493,7 +494,7 @@ BigInt DL_Group::multiply_mod_p(const BigInt& x, const BigInt& y) const { BigInt DL_Group::inverse_mod_q(const BigInt& x) const { data().assert_q_is_set("inverse_mod_q"); // precompute?? - return inverse_mod(x, get_q()); + return inverse_modulo_public_prime(x, get_q()); } BigInt DL_Group::mod_q(const BigInt& x) const { diff --git a/src/lib/pubkey/dsa/dsa.cpp b/src/lib/pubkey/dsa/dsa.cpp index b43a2e718e5..a6b91763bf7 100644 --- a/src/lib/pubkey/dsa/dsa.cpp +++ b/src/lib/pubkey/dsa/dsa.cpp @@ -167,7 +167,7 @@ std::vector DSA_Signature_Operation::raw_sign(std::span const BigInt k = BigInt::random_integer(rng, 1, q); #endif - const BigInt k_inv = group.inverse_mod_q(m_b * k) * m_b; + const BigInt k_inv = group.inverse_mod_q(group.mod_q(m_b * k)) * m_b; /* * It may not be strictly necessary for the reduction (g^k mod p) mod q to be diff --git a/src/lib/pubkey/ec_group/ec_inner_data.h b/src/lib/pubkey/ec_group/ec_inner_data.h index d861ac5d116..09fe42f12b4 100644 --- a/src/lib/pubkey/ec_group/ec_inner_data.h +++ b/src/lib/pubkey/ec_group/ec_inner_data.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -204,7 +205,7 @@ class EC_Group_Data final : public std::enable_shared_from_this { return m_mod_order.multiply(m_mod_order.multiply(x, y), z); } - BigInt inverse_mod_order(const BigInt& x) const { return inverse_mod(x, m_order); } + BigInt inverse_mod_order(const BigInt& x) const { return inverse_modulo_public_prime(x, m_order); } EC_Group_Source source() const { return m_source; } diff --git a/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp b/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp index a96c5008118..efcc0c47f6e 100644 --- a/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp +++ b/src/lib/pubkey/ec_group/legacy_ec_point/ec_point.cpp @@ -85,7 +85,7 @@ BigInt fe_sqr(const EC_Group_Data& group, const BigInt& x, secure_vector& } BigInt invert_element(const EC_Group_Data& group, const BigInt& x, secure_vector& ws) { - return group.monty().inv_mod_p(x, ws); + return group.monty().mul(inverse_modulo_public_prime(x, group.p()), group.monty().R3(), ws); } size_t monty_ws_size(const EC_Group_Data& group) { diff --git a/src/lib/pubkey/rsa/rsa.cpp b/src/lib/pubkey/rsa/rsa.cpp index ad62c9a12f0..7a86a95205a 100644 --- a/src/lib/pubkey/rsa/rsa.cpp +++ b/src/lib/pubkey/rsa/rsa.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -288,12 +289,12 @@ RSA_PrivateKey::RSA_PrivateKey( if(d.is_zero()) { const BigInt phi_n = lcm(p_minus_1, q_minus_1); - d = inverse_mod(e, phi_n); + d = compute_rsa_secret_exponent(e, phi_n, p, q); } BigInt d1 = ct_modulo(d, p_minus_1); BigInt d2 = ct_modulo(d, q_minus_1); - BigInt c = inverse_mod(q, p); + BigInt c = inverse_modulo_secret_prime(ct_modulo(q, p), p); RSA_PublicKey::init(std::move(n), std::move(e)); @@ -348,10 +349,10 @@ RSA_PrivateKey::RSA_PrivateKey(RandomNumberGenerator& rng, size_t bits, size_t e // This is guaranteed because p,q == 3 mod 4 BOTAN_DEBUG_ASSERT(low_zero_bits(phi_n) == 1); - BigInt d = inverse_mod(e, phi_n); + BigInt d = compute_rsa_secret_exponent(e, phi_n, p, q); BigInt d1 = ct_modulo(d, p_minus_1); BigInt d2 = ct_modulo(d, q_minus_1); - BigInt c = inverse_mod(q, p); + BigInt c = inverse_modulo_secret_prime(ct_modulo(q, p), p); RSA_PublicKey::init(std::move(n), std::move(e)); @@ -406,7 +407,7 @@ bool RSA_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { if(get_d2() != ct_modulo(get_d(), get_q() - 1)) { return false; } - if(get_c() != inverse_mod(get_q(), get_p())) { + if(get_c() != inverse_modulo_secret_prime(ct_modulo(get_q(), get_p()), get_p())) { return false; } @@ -448,7 +449,7 @@ class RSA_Private_Operation { m_public->get_n(), rng, [this](const BigInt& k) { return m_public->public_op(k); }, - [this](const BigInt& k) { return inverse_mod(k, m_public->get_n()); }), + [this](const BigInt& k) { return inverse_modulo_rsa_public_modulus(k, m_public->get_n()); }), m_blinding_bits(64), m_max_d1_bits(m_private->p_bits() + m_blinding_bits), m_max_d2_bits(m_private->q_bits() + m_blinding_bits) {} diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index 2312e5cee9c..cf5ecac30b0 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -385,7 +385,7 @@ def cli_factor_tests(_tmp_dir): def cli_mod_inverse_tests(_tmp_dir): test_cli("mod_inverse", "97 802", "339") - test_cli("mod_inverse", "98 802", "0") + test_cli("mod_inverse", "98 802", "No modular inverse exists") def cli_base64_tests(_tmp_dir): test_cli("base64_enc", "-", "YmVlcyE=", "bees!") diff --git a/src/tests/test_bigint.cpp b/src/tests/test_bigint.cpp index c8d4ddf35fb..a8b5c1430fd 100644 --- a/src/tests/test_bigint.cpp +++ b/src/tests/test_bigint.cpp @@ -15,6 +15,7 @@ #include #include #include + #include #include #include #include @@ -666,19 +667,26 @@ class BigInt_InvMod_Test final : public Text_Based_Test { const Botan::BigInt mod = vars.get_req_bn("Modulus"); const Botan::BigInt expected = vars.get_req_bn("Output"); - const Botan::BigInt a_inv = Botan::inverse_mod(a, mod); + result.test_eq("inverse_mod", Botan::inverse_mod(a, mod), expected); - result.test_eq("inverse_mod", a_inv, expected); + if(a < mod && a > 0 && a < mod) { + auto g = Botan::inverse_modulo_general(a, mod); + if(g.has_value()) { + result.test_eq("inverse_modulo_general", g.value(), expected); + result.test_eq("inverse works", ((g.value() * a) % mod), BigInt::one()); + } else { + result.confirm("inverse_modulo_general", expected.is_zero()); + } - if(a_inv > 1) { - result.test_eq("inverse ok", (a * a_inv) % mod, 1); - } - /* - else if((a % mod) > 0) - { - result.confirm("no inverse with gcd > 1", gcd(a, mod) > 1); + if(Botan::is_prime(mod, rng()) && mod != 2) { + BOTAN_ASSERT_NOMSG(expected > 0); + result.test_eq("inverse_modulo_secret_prime", Botan::inverse_modulo_secret_prime(a, mod), expected); + + // Currently inverse_modulo_public_prime just forwards to inverse_modulo_secret_prime so we'll + // skip this test until a specific algorithm is implemented for this function + //result.test_eq("inverse_modulo_public_prime", Botan::inverse_modulo_public_prime(a, mod), expected); } - */ + } return result; } @@ -744,6 +752,58 @@ class Lucas_Primality_Test final : public Test { BOTAN_REGISTER_TEST("math", "bn_lucas", Lucas_Primality_Test); +class RSA_Compute_Exp_Test : public Test { + public: + std::vector run() override { + const size_t iter = 4000; + + Test::Result result("RSA compute exponent"); + + const auto e = Botan::BigInt::from_u64(65537); + + /* + * Rather than create a fresh p/q for each iteration this test creates + * a pool of primes then selects 2 at random as p/q + */ + + const auto random_primes = [&]() { + std::vector rp; + for(size_t i = 0; i != iter / 10; ++i) { + size_t bits = (128 + (i % 1024)) % 4096; + auto p = Botan::random_prime(rng(), bits); + if(gcd(p - 1, e) == 1) { + rp.push_back(p); + } + } + return rp; + }(); + + for(size_t i = 0; i != iter; ++i) { + const size_t p_idx = random_index(rng(), random_primes.size()); + const size_t q_idx = random_index(rng(), random_primes.size()); + + if(p_idx == q_idx) { + continue; + } + + const auto& p = random_primes[p_idx]; + const auto& q = random_primes[q_idx]; + + auto phi_n = lcm(p - 1, q - 1); + + auto d = Botan::compute_rsa_secret_exponent(e, phi_n, p, q); + + auto one = (e * d) % phi_n; + + result.test_eq("compute_rsa_secret_exponent returned inverse", (e * d) % phi_n, Botan::BigInt::one()); + } + + return {result}; + } +}; + +BOTAN_REGISTER_TEST("math", "rsa_compute_d", RSA_Compute_Exp_Test); + class DSA_ParamGen_Test final : public Text_Based_Test { public: DSA_ParamGen_Test() : Text_Based_Test("bn/dsa_gen.vec", "P,Q,Counter,Seed") {}