From 58a996217c581f3918835ca8b4a22a989454a487 Mon Sep 17 00:00:00 2001 From: Fabian Albert Date: Wed, 24 Jan 2024 16:29:16 +0100 Subject: [PATCH] Generic ct division for side-channel prevention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René Meusel --- .../kyber/kyber_common/kyber_structures.h | 71 ++++++++++++++----- src/tests/test_kyber.cpp | 22 ++++++ 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_structures.h b/src/lib/pubkey/kyber/kyber_common/kyber_structures.h index 8675db0542b..bc04f6b1774 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_structures.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_structures.h @@ -7,7 +7,7 @@ * (C) 2021-2024 Jack Lloyd * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH - * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * (C) 2024 René Meusel and Fabian Albert, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -28,6 +28,50 @@ namespace Botan { +namespace detail { + +/** + * Helper to implement integer division by a divisor @p d > 1 known at compile + * time, that avoids the emission of potential variable time operations by the + * compiler. Note that this will work for numerators that must adhere to + * n < 2^W. @p W is given as a template parameter. + * + * Based on "Hacker's Delight" (Second Edition) by Henry S. Warren, Jr. Chapter + * 10-9 "Unsigned Division by Divisors >= 1" + */ +template + requires(d >= 2) && (d < (1 << W)) && (W <= 32) +consteval std::pair calculate_p_and_m() { + constexpr uint8_t p = [] { + constexpr auto nc = static_cast((uint64_t(1) << W) - ((uint64_t(1) << W) % d) - 1); + for(uint8_t p_candidate = W; p_candidate <= 2 * W; ++p_candidate) { + const uint64_t pp2 = (uint64_t(1) << p_candidate); + if(pp2 > nc * (d - 1 - ((pp2 - 1) % d))) { + return p_candidate; + } + } + + BOTAN_ASSERT_UNREACHABLE(); + }(); + + static_assert(p < 64); + constexpr uint64_t pp2 = (uint64_t(1) << p); + constexpr uint64_t m = (pp2 + d - 1 - ((pp2 - 1) % d)) / d; + static_assert(m <= std::numeric_limits::max()); + return {p, static_cast(m)}; +} + +} // namespace detail + +template +uint32_t ct_divide_by(uint32_t n) { + // Get constants p and m for a division by d for numerators < 2^18. + constexpr uint8_t W = 18; + BOTAN_DEBUG_ASSERT(n < (1 << W)); + constexpr auto pm = detail::calculate_p_and_m(); + return static_cast((static_cast(n) * pm.second) >> pm.first); +} + class Polynomial { public: Polynomial() : m_coeffs({0}) {} @@ -180,21 +224,12 @@ class Polynomial { this->csubq(); - auto compress = [](uint32_t t) { - // (t << 1) + ((KyberConstants::Q / 2) / KyberConstants::Q) & 1 - // Note that magic numbers assume that ::Q = 3329 - t <<= 1; - t += 1665; - t *= 80635; - t >>= 28; - t &= 1; - return static_cast(t); - }; - for(size_t i = 0; i < size() / 8; ++i) { result[i] = 0; for(size_t j = 0; j < 8; ++j) { - result[i] |= compress(this->m_coeffs[8 * i + j]) << j; + const auto t = ct_divide_by((static_cast(this->m_coeffs[8 * i + j]) << 1) + + KyberConstants::Q / 2); + result[i] |= static_cast(t & 1) << j; } } @@ -632,8 +667,9 @@ class Ciphertext { if(mode.k() == 2 || mode.k() == 3) { for(size_t i = 0; i < p.size() / 8; ++i) { for(size_t j = 0; j < 8; ++j) { - t[j] = - (((static_cast(p[8 * i + j]) << 4) + KyberConstants::Q / 2) / KyberConstants::Q) & 15; + t[j] = static_cast(ct_divide_by( + (static_cast(p[8 * i + j]) << 4) + KyberConstants::Q / 2) & + 0b1111); } auto r = bs.next<4>(); @@ -645,8 +681,9 @@ class Ciphertext { } else if(mode.k() == 4) { for(size_t i = 0; i < p.size() / 8; ++i) { for(size_t j = 0; j < 8; ++j) { - t[j] = - (((static_cast(p[8 * i + j]) << 5) + KyberConstants::Q / 2) / KyberConstants::Q) & 31; + t[j] = static_cast(ct_divide_by( + (static_cast(p[8 * i + j]) << 5) + KyberConstants::Q / 2) & + 0b11111); } auto r = bs.next<5>(); diff --git a/src/tests/test_kyber.cpp b/src/tests/test_kyber.cpp index a451b3d504d..9320a5fc62c 100644 --- a/src/tests/test_kyber.cpp +++ b/src/tests/test_kyber.cpp @@ -26,6 +26,8 @@ #include #include #include + #include + #include #include #endif @@ -249,6 +251,26 @@ class Kyber_Keygen_Tests final : public PK_Key_Generation_Test { }; BOTAN_REGISTER_TEST("kyber", "kyber_keygen", Kyber_Keygen_Tests); + +namespace { + +std::vector test_kyber_utilities() { + return { + Botan_Tests::CHECK("constant-time division helper", + [](Test::Result& result) { + constexpr auto Q = Botan::KyberConstants::Q; + // Check ct_divide_by with all possible inputs that may appear in Kyber. + for(uint32_t i = 0; i < (1 << 18); ++i) { + result.test_is_eq(Botan::fmt("{}/Q", i), Botan::ct_divide_by(i), i / Q); + } + }), + }; +} + +} // namespace + +BOTAN_REGISTER_TEST_FN("kyber", "kyber_utils", test_kyber_utilities); + #endif } // namespace Botan_Tests