From 96e5f1d7ae86747d0f8f1e8729d0ff5b362ef781 Mon Sep 17 00:00:00 2001 From: Douglas Stebila Date: Fri, 19 Jun 2020 13:15:13 -0400 Subject: [PATCH] Fix timing leak in decapsulation. As identified in: Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. Based on https://github.com/microsoft/PQCrypto-LWEKE/commit/155c24c3df47be6d5d9845fea37be110945e963c --- crypto_kem/frodokem1344aes/clean/common.h | 2 ++ crypto_kem/frodokem1344aes/clean/kem.c | 15 ++++++------- crypto_kem/frodokem1344aes/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem1344aes/opt/common.h | 2 ++ crypto_kem/frodokem1344aes/opt/kem.c | 15 ++++++------- crypto_kem/frodokem1344aes/opt/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem1344shake/clean/common.h | 2 ++ crypto_kem/frodokem1344shake/clean/kem.c | 15 ++++++------- crypto_kem/frodokem1344shake/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem1344shake/opt/common.h | 2 ++ crypto_kem/frodokem1344shake/opt/kem.c | 15 ++++++------- crypto_kem/frodokem1344shake/opt/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem640aes/clean/common.h | 2 ++ crypto_kem/frodokem640aes/clean/kem.c | 15 ++++++------- crypto_kem/frodokem640aes/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem640aes/opt/common.h | 2 ++ crypto_kem/frodokem640aes/opt/kem.c | 15 ++++++------- crypto_kem/frodokem640aes/opt/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem640shake/clean/common.h | 2 ++ crypto_kem/frodokem640shake/clean/kem.c | 15 ++++++------- crypto_kem/frodokem640shake/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem640shake/opt/common.h | 2 ++ crypto_kem/frodokem640shake/opt/kem.c | 15 ++++++------- crypto_kem/frodokem640shake/opt/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem976aes/clean/common.h | 2 ++ crypto_kem/frodokem976aes/clean/kem.c | 15 ++++++------- crypto_kem/frodokem976aes/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem976aes/opt/common.h | 2 ++ crypto_kem/frodokem976aes/opt/kem.c | 15 ++++++------- crypto_kem/frodokem976aes/opt/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem976shake/clean/common.h | 2 ++ crypto_kem/frodokem976shake/clean/kem.c | 15 ++++++------- crypto_kem/frodokem976shake/clean/util.c | 24 +++++++++++++++++++++ crypto_kem/frodokem976shake/opt/common.h | 2 ++ crypto_kem/frodokem976shake/opt/kem.c | 15 ++++++------- crypto_kem/frodokem976shake/opt/util.c | 24 +++++++++++++++++++++ 36 files changed, 396 insertions(+), 96 deletions(-) diff --git a/crypto_kem/frodokem1344aes/clean/common.h b/crypto_kem/frodokem1344aes/clean/common.h index f9fa3779..524370f0 100644 --- a/crypto_kem/frodokem1344aes/clean/common.h +++ b/crypto_kem/frodokem1344aes/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM1344AES_CLEAN_key_encode(uint16_t *out, const uint16_t *in) void PQCLEAN_FRODOKEM1344AES_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM1344AES_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM1344AES_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM1344AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM1344AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM1344AES_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM1344AES_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM1344AES_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem1344aes/clean/kem.c b/crypto_kem/frodokem1344aes/clean/kem.c index 8faaf005..fcef3ee4 100644 --- a/crypto_kem/frodokem1344aes/clean/kem.c +++ b/crypto_kem/frodokem1344aes/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM1344AES_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM1344AES_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM1344AES_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM1344AES_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem1344aes/clean/util.c b/crypto_kem/frodokem1344aes/clean/util.c index 138dbe23..be4e2d3e 100644 --- a/crypto_kem/frodokem1344aes/clean/util.c +++ b/crypto_kem/frodokem1344aes/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM1344AES_CLEAN_unpack(uint16_t *out, size_t outlen, const ui } +int8_t PQCLEAN_FRODOKEM1344AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM1344AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM1344AES_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem1344aes/opt/common.h b/crypto_kem/frodokem1344aes/opt/common.h index 1ea3ac89..e00eef0b 100644 --- a/crypto_kem/frodokem1344aes/opt/common.h +++ b/crypto_kem/frodokem1344aes/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM1344AES_OPT_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM1344AES_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM1344AES_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM1344AES_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM1344AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM1344AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM1344AES_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM1344AES_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM1344AES_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem1344aes/opt/kem.c b/crypto_kem/frodokem1344aes/opt/kem.c index f00af9c4..89c94adc 100644 --- a/crypto_kem/frodokem1344aes/opt/kem.c +++ b/crypto_kem/frodokem1344aes/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM1344AES_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, c BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM1344AES_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM1344AES_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM1344AES_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem1344aes/opt/util.c b/crypto_kem/frodokem1344aes/opt/util.c index 33b28109..d2dbb565 100644 --- a/crypto_kem/frodokem1344aes/opt/util.c +++ b/crypto_kem/frodokem1344aes/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM1344AES_OPT_unpack(uint16_t *out, size_t outlen, const uint } +int8_t PQCLEAN_FRODOKEM1344AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM1344AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM1344AES_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem1344shake/clean/common.h b/crypto_kem/frodokem1344shake/clean/common.h index 5e487aec..2daf0da3 100644 --- a/crypto_kem/frodokem1344shake/clean/common.h +++ b/crypto_kem/frodokem1344shake/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_key_encode(uint16_t *out, const uint16_t *i void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM1344SHAKE_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM1344SHAKE_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem1344shake/clean/kem.c b/crypto_kem/frodokem1344shake/clean/kem.c index 9e4bd8b5..8374b5c3 100644 --- a/crypto_kem/frodokem1344shake/clean/kem.c +++ b/crypto_kem/frodokem1344shake/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM1344SHAKE_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *c BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem1344shake/clean/util.c b/crypto_kem/frodokem1344shake/clean/util.c index 1a403a39..189ea752 100644 --- a/crypto_kem/frodokem1344shake/clean/util.c +++ b/crypto_kem/frodokem1344shake/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const } +int8_t PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM1344SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem1344shake/opt/common.h b/crypto_kem/frodokem1344shake/opt/common.h index 32af833b..7d907b6a 100644 --- a/crypto_kem/frodokem1344shake/opt/common.h +++ b/crypto_kem/frodokem1344shake/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM1344SHAKE_OPT_key_encode(uint16_t *out, const uint16_t *in) void PQCLEAN_FRODOKEM1344SHAKE_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM1344SHAKE_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM1344SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM1344SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM1344SHAKE_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM1344SHAKE_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem1344shake/opt/kem.c b/crypto_kem/frodokem1344shake/opt/kem.c index d4022d58..57f38048 100644 --- a/crypto_kem/frodokem1344shake/opt/kem.c +++ b/crypto_kem/frodokem1344shake/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM1344SHAKE_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem1344shake/opt/util.c b/crypto_kem/frodokem1344shake/opt/util.c index 22606c95..220bfae5 100644 --- a/crypto_kem/frodokem1344shake/opt/util.c +++ b/crypto_kem/frodokem1344shake/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM1344SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const ui } +int8_t PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM1344SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM1344SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem640aes/clean/common.h b/crypto_kem/frodokem640aes/clean/common.h index 76309497..f30d5835 100644 --- a/crypto_kem/frodokem640aes/clean/common.h +++ b/crypto_kem/frodokem640aes/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM640AES_CLEAN_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640AES_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640AES_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM640AES_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM640AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM640AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM640AES_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM640AES_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM640AES_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem640aes/clean/kem.c b/crypto_kem/frodokem640aes/clean/kem.c index 75b38eb0..3572ee59 100644 --- a/crypto_kem/frodokem640aes/clean/kem.c +++ b/crypto_kem/frodokem640aes/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM640AES_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM640AES_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM640AES_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM640AES_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem640aes/clean/util.c b/crypto_kem/frodokem640aes/clean/util.c index 96bec752..d0218113 100644 --- a/crypto_kem/frodokem640aes/clean/util.c +++ b/crypto_kem/frodokem640aes/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM640AES_CLEAN_unpack(uint16_t *out, size_t outlen, const uin } +int8_t PQCLEAN_FRODOKEM640AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM640AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM640AES_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem640aes/opt/common.h b/crypto_kem/frodokem640aes/opt/common.h index ea2f4d6d..aead8777 100644 --- a/crypto_kem/frodokem640aes/opt/common.h +++ b/crypto_kem/frodokem640aes/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM640AES_OPT_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640AES_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640AES_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM640AES_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM640AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM640AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM640AES_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM640AES_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM640AES_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem640aes/opt/kem.c b/crypto_kem/frodokem640aes/opt/kem.c index 587fd86b..d65a08ac 100644 --- a/crypto_kem/frodokem640aes/opt/kem.c +++ b/crypto_kem/frodokem640aes/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM640AES_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, co BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM640AES_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM640AES_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM640AES_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem640aes/opt/util.c b/crypto_kem/frodokem640aes/opt/util.c index 716c36dc..b43d9f84 100644 --- a/crypto_kem/frodokem640aes/opt/util.c +++ b/crypto_kem/frodokem640aes/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM640AES_OPT_unpack(uint16_t *out, size_t outlen, const uint8 } +int8_t PQCLEAN_FRODOKEM640AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM640AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM640AES_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem640shake/clean/common.h b/crypto_kem/frodokem640shake/clean/common.h index aa19ea07..ddba6428 100644 --- a/crypto_kem/frodokem640shake/clean/common.h +++ b/crypto_kem/frodokem640shake/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM640SHAKE_CLEAN_key_encode(uint16_t *out, const uint16_t *in void PQCLEAN_FRODOKEM640SHAKE_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640SHAKE_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM640SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM640SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM640SHAKE_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM640SHAKE_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem640shake/clean/kem.c b/crypto_kem/frodokem640shake/clean/kem.c index b9c42351..40d5dd76 100644 --- a/crypto_kem/frodokem640shake/clean/kem.c +++ b/crypto_kem/frodokem640shake/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM640SHAKE_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem640shake/clean/util.c b/crypto_kem/frodokem640shake/clean/util.c index 6c12743f..31e1b155 100644 --- a/crypto_kem/frodokem640shake/clean/util.c +++ b/crypto_kem/frodokem640shake/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM640SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const u } +int8_t PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM640SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM640SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem640shake/opt/common.h b/crypto_kem/frodokem640shake/opt/common.h index 2910c84f..32b4a4c1 100644 --- a/crypto_kem/frodokem640shake/opt/common.h +++ b/crypto_kem/frodokem640shake/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM640SHAKE_OPT_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640SHAKE_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM640SHAKE_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM640SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM640SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM640SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM640SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM640SHAKE_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM640SHAKE_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem640shake/opt/kem.c b/crypto_kem/frodokem640shake/opt/kem.c index 606e5544..e1b50855 100644 --- a/crypto_kem/frodokem640shake/opt/kem.c +++ b/crypto_kem/frodokem640shake/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM640SHAKE_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM640SHAKE_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM640SHAKE_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM640SHAKE_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem640shake/opt/util.c b/crypto_kem/frodokem640shake/opt/util.c index ab2f74c9..6cbe46b0 100644 --- a/crypto_kem/frodokem640shake/opt/util.c +++ b/crypto_kem/frodokem640shake/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM640SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const uin } +int8_t PQCLEAN_FRODOKEM640SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM640SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM640SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem976aes/clean/common.h b/crypto_kem/frodokem976aes/clean/common.h index c6d420bb..6480b837 100644 --- a/crypto_kem/frodokem976aes/clean/common.h +++ b/crypto_kem/frodokem976aes/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM976AES_CLEAN_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976AES_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976AES_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM976AES_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM976AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM976AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM976AES_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM976AES_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM976AES_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem976aes/clean/kem.c b/crypto_kem/frodokem976aes/clean/kem.c index 4173ba37..125463c5 100644 --- a/crypto_kem/frodokem976aes/clean/kem.c +++ b/crypto_kem/frodokem976aes/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM976AES_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM976AES_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM976AES_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM976AES_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem976aes/clean/util.c b/crypto_kem/frodokem976aes/clean/util.c index 92199cae..dda97621 100644 --- a/crypto_kem/frodokem976aes/clean/util.c +++ b/crypto_kem/frodokem976aes/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM976AES_CLEAN_unpack(uint16_t *out, size_t outlen, const uin } +int8_t PQCLEAN_FRODOKEM976AES_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM976AES_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM976AES_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem976aes/opt/common.h b/crypto_kem/frodokem976aes/opt/common.h index 141e41b0..e655aedf 100644 --- a/crypto_kem/frodokem976aes/opt/common.h +++ b/crypto_kem/frodokem976aes/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM976AES_OPT_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976AES_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976AES_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM976AES_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM976AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM976AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM976AES_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM976AES_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM976AES_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem976aes/opt/kem.c b/crypto_kem/frodokem976aes/opt/kem.c index 52b00bee..c2420c8e 100644 --- a/crypto_kem/frodokem976aes/opt/kem.c +++ b/crypto_kem/frodokem976aes/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM976AES_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, co BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM976AES_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM976AES_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM976AES_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem976aes/opt/util.c b/crypto_kem/frodokem976aes/opt/util.c index 0369e652..67019878 100644 --- a/crypto_kem/frodokem976aes/opt/util.c +++ b/crypto_kem/frodokem976aes/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM976AES_OPT_unpack(uint16_t *out, size_t outlen, const uint8 } +int8_t PQCLEAN_FRODOKEM976AES_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM976AES_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM976AES_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem976shake/clean/common.h b/crypto_kem/frodokem976shake/clean/common.h index 12e2b2b8..eadc17c6 100644 --- a/crypto_kem/frodokem976shake/clean/common.h +++ b/crypto_kem/frodokem976shake/clean/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM976SHAKE_CLEAN_key_encode(uint16_t *out, const uint16_t *in void PQCLEAN_FRODOKEM976SHAKE_CLEAN_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976SHAKE_CLEAN_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM976SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM976SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM976SHAKE_CLEAN_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM976SHAKE_CLEAN_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem976shake/clean/kem.c b/crypto_kem/frodokem976shake/clean/kem.c index 1b70711c..ec7c3b63 100644 --- a/crypto_kem/frodokem976shake/clean/kem.c +++ b/crypto_kem/frodokem976shake/clean/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM976SHAKE_CLEAN_crypto_kem_dec(uint8_t *ss, const uint8_t *ct BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem976shake/clean/util.c b/crypto_kem/frodokem976shake/clean/util.c index a92a24b0..b8246b87 100644 --- a/crypto_kem/frodokem976shake/clean/util.c +++ b/crypto_kem/frodokem976shake/clean/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM976SHAKE_CLEAN_unpack(uint16_t *out, size_t outlen, const u } +int8_t PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM976SHAKE_CLEAN_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM976SHAKE_CLEAN_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing. diff --git a/crypto_kem/frodokem976shake/opt/common.h b/crypto_kem/frodokem976shake/opt/common.h index b51cfdf1..07dad62e 100644 --- a/crypto_kem/frodokem976shake/opt/common.h +++ b/crypto_kem/frodokem976shake/opt/common.h @@ -12,6 +12,8 @@ void PQCLEAN_FRODOKEM976SHAKE_OPT_key_encode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976SHAKE_OPT_key_decode(uint16_t *out, const uint16_t *in); void PQCLEAN_FRODOKEM976SHAKE_OPT_pack(uint8_t *out, size_t outlen, const uint16_t *in, size_t inlen, uint8_t lsb); void PQCLEAN_FRODOKEM976SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const uint8_t *in, size_t inlen, uint8_t lsb); +int8_t PQCLEAN_FRODOKEM976SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len); +void PQCLEAN_FRODOKEM976SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector); void PQCLEAN_FRODOKEM976SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n); uint16_t PQCLEAN_FRODOKEM976SHAKE_OPT_LE_TO_UINT16(uint16_t n); uint16_t PQCLEAN_FRODOKEM976SHAKE_OPT_UINT16_TO_LE(uint16_t n); diff --git a/crypto_kem/frodokem976shake/opt/kem.c b/crypto_kem/frodokem976shake/opt/kem.c index 5156b787..7dae303a 100644 --- a/crypto_kem/frodokem976shake/opt/kem.c +++ b/crypto_kem/frodokem976shake/opt/kem.c @@ -214,14 +214,13 @@ int PQCLEAN_FRODOKEM976SHAKE_OPT_crypto_kem_dec(uint8_t *ss, const uint8_t *ct, BBp[i] = BBp[i] & ((1 << PARAMS_LOGQ) - 1); } - // Is (Bp == BBp & C == CC) = true - if (memcmp(Bp, BBp, 2 * PARAMS_N * PARAMS_NBAR) == 0 && memcmp(C, CC, 2 * PARAMS_NBAR * PARAMS_NBAR) == 0) { - // Load k' to do ss = F(ct || k') - memcpy(Fin_k, kprime, CRYPTO_BYTES); - } else { - // Load s to do ss = F(ct || s) - memcpy(Fin_k, sk_s, CRYPTO_BYTES); - } + // If (Bp == BBp & C == CC) then ss = F(ct || k'), else ss = F(ct || s) + // Needs to avoid branching on secret data as per: + // Qian Guo, Thomas Johansson, Alexander Nilsson. A key-recovery timing attack on post-quantum + // primitives using the Fujisaki-Okamoto transformation and its application on FrodoKEM. In CRYPTO 2020. + int8_t selector = PQCLEAN_FRODOKEM976SHAKE_OPT_ct_verify(Bp, BBp, PARAMS_N * PARAMS_NBAR) | PQCLEAN_FRODOKEM976SHAKE_OPT_ct_verify(C, CC, PARAMS_NBAR * PARAMS_NBAR); + // If (selector == 0) then load k' to do ss = F(ct || k'), else if (selector == -1) load s to do ss = F(ct || s) + PQCLEAN_FRODOKEM976SHAKE_OPT_ct_select((uint8_t *)Fin_k, (uint8_t *)kprime, (uint8_t *)sk_s, CRYPTO_BYTES, selector); shake(ss, CRYPTO_BYTES, Fin, CRYPTO_CIPHERTEXTBYTES + CRYPTO_BYTES); // Cleanup: diff --git a/crypto_kem/frodokem976shake/opt/util.c b/crypto_kem/frodokem976shake/opt/util.c index 47b20190..0ae983c1 100644 --- a/crypto_kem/frodokem976shake/opt/util.c +++ b/crypto_kem/frodokem976shake/opt/util.c @@ -224,6 +224,30 @@ void PQCLEAN_FRODOKEM976SHAKE_OPT_unpack(uint16_t *out, size_t outlen, const uin } +int8_t PQCLEAN_FRODOKEM976SHAKE_OPT_ct_verify(const uint16_t *a, const uint16_t *b, size_t len) { + // Compare two arrays in constant time. + // Returns 0 if the byte arrays are equal, -1 otherwise. + uint16_t r = 0; + + for (size_t i = 0; i < len; i++) { + r |= a[i] ^ b[i]; + } + + r = (-(int16_t)r) >> (8 * sizeof(uint16_t) -1); + return (int8_t)r; +} + + +void PQCLEAN_FRODOKEM976SHAKE_OPT_ct_select(uint8_t *r, const uint8_t *a, const uint8_t *b, size_t len, int8_t selector) { + // Select one of the two input arrays to be moved to r + // If (selector == 0) then load r with a, else if (selector == -1) load r with b + + for (size_t i = 0; i < len; i++) { + r[i] = (~selector & a[i]) | (selector & b[i]); + } +} + + void PQCLEAN_FRODOKEM976SHAKE_OPT_clear_bytes(uint8_t *mem, size_t n) { // Clear 8-bit bytes from memory. "n" indicates the number of bytes to be zeroed. // This function uses the volatile type qualifier to inform the compiler not to optimize out the memory clearing.