You need to keep an internal representation of a symmetric key. You may want to save this key to disk, pass it over a network, or use it in some other way.
Simply keep the key as an ordered array of bytes. For example:
/* When statically allocated */ unsigned char *key[KEYLEN_BYTES]; /* When dynamically allocated */ unsigned char *key = (unsigned char *)malloc(KEYLEN_BYTES);
When you’re done using a key, you should delete it securely to prevent local attackers from recovering it from memory. (This is discussed in Recipe 13.2.)
While keys in public key cryptography are represented as very large numbers (and often stored in containers such as X.509 certificates), symmetric keys are always represented as a series of consecutive bits. Algorithms operate on these binary representations.
Occasionally, people are tempted to use a single 64-bit unit to
represent short keys (e.g., a long long
when using
GCC on most platforms). Similarly, we’ve commonly
seen people use an array of word-size values. That’s
a bad idea because of byte-ordering issues.
When representing
integers, the bytes of the integer may appear most significant byte
first (big-endian) or least significant byte first (little-endian).
Figure 4-1 provides a visual illustration of the
difference between
big-endian and
little-endian storage:
Endian-ness doesn’t matter when performing integer
operations, because the CPU implicitly knows how integers are
supposed to be represented and treats them appropriately. However, a
problem arises when we wish to treat a single integer or an array of
integers as an array of bytes. Casting the address of the first
integer to be a pointer to char
does not give the
right results on a little-endian machine, because the cast does not
cause bytes to be swapped to their
“natural” order. If you absolutely
always cast to an appropriate type, this may not be an issue if you
don’t move data between architectures, but that
would defeat any possible reason to use a bigger storage unit than a
single byte. For this reason, you should always represent key
material as an array of one-byte elements. If you do so, your code
and the data will always be portable, even if you send the data
across the network.
You should also avoid using
signed data
types, simply to avoid potential printing oddities due to sign
extension. For example, let’s say that you have a
signed 32-bit value, 0xFF000000
, and you want to
shift it right by one bit. You might expect the result
0x7F800000
, but you’d actually
get 0xFF800000
, because the sign bit gets shifted,
and the result also maintains the same sign.[1]
[1] To be clear on semantics, note that shifting right eight bits will always give the same result as shifting right one bit eight times. That is, when shifting right an unsigned value, the leftmost bits always get filled in with zeros. But with a signed value, they always get filled in with the original value of the most significant bit.
Get Secure Programming Cookbook for C and C++ now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.