Skip to content
This repository was archived by the owner on Aug 4, 2026. It is now read-only.

Commit 3b43bc6

Browse files
authored
fix(kyber): Close potential side-channel vulnerability (#534)
* fix(kyber): Close potential side-channel vulnerability Address the Kyber side-channel vulnerabilities that are potentially introduced by the division in certain poly operations. Closes #533 * Add note to SECURITY.md * Update security note
1 parent a6c205a commit 3b43bc6

16 files changed

Lines changed: 115 additions & 37 deletions

File tree

‎SECURITY.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ Use at your own risk.
2020
### Historic issues
2121
* LEDAcryptKEM `leaktime` implementations are known to not be constant-time and expected to have timing side channel vulnerabilities.
2222
* LEDA support has since been dropped from PQClean as it got eliminated from the NIST PQC standardization project in round 3.
23-
* Rainbow level I has been shown to be insecure by Ward Beullens.
23+
* Rainbow level I has been shown to be insecure by Ward Beullens.
2424
* Rainbow was eliminated from the NIST PQC standardization project and removed from PQClean in round 4.
2525

2626
<!-- new date line
2727
### 2019-XX-XX
2828
-->
2929

30+
### 2024-01-25
31+
* Kyber used division operations that might leak side-channel information.
32+
[PR #534](https://github.com/PQClean/PQClean/pull/534) addressed this for the `clean` and `avx2` implementations.
33+
Note that the `aarch64` implementation is waiting for [PR#527](https://github.com/PQClean/PQClean/pull/527) to get fixed.
34+
3035
### 2020-12-11
3136
* The fix of the timing leak in the CCA transform of FrodoKEM in [PR #303](https://github.com/PQClean/PQClean/pull/303) was ineffective. The FrodoKEM team released another [fix](https://github.com/microsoft/PQCrypto-LWEKE/commit/669522db63850fa64d1a24a47e138e80a59349db) which was ported to PQClean in [PR #367](https://github.com/PQClean/PQClean/pull/367).
3237

‎crypto_kem/kyber1024/META.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ auxiliary-submitters:
2222
- Jintai Ding
2323
implementations:
2424
- name: clean
25-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
25+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2626
- name: avx2
27-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
27+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2828
supported_platforms:
2929
- architecture: x86_64
3030
operating_systems:

‎crypto_kem/kyber1024/clean/poly.c‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616
* - const poly *a: pointer to input polynomial
1717
**************************************************/
1818
void PQCLEAN_KYBER1024_CLEAN_poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) {
19-
size_t i, j;
20-
int16_t u;
19+
unsigned int i, j;
20+
int32_t u;
21+
uint32_t d0;
2122
uint8_t t[8];
2223

2324
for (i = 0; i < KYBER_N / 8; i++) {
2425
for (j = 0; j < 8; j++) {
2526
// map to positive standard representatives
2627
u = a->coeffs[8 * i + j];
2728
u += (u >> 15) & KYBER_Q;
28-
t[j] = ((((uint32_t)u << 5) + KYBER_Q / 2) / KYBER_Q) & 31;
29+
/* t[j] = ((((uint32_t)u << 5) + KYBER_Q/2)/KYBER_Q) & 31; */
30+
d0 = u << 5;
31+
d0 += 1664;
32+
d0 *= 40318;
33+
d0 >>= 27;
34+
t[j] = d0 & 0x1f;
2935
}
3036

3137
r[0] = (t[0] >> 0) | (t[1] << 5);
@@ -141,15 +147,20 @@ void PQCLEAN_KYBER1024_CLEAN_poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCP
141147
* - const poly *a: pointer to input polynomial
142148
**************************************************/
143149
void PQCLEAN_KYBER1024_CLEAN_poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) {
144-
size_t i, j;
145-
uint16_t t;
150+
unsigned int i, j;
151+
uint32_t t;
146152

147153
for (i = 0; i < KYBER_N / 8; i++) {
148154
msg[i] = 0;
149155
for (j = 0; j < 8; j++) {
150156
t = a->coeffs[8 * i + j];
151-
t += ((int16_t)t >> 15) & KYBER_Q;
152-
t = (((t << 1) + KYBER_Q / 2) / KYBER_Q) & 1;
157+
// t += ((int16_t)t >> 15) & KYBER_Q;
158+
// t = (((t << 1) + KYBER_Q/2)/KYBER_Q) & 1;
159+
t <<= 1;
160+
t += 1665;
161+
t *= 80635;
162+
t >>= 28;
163+
t &= 1;
153164
msg[i] |= t << j;
154165
}
155166
}

‎crypto_kem/kyber1024/clean/polyvec.c‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@
1414
**************************************************/
1515
void PQCLEAN_KYBER1024_CLEAN_polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) {
1616
unsigned int i, j, k;
17+
uint64_t d0;
1718

1819
uint16_t t[8];
1920
for (i = 0; i < KYBER_K; i++) {
2021
for (j = 0; j < KYBER_N / 8; j++) {
2122
for (k = 0; k < 8; k++) {
2223
t[k] = a->vec[i].coeffs[8 * j + k];
2324
t[k] += ((int16_t)t[k] >> 15) & KYBER_Q;
24-
t[k] = ((((uint32_t)t[k] << 11) + KYBER_Q / 2) / KYBER_Q) & 0x7ff;
25+
/* t[k] = ((((uint32_t)t[k] << 11) + KYBER_Q/2)/KYBER_Q) & 0x7ff; */
26+
d0 = t[k];
27+
d0 <<= 11;
28+
d0 += 1664;
29+
d0 *= 645084;
30+
d0 >>= 31;
31+
t[k] = d0 & 0x7ff;
32+
2533
}
2634

2735
r[ 0] = (uint8_t)(t[0] >> 0);

‎crypto_kem/kyber512/META.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ auxiliary-submitters:
2222
- Jintai Ding
2323
implementations:
2424
- name: clean
25-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
25+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2626
- name: avx2
27-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
27+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2828
supported_platforms:
2929
- architecture: x86_64
3030
operating_systems:

‎crypto_kem/kyber512/clean/poly.c‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616
* - const poly *a: pointer to input polynomial
1717
**************************************************/
1818
void PQCLEAN_KYBER512_CLEAN_poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) {
19-
size_t i, j;
20-
int16_t u;
19+
unsigned int i, j;
20+
int32_t u;
21+
uint32_t d0;
2122
uint8_t t[8];
2223

2324
for (i = 0; i < KYBER_N / 8; i++) {
2425
for (j = 0; j < 8; j++) {
2526
// map to positive standard representatives
2627
u = a->coeffs[8 * i + j];
2728
u += (u >> 15) & KYBER_Q;
28-
t[j] = ((((uint16_t)u << 4) + KYBER_Q / 2) / KYBER_Q) & 15;
29+
/* t[j] = ((((uint16_t)u << 4) + KYBER_Q/2)/KYBER_Q) & 15; */
30+
d0 = u << 4;
31+
d0 += 1665;
32+
d0 *= 80635;
33+
d0 >>= 28;
34+
t[j] = d0 & 0xf;
2935
}
3036

3137
r[0] = t[0] | (t[1] << 4);
@@ -128,15 +134,20 @@ void PQCLEAN_KYBER512_CLEAN_poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA
128134
* - const poly *a: pointer to input polynomial
129135
**************************************************/
130136
void PQCLEAN_KYBER512_CLEAN_poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) {
131-
size_t i, j;
132-
uint16_t t;
137+
unsigned int i, j;
138+
uint32_t t;
133139

134140
for (i = 0; i < KYBER_N / 8; i++) {
135141
msg[i] = 0;
136142
for (j = 0; j < 8; j++) {
137143
t = a->coeffs[8 * i + j];
138-
t += ((int16_t)t >> 15) & KYBER_Q;
139-
t = (((t << 1) + KYBER_Q / 2) / KYBER_Q) & 1;
144+
// t += ((int16_t)t >> 15) & KYBER_Q;
145+
// t = (((t << 1) + KYBER_Q/2)/KYBER_Q) & 1;
146+
t <<= 1;
147+
t += 1665;
148+
t *= 80635;
149+
t >>= 28;
150+
t &= 1;
140151
msg[i] |= t << j;
141152
}
142153
}

‎crypto_kem/kyber512/clean/polyvec.c‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414
**************************************************/
1515
void PQCLEAN_KYBER512_CLEAN_polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) {
1616
unsigned int i, j, k;
17+
uint64_t d0;
1718

1819
uint16_t t[4];
1920
for (i = 0; i < KYBER_K; i++) {
2021
for (j = 0; j < KYBER_N / 4; j++) {
2122
for (k = 0; k < 4; k++) {
2223
t[k] = a->vec[i].coeffs[4 * j + k];
2324
t[k] += ((int16_t)t[k] >> 15) & KYBER_Q;
24-
t[k] = ((((uint32_t)t[k] << 10) + KYBER_Q / 2) / KYBER_Q) & 0x3ff;
25+
/* t[k] = ((((uint32_t)t[k] << 10) + KYBER_Q/2)/ KYBER_Q) & 0x3ff; */
26+
d0 = t[k];
27+
d0 <<= 10;
28+
d0 += 1665;
29+
d0 *= 1290167;
30+
d0 >>= 32;
31+
t[k] = d0 & 0x3ff;
2532
}
2633

2734
r[0] = (uint8_t)(t[0] >> 0);

‎crypto_kem/kyber768/META.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ auxiliary-submitters:
2222
- Jintai Ding
2323
implementations:
2424
- name: clean
25-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
25+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2626
- name: avx2
27-
version: https://github.com/pq-crystals/kyber/commit/4ecce06d584a4e7cecc42f141aadae39b8431b06 via https://github.com/mkannwischer/package-pqclean/tree/146bcfac/kyber
27+
version: https://github.com/pq-crystals/kyber/commit/11d00ff1f20cfca1f72d819e5a45165c1e0a2816 via https://github.com/mkannwischer/package-pqclean/tree/0e93c032/kyber
2828
supported_platforms:
2929
- architecture: x86_64
3030
operating_systems:

‎crypto_kem/kyber768/clean/poly.c‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616
* - const poly *a: pointer to input polynomial
1717
**************************************************/
1818
void PQCLEAN_KYBER768_CLEAN_poly_compress(uint8_t r[KYBER_POLYCOMPRESSEDBYTES], const poly *a) {
19-
size_t i, j;
20-
int16_t u;
19+
unsigned int i, j;
20+
int32_t u;
21+
uint32_t d0;
2122
uint8_t t[8];
2223

2324
for (i = 0; i < KYBER_N / 8; i++) {
2425
for (j = 0; j < 8; j++) {
2526
// map to positive standard representatives
2627
u = a->coeffs[8 * i + j];
2728
u += (u >> 15) & KYBER_Q;
28-
t[j] = ((((uint16_t)u << 4) + KYBER_Q / 2) / KYBER_Q) & 15;
29+
/* t[j] = ((((uint16_t)u << 4) + KYBER_Q/2)/KYBER_Q) & 15; */
30+
d0 = u << 4;
31+
d0 += 1665;
32+
d0 *= 80635;
33+
d0 >>= 28;
34+
t[j] = d0 & 0xf;
2935
}
3036

3137
r[0] = t[0] | (t[1] << 4);
@@ -128,15 +134,20 @@ void PQCLEAN_KYBER768_CLEAN_poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA
128134
* - const poly *a: pointer to input polynomial
129135
**************************************************/
130136
void PQCLEAN_KYBER768_CLEAN_poly_tomsg(uint8_t msg[KYBER_INDCPA_MSGBYTES], const poly *a) {
131-
size_t i, j;
132-
uint16_t t;
137+
unsigned int i, j;
138+
uint32_t t;
133139

134140
for (i = 0; i < KYBER_N / 8; i++) {
135141
msg[i] = 0;
136142
for (j = 0; j < 8; j++) {
137143
t = a->coeffs[8 * i + j];
138-
t += ((int16_t)t >> 15) & KYBER_Q;
139-
t = (((t << 1) + KYBER_Q / 2) / KYBER_Q) & 1;
144+
// t += ((int16_t)t >> 15) & KYBER_Q;
145+
// t = (((t << 1) + KYBER_Q/2)/KYBER_Q) & 1;
146+
t <<= 1;
147+
t += 1665;
148+
t *= 80635;
149+
t >>= 28;
150+
t &= 1;
140151
msg[i] |= t << j;
141152
}
142153
}

‎crypto_kem/kyber768/clean/polyvec.c‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414
**************************************************/
1515
void PQCLEAN_KYBER768_CLEAN_polyvec_compress(uint8_t r[KYBER_POLYVECCOMPRESSEDBYTES], const polyvec *a) {
1616
unsigned int i, j, k;
17+
uint64_t d0;
1718

1819
uint16_t t[4];
1920
for (i = 0; i < KYBER_K; i++) {
2021
for (j = 0; j < KYBER_N / 4; j++) {
2122
for (k = 0; k < 4; k++) {
2223
t[k] = a->vec[i].coeffs[4 * j + k];
2324
t[k] += ((int16_t)t[k] >> 15) & KYBER_Q;
24-
t[k] = ((((uint32_t)t[k] << 10) + KYBER_Q / 2) / KYBER_Q) & 0x3ff;
25+
/* t[k] = ((((uint32_t)t[k] << 10) + KYBER_Q/2)/ KYBER_Q) & 0x3ff; */
26+
d0 = t[k];
27+
d0 <<= 10;
28+
d0 += 1665;
29+
d0 *= 1290167;
30+
d0 >>= 32;
31+
t[k] = d0 & 0x3ff;
2532
}
2633

2734
r[0] = (uint8_t)(t[0] >> 0);

0 commit comments

Comments
 (0)