algos_enc: make timing&sidechannel attacks harder

There was possible timing information leaking from failed decryptions,
new code makes the whole thing fail in almost the same time in all
cases.
This commit is contained in:
Mirek Kratochvil 2014-04-05 13:04:29 +02:00
parent fd489ae69f
commit 37d9c9a98e
3 changed files with 54 additions and 22 deletions

View file

@ -236,8 +236,6 @@ static bool message_unpad (std::vector<byte> in, bvector&out,
#define MIN(a,b) ((a)<(b)?(a):(b))
#include "arcfour.h"
/*
* Generic F-O functions. Note that ranksize must be equal to
*
@ -302,7 +300,7 @@ static int fo_encrypt (const bvector&plain, bvector&cipher,
//run McEliece
if (Pub.encrypt (mce_plain, cipher, ev) ) return 5;
//encrypt the message part (xor with arcfour)
//encrypt the message part
scipher sc;
sc.init ();
//whole key must be tossed in, so split if when necessary
@ -350,8 +348,40 @@ static int fo_decrypt (const bvector&cipher, bvector&plain,
cipher.begin(),
cipher.begin() + ciphersize);
//get and check the message size for later
uint msize = cipher.size() - ciphersize;
if (msize & 0x7) return 6;
/*
* There is an (easy) timing attack on McEliece variants' decryption
* that determines whether McEliece decoding failed or CCA2 padding
* verification failed and can be (pretty easily) used to recover all
* errors in the error vector. To make it a (whole lot) harder, we make
* sure that following computation runs the same in both cases, at
* least to practical extent.
*
* Note that this doesn't cover potential attack on underlying
* rootfinding algorithm (but that one is way harder to run correctly).
*
* bool failed is volatile because we need to force the compiler not to
* optimize out the loop that constructs the dummy error vector.
*/
//decrypt the symmetric key
if (Priv.decrypt (mce_cipher, mce_plain, ev) ) return 6;
volatile bool failed = Priv.decrypt (mce_cipher, mce_plain, ev);
/*
* if decoding failed, ev contains something weird. We need to make it
* to contain some dummy (but still valid) error vector that would work
* with CCA2 verification and fail later.
*
* ev now contains the broken error vector; let's fix it up to
*
* 11111(errorcount of 1s)111100000000000
*/
for (i = 0; i < ev.size(); ++i)
ev[i] = failed ? (i < errorcount) : ev[i];
//convert stuff to byte vectors
std::vector<byte> K, M;
@ -359,13 +389,11 @@ static int fo_decrypt (const bvector&cipher, bvector&plain,
for (i = 0; i < plainsize; ++i)
if (mce_plain[i]) K[i >> 3] |= 1 << (i & 0x7);
uint msize = cipher.size() - ciphersize;
if (msize & 0x7) return 7;
M.resize (msize >> 3, 0);
for (i = 0; i < msize; ++i)
if (cipher[ciphersize + i]) M[i >> 3] |= 1 << (i & 0x7);
//prepare arcfour
//prepare symmetric cipher
scipher sc;
sc.init ();
//stuff in the whole key
@ -381,21 +409,19 @@ static int fo_decrypt (const bvector&cipher, bvector&plain,
hash_type hf;
H = hf (M2);
/*
* Colex rank the vector to hash (it is faster than unranking)
*/
//colex rank the vector to hash (it is faster than unranking)
bvector ev_rank;
ev.colex_rank (ev_rank);
ev_rank.resize (ranksize, 0);
for (i = 0; i < ranksize; ++i)
if (ev_rank[i] != (1 & (H[ (i >> 3) % H.size()]
>> (i & 0x7) ) ) )
return 8;
return 7;
//if the message seems okay, unpad and return it.
pad_hash_type phf;
if (!message_unpad (M, plain, phf) ) return 9;
if (!message_unpad (M, plain, phf) ) return 8;
return 0;
}
@ -404,6 +430,8 @@ static int fo_decrypt (const bvector&cipher, bvector&plain,
* Instances for actual encryption/descryption algorithms
*/
#include "arcfour.h"
typedef arcfour<byte, 8, 4096> arcfour_fo_cipher;
#if HAVE_CRYPTOPP==1

View file

@ -141,6 +141,8 @@ bool evaluate_error_locator_trace (polynomial&sigma, bvector&ev, gf2m&fld)
stk.insert (make_pair (0, sigma) );
bool failed = false;
while (!stk.empty() ) {
uint i = stk.begin()->first;
@ -156,7 +158,10 @@ bool evaluate_error_locator_trace (polynomial&sigma, bvector&ev, gf2m&fld)
continue;
}
if (i >= fld.m) return false;
if (i >= fld.m) {
failed = true;
continue;
}
if (trace[i].zero() ) {
//compute the trace if it isn't cached
@ -176,6 +181,6 @@ bool evaluate_error_locator_trace (polynomial&sigma, bvector&ev, gf2m&fld)
stk.insert (make_pair (i + 1, q) );
}
return true;
return !failed;
}

View file

@ -421,10 +421,10 @@ int privkey::decrypt (const bvector & in, bvector & out, bvector & errors)
polynomial loc;
compute_alternant_error_locator (synd, fld, 1 << T, loc);
bool failed = false;
bvector ev;
if (!evaluate_error_locator_trace (loc, ev, fld) )
return 1; //couldn't decode
//TODO evaluator should return error positions, not bvector. fix it everywhere!
failed = true;
out = in;
out.resize (plain_size() );
@ -433,17 +433,16 @@ int privkey::decrypt (const bvector & in, bvector & out, bvector & errors)
//flip error positions of out.
for (i = 0; i < ev.size(); ++i) if (ev[i]) {
uint epos = support_pos[fld.inv (i)];
if (epos == fld.n) {
//found unexpected support, die.
out.clear();
return 1;
if (epos == fld.n || epos >= cipher_size() ) {
//found unexpected/wrong support, die.
failed = true;
continue;
}
if (epos >= cipher_size() ) return 1;
errors[epos] = 1;
if (epos < plain_size() )
out[epos] = !out[epos];
}
return 0;
return failed ? 1 : 0;
}