Reputation: 125
There are two elements in Kuznyechik source code from VeraCrypt source code: https://github.com/veracrypt/VeraCrypt/blob/master/src/Crypto/kuznyechik.c#L2271-L2272
uint64 x1 = *(const uint64*)in;
uint64 x2 = *(((const uint64*)in)+1);
They are processed in the Kuznyechik S-Box like this:
#define LS(x1,x2,t1,t2) { \
t1 = T[0][(byte)(x1)][0] ^ T[1][(byte)(x1 >> 8)][0] ^ T[2][(byte)(x1 >> 16)][0] ^ T[3][(byte)(x1 >> 24)][0] ^ T[4][(byte)(x1 >> 32)][0] ^ T[5][(byte)(x1 >> 40)][0] ^ \
T[6][(byte)(x1 >> 48)][0] ^ T[7][(byte)(x1 >> 56)][0] ^ T[8][(byte)(x2)][0] ^ T[9][(byte)(x2 >> 8)][0] ^ T[10][(byte)(x2 >> 16)][0] ^ T[11][(byte)(x2 >> 24)][0] ^ \
T[12][(byte)(x2 >> 32)][0] ^ T[13][(byte)(x2 >> 40)][0] ^ T[14][(byte)(x2 >> 48)][0] ^ T[15][(byte)(x2 >> 56)][0]; \
t2 = T[0][(byte)(x1)][1] ^ T[1][(byte)(x1 >> 8)][1] ^ T[2][(byte)(x1 >> 16)][1] ^ T[3][(byte)(x1 >> 24)][1] ^ T[4][(byte)(x1 >> 32)][1] ^ T[5][(byte)(x1 >> 40)][1] ^ \
T[6][(byte)(x1 >> 48)][1] ^ T[7][(byte)(x1 >> 56)][1] ^ T[8][(byte)(x2)][1] ^ T[9][(byte)(x2 >> 8)][1] ^ T[10][(byte)(x2 >> 16)][1] ^ T[11][(byte)(x2 >> 24)][1] ^ \
T[12][(byte)(x2 >> 32)][1] ^ T[13][(byte)(x2 >> 40)][1] ^ T[14][(byte)(x2 >> 48)][1] ^ T[15][(byte)(x2 >> 56)][1]; \
}
/\ https://github.com/veracrypt/VeraCrypt/blob/master/src/Crypto/kuznyechik.c#L2147-L2152
What does this mean? Does it mean that x1 will process the first to eighth byte in t1 and will do the same again but from eighth to first byte (in inverse order)? am I right?
Upvotes: 0
Views: 81
Reputation: 140589
Looking at somewhat more of the code than what you posted, I see that in
is a function parameter declared as (a typedef for) const char *
. Given that, the first fragment you posted could have been written more readably as
const uint64_t *in_qword = (const uint64_t *)in;
uint64_t x1 = in_qword[0];
uint64_t x2 = in_qword[1];
(This program's uint64
and the standard uint64_t
appear to be the same thing, but I did not dig through the maze of #ifdefs in this program's header files carefully enough to be sure of that. It doesn't actually matter for the rest of what I'm going to say.)
Rewritten this way, we can see that we have a pointer to an array of char
and the program is attempting to access the first several char
s of that array as-if it had been an array of type uint64_t
all along. This is a relatively common thing to see in cryptography code, but the C language does not actually allow you to do this. Both the original code and my rewrite have what is formally known as "undefined behavior". In my somewhat checkered experience, code like this will probably behave as intended as long as you don't try to use link-time optimization, but that is something you are getting away with, not something you're allowed to rely on.
(OK, how should you take the first sizeof(uint64_t)
bytes of a char
array and load them into a uint64_t
as a machine-endian integer? The only strictly conforming way to do it is
memcpy(&x1, &in[0], sizeof(uint64_t));
but this doesn't give the "yes it compiles to one load instruction" performance guarantee that people usually want in low-level code like this. The C language doesn't offer any alternative, which is why people keep using "type punning" even though it's incorrect.)
The second fragment you posted appears to be doing byte shuffling and S-box lookups in typical illegible fashion for a cryptographic primitive. I cannot tell whether it is correct C in isolation and I am not qualified to say whether the cipher is any good.
Upvotes: 1