-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Classic McEliece Side Channel Report #204
Open
aewag
wants to merge
4
commits into
main
Choose a base branch
from
doc/cmce_sca
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
"""" | ||
Classic McEliece | ||
"""" | ||
|
||
Analysed variants: | ||
|
||
- 348864 | ||
- 348864f | ||
|
||
For the analysis of Classic McEliece (CMCE), a utility program has been written that calls the functions to be analyzed in a similar way to the Botan CLI. | ||
The following call is used to obtain the encapsulation and decapsulation outputs: | ||
|
||
.. code-block:: cpp | ||
|
||
const Botan::Classic_McEliece_PrivateKey priv_key(rng, params); | ||
|
||
[...] | ||
|
||
auto encryptor = Botan::PK_KEM_Encryptor(priv_key, "Raw", "base"); | ||
encryptor.encrypt(cipher_text, sym_key, rng, shared_secret_length); | ||
|
||
[...] | ||
|
||
auto decryptor = Botan::PK_KEM_Decryptor(priv_key, rng, "Raw"); | ||
sym_key = decryptor.decrypt(cipher_text.data(), cipher_text.size(), shared_secret_length); | ||
|
||
The Botan library is configured using the following console prompt: | ||
|
||
.. code-block:: | ||
|
||
./configure.py --prefix=./build --cc=gcc | ||
--cc-bin=/home/wagner/workspace/tools/homebrew/Cellar/gcc@11/11.4.0/bin/g++-11 \ | ||
--cc-abi=-fno-plt \ | ||
--disable-modules locking_allocator --disable-sse2 --disable-ssse3 \ | ||
--disable-sse4.1 --disable-sse4.2 --disable-avx2 --disable-bmi2 --disable-rdrand \ | ||
--disable-rdseed --disable-aes-ni --disable-sha-ni --disable-altivec \ | ||
--disable-neon --disable-armv8crypto --disable-powercrypto \ | ||
--without-os-feature=threads --build-targets=static,cli | ||
|
||
The binary is compiled with the `gcc` compiler with the following version: | ||
|
||
.. code-block:: | ||
|
||
$ g++-11 --version | ||
g++-11 (Homebrew GCC 11.4.0) 11.4.0 | ||
|
||
The host operating system is `Ubuntu 20.04.6 LTS`. | ||
|
||
**Analysis with DATA** | ||
|
||
In the first evaluation runs DATA identified leakage points within the routines used for key pair generation, encryption and decryption. | ||
Based on these results, the PRNG instantiated in the utility program was modified to isolate the cause of the observed leakage points. | ||
The modifications comprised seeding the PRNG with a constant and a key-unique seed. | ||
The constant seed resulted in no differences within the execution. | ||
In contrast, the key-unique seed resulted in a clear distinction of the execution traces between the used key material. | ||
|
||
The leakage occurs at the same lines of code for all three routines (key pair generation, encryption, decryption) within the CMCE Botan implementation. | ||
In the following we describe the cause of the leakage and the criticality in relation to the decryption routine. | ||
Please note that this also applies to the key pair generation and encryption routine but is omitted here as the information would be redundant. | ||
|
||
The leakage was observed in the `decode()` routine which is part of the `Classic_McEliece_Decryptor`. | ||
The function definition is in the file `cmce_decaps.cpp` in file 84 to 119. | ||
It is listed below: | ||
|
||
.. code-block:: | ||
|
||
std::pair<CT::Mask<uint8_t>, secure_bitvector> Classic_McEliece_Decryptor::decode( | ||
const Classic_McEliece_PrivateKeyInternal& sk, bitvector big_c) { | ||
BOTAN_ASSERT(big_c.size() == sk.params().m() * sk.params().t(), "Correct ciphertext input size"); | ||
big_c.resize(sk.params().n()); | ||
|
||
auto syndrome = compute_goppa_syndrome( | ||
sk.params(), sk.g(), sk.field_ordering(), big_c.as_locked()); | ||
auto locator = berlekamp_massey(sk.params(), syndrome); | ||
|
||
std::vector<Classic_McEliece_GF> images; | ||
auto alphas = sk.field_ordering().alphas(sk.params().n()); | ||
std::transform( | ||
alphas.begin(), alphas.end(), std::back_inserter(images), [&](const auto& alpha) { return locator(alpha); }); | ||
|
||
// Obtain e and check whether wt(e) = t | ||
secure_bitvector e; | ||
size_t hamming_weight_e = 0; | ||
auto decode_success = CT::Mask<uint8_t>::set(); // Avoid bool to avoid compiler optimizations | ||
for(const auto& image : images) { | ||
auto is_zero_mask = CT::Mask<uint16_t>::is_zero(image.elem()); | ||
e.push_back(is_zero_mask.as_bool()); | ||
hamming_weight_e += is_zero_mask.if_set_return(1); | ||
} | ||
decode_success &= CT::Mask<uint8_t>(CT::Mask<size_t>::is_equal(hamming_weight_e, sk.params().t())); | ||
|
||
// Check the error vector | ||
auto syndrome_from_e = compute_goppa_syndrome(sk.params(), sk.g(), sk.field_ordering(), e); | ||
auto syndromes_are_eq = GF_Mask::set(); | ||
for(size_t i = 0; i < syndrome.size(); ++i) { | ||
syndromes_are_eq &= GF_Mask::is_equal(syndrome.at(i), syndrome_from_e.at(i)); | ||
} | ||
|
||
decode_success &= syndromes_are_eq.elem_mask(); | ||
|
||
return {decode_success, std::move(e)}; | ||
} | ||
|
||
Within the `decode()` function the leakage was observed at code lines 102 to 106. | ||
This is the part where the error vector `e` is obtained. | ||
The identified leakage was due to an execution flow difference. | ||
This execution flow difference stems from the code in the `bitvector.h` file at line 223. | ||
It contains the following constant expression: A bit is set if the given bool is true. | ||
The relevant line is listed below: | ||
|
||
.. code-block:: | ||
|
||
private: | ||
constexpr bitref& assign(bool bit) noexcept { return (bit) ? set() : unset(); } | ||
|
||
When compiled, this line results in a conditional jump instruction in assembly code. | ||
Depending on the boolean input value a different code branch is executed. | ||
This expression is executed when assigning values to the elements of `e` at line 104: | ||
|
||
.. code-block:: | ||
|
||
e.push_back(is_zero_mask.as_bool()); | ||
|
||
The `push_back()` routine is contained in the file `bitvector.h` at lines 381 to 385 and is implemented as follows: | ||
|
||
.. code-block:: | ||
|
||
void push_back(bool bit) { | ||
const auto i = size(); | ||
resize(i + 1); | ||
ref(i) = bit; | ||
} | ||
|
||
The `=` operator at line 384 is implemented for the `bitvector` class at line 208 as: | ||
|
||
.. code-block:: | ||
|
||
constexpr bitref& operator=(bool bit) noexcept { return assign(bit); } | ||
|
||
This results in a call of the `assign()` routine listed above. | ||
|
||
The identified leakage would allow an adversary to potentially recover the error vector from the code execution, which is security critical. | ||
|
||
**Countermeasure implementation and evaluation** | ||
|
||
In the following, we propose a countermeasure, implement it and evaluate its effectiveness. | ||
The underlying issue of the leakage is due to the branch of the `?` operator used in the `assign()` routine. | ||
This must be avoided, e.g., by performing the set and unset operations regardless of the input value. | ||
As only one of the two operations is needed at a time, the other operation has to be ineffective. | ||
Below is a proposal for such a countermeasure: | ||
|
||
.. code-block:: | ||
|
||
private: | ||
constexpr bitref& assign(bool bit) noexcept { | ||
const block_type assign_mask = 0 - static_cast<block_type>(bit); | ||
this->m_block \|= (this->m_mask & assign_mask); | ||
this->m_block &= ~(this->m_mask & ~assign_mask); | ||
return \*this; | ||
} | ||
|
||
The input bool `bit` is casted as an `uint8_t` datatype and is used to generate a mask. | ||
This mask is used to implement the behavior that only one operation is effective at a time. | ||
When compiled, this results in the following instructions - without any conditional branch based on the input: | ||
|
||
.. code-block:: | ||
|
||
[ ... ] | ||
const block_type assign_mask = 0 - static_cast<block_type>(bit); | ||
41d397: 41 f7 dc neg %r12d | ||
[ ... ] | ||
this->m_block \|= (this->m_mask & assign_mask); | ||
41d3ab: 44 89 e1 mov %r12d,%ecx | ||
41d3ae: 21 c1 and %eax,%ecx | ||
this->m_block &= ~(this->m_mask & ~assign_mask); | ||
41d3b0: f7 d0 not %eax | ||
this->m_block \|= (this->m_mask & assign_mask); | ||
41d3b2: 0a 0a or (%rdx),%cl | ||
this->m_block &= ~(this->m_mask & ~assign_mask); | ||
41d3b4: 44 09 e0 or %r12d,%eax | ||
41d3b7: 21 c8 and %ecx,%eax | ||
[ ... ] | ||
|
||
Note that in the final version of the CMCE implementation in Botan, the constant time helper class CT::MASK is used as a side-channel countermeasure. | ||
Therefore, the identified side-channel does not exist in the CMCE implementation in Botan. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We fixed the side channel using Botan's constant time helper class CT::Mask, which is more readable, in my opinion and does similar things as this code block. I guess your code translates better to assembly, though. So I'm fine with you version. (So nothing to do here, just a comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably worth mentioning that the later (and final) versions of the code fix this side channel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note added in 5df0aa0