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
Encaps/decaps failures with SIKE-compressed #988
Comments
I'm not able to get it to happen in SIKE-p434 (not compressed), it seems... |
ahhhhh of course, the NIST RNG isn't thread-safe, so my seeds are invalid. |
single-threaded, 434-compressed:
|
this reliably reproduces it. |
Non-thread-safe RNG shouldn't affect test_kem; while it might produce weird seeds, you should still get equal shared secrets. Multi-threaded kat_kem would produce wrong behaviour, but that's a different story. |
I've confirmed that this affects the upstream code. @thomwiggers care to submit the bug report? |
Here's the code that reproduces it upstream: #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rng/rng.h"
#include "../src/P434/P434_compressed_api.h"
#define KAT_SUCCESS 0
#define KAT_CRYPTO_FAILURE -4
int
main()
{
unsigned char seed[48] = {0x70, 0x4D, 0x49, 0xD5, 0xFF, 0x14, 0x5E, 0xF5, 0xB7, 0x90, 0x43, 0x93, 0x55, 0x38, 0xBC, 0xAC, 0x03, 0x71, 0x08, 0x17, 0x9E, 0x68, 0xBD, 0xDB, 0x47, 0x60, 0x06, 0x11, 0x9B, 0x7F, 0x3C, 0x68, 0x37, 0x29, 0xEB, 0x33, 0x97, 0x87, 0xBB, 0x93, 0x09, 0xA4, 0x48, 0x5C, 0xA4, 0xBA, 0x25, 0x38};
unsigned char ct[CRYPTO_CIPHERTEXTBYTES], ss[CRYPTO_BYTES], ss1[CRYPTO_BYTES];
unsigned char pk[CRYPTO_PUBLICKEYBYTES], sk[CRYPTO_SECRETKEYBYTES];
int ret_val;
randombytes_init(seed, NULL, 256);
// Generate the public/private keypair
if ( (ret_val = crypto_kem_keypair_SIKEp434_compressed(pk, sk)) != 0) {
printf("crypto_kem_keypair returned <%d>\n", ret_val);
return KAT_CRYPTO_FAILURE;
}
if ( (ret_val = crypto_kem_enc_SIKEp434_compressed(ct, ss, pk)) != 0) {
printf("crypto_kem_enc returned <%d>\n", ret_val);
return KAT_CRYPTO_FAILURE;
}
if ( (ret_val = crypto_kem_dec_SIKEp434_compressed(ss1, ct, sk)) != 0) {
printf("crypto_kem_dec returned <%d>\n", ret_val);
return KAT_CRYPTO_FAILURE;
}
if ( memcmp(ss, ss1, CRYPTO_BYTES) ) {
printf("crypto_kem_dec returned bad 'ss' value\n");
return KAT_CRYPTO_FAILURE;
}
return KAT_SUCCESS;
} |
Thanks for writing that up. I've also found the following seed for p503-compressed:
|
Yeah, my main concern here was that it would not give me the correct seed for the wrong behaviour. |
FYI, @patricklonga. |
FYI I reproduced this with the fuzzer sometime yesterday, and I took the opportunity to dig into it a bit further. I understand that the SIKE team is working on a fix, but I think we might want to provide some input on what an acceptable fix might look like. Here's the backtrace from address sanitizer ==520361==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7fc9fa7db7f8 at pc 0x7fc9fa351cfd bp 0x7ffd915c6430 sp 0x7ffd915c6428
READ of size 8 at 0x7fc9fa7db7f8 thread T0
#0 0x7f528666ecfc in fpcopy434 src/kem/sike/external/P434/../fpx.c:95:16
#1 0x7f528668a40f in BuildEntangledXonly src/kem/sike/external/P434/../compression/torsion_basis.c:395:9
#2 0x7f52866873e3 in BuildOrdinary2nBasis_dual src/kem/sike/external/P434/../compression/torsion_basis.c:423:5
#3 0x7f528666ac1d in EphemeralKeyGeneration_B_extended src/kem/sike/external/P434/../compression/sidh_compressed.c:630:5
#4 0x7f528666c958 in OQS_KEM_sike_p434_compressed_encaps src/kem/sike/external/P434/../compression/sike_compressed.c:47:5
#5 0x7f5286aaab68 in OQS_KEM_encaps src/kem/kem.c:826:10
0x7f7b064667f8 is located 0 bytes to the right of global variable 'table_r_qnr' defined in '../src/kem/sike/external/P434/P434_compressed.c:238:23' (0x7f7b06466440) of size 952
SUMMARY: AddressSanitizer: global-buffer-overflow /home/john/liboqstest/build/../src/kem/sike/external/P434/../fpx.c:95:16 in fpcopy434 table_r_qnr lists T elements of F_p (currently T=17). The BuildEntangledXonly function takes As far as I can tell, with probability ~2^-T the table will be exhausted (maybe there's some clever way of choosing the table entries but I don't see how to avoid this). At which point the protocol should either abort or it should generate new table entries on-the-fly. The latter is somewhat computationally expensive. I think a failure-free protocol / on-the-fly generation of extra table entries is the better option. But I can also see us accepting a sufficiently large table + a bounds check. How large a table would we want? Would we be comfortable with a 30 entry table and a 1 in a billion chance of failure? |
I personally wouldn't consider any algorithm "fit for purpose" (of large-scale deployment, i.e., billions of executions every second assuming world-wide deployment) if it is known to fail "every now and then". Probabilities shouldn't play a role. |
Yes generating extra table entries on the fly would be the preferred way. Extra computations would be limited to those cases.
I agree that no failures are a nice property, a property that SIKE actually has. Would failure rates be a useful info for the algorithm data sheets? (#892) |
These failures reveal something about either the randomness that went into the secret key or the randomness that went into the encapsulation operation. I'm not sure which of the two, or if it is relevant, but failures may not be good for security... |
Interesting... I'd find this property very disturbing in "regular use" -- and a possible "classical", if only side-channel, attack avenue. Do you have a pointer documenting this (hopefully including an argumentation why this is no security problem)? |
Perhaps what I wrote was misleading. I meant that SIKE has the property of "No Failures". |
Now I'm even more confused: Doesn't the above constitute a failure (even if just rarely)? Or are you saying it's "just" the implementation that's wrong/out-of-step with the spec of SIKE? |
Correct. |
The failure is in encaps and is not a security risk. A constant fraction of the keyspace is not suitable for use with compression, and the subset depends only on the choice of table entries.
It's not quite true that it is "just" the implementation. Page 83 of the spec says: "Experimentally, less than 20 elements are enough" and Algorithm 47 does not handle the case of exhausting the table. So the spec needs an update. |
+1 |
30, 50 or even 128 entries (56 bytes per entry, if I'm calculating this correctly) might be fine, since there are much larger tables for the compressed schemes already... In the upstream repo, |
And I was wondering why So what about adding Further idea: use those flags to exclude such algorithms from a possible future "SIZE_OPTIMIZED" build type. |
Thanks @thomwiggers and everyone else for reporting/debugging this issue. I've submitted a pull request with a fix to avoid running into such failures by performing online computations when tables run out of elements. The online computations are not relatively expensive so haven't added extra elements to the tables so no memory overhead in the fix. |
SIKE PR #45 has been merged; will start integrating here. |
I've been seeing encaps/decapsulation failures with SIKE-434-compressed.
I modified
test_kem.c
to be multithreaded and use NIST-KAT's RNG: https://gist.github.com/thomwiggers/c23e2c4e01971ccfd5496466b72a89c6I've not yet entirely figured out how to feed the seed back in to get a reproducible-every-time version.
See discussion in #981
The text was updated successfully, but these errors were encountered: