Cryptography Fundamentals Questions
Core concepts and vocabulary of cryptography: confidentiality, integrity, authentication, and non-repudiation; the difference between symmetric and asymmetric primitives; and how standard algorithms, libraries, and protocols fit together. Covers threat models, common standards, and applying primitives and cryptographic libraries correctly to real-world security problems. The entry point for the cryptography track.
Your code needs N cryptographically secure random bytes for a key or nonce. Walk through how you'd get them correctly in Python and in C, and what specific mistakes in either language would quietly make the result insecure.
Sample Answer
Direct answer
A key or nonce needs bytes from a cryptographically secure pseudorandom number generator (CSPRNG), a random source specifically designed so its output cannot be predicted even by an attacker who has seen previous outputs, unlike an ordinary statistical random-number generator, which is built for speed and distribution quality, not unpredictability against an adversary. In Python, use the secrets module or os.urandom(). In C, use the operating system's CSPRNG interface directly, arc4random_buf() on Apple and BSD platforms, or the getrandom() system call (falling back to reading /dev/urandom) on Linux. The quiet, common mistake in both languages is reaching for the general-purpose random-number function instead, which looks identical in code but is not safe for this purpose.
Python: correct and incorrect
import os, secrets, random
# Correct: designed specifically for security-sensitive use.
key_a = secrets.token_bytes(32)
key_b = os.urandom(32)
# WRONG for keys or nonces: random.random() and friends use the Mersenne Twister,
# a fast, high-quality STATISTICAL generator whose internal state can be reconstructed
# from a few hundred of its outputs, making every future output predictable. It was
# never designed to resist an adversary, only to pass statistical randomness tests.
insecure_key = bytes(random.randint(0, 255) for _ in range(32))
print("secrets.token_bytes(32):", key_a.hex())
print("os.urandom(32): ", key_b.hex())
print("random module (INSECURE, do not use):", insecure_key.hex())
Running this prints three distinct 32-byte hex strings; the first two are safe to use as a key or nonce, the third looks identical in shape but must never be used for anything security-sensitive, because its generator is not designed to resist prediction.
C: correct and incorrect
#include <stdio.h>
#include <stdint.h>
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <stdlib.h>
static int get_random_bytes(uint8_t *buf, size_t n) {
arc4random_buf(buf, n); /* CSPRNG, cannot fail, needs no caller-supplied seed */
return 0;
}
#else
#include <sys/random.h>
#include <errno.h>
static int get_random_bytes(uint8_t *buf, size_t n) {
size_t got = 0;
while (got < n) {
ssize_t r = getrandom(buf + got, n - got, 0); /* Linux syscall, kernel CSPRNG */
if (r < 0) { if (errno == EINTR) continue; return -1; }
got += (size_t)r;
}
return 0;
}
#endif
int main(void) {
uint8_t key[32];
if (get_random_bytes(key, sizeof(key)) != 0) {
fprintf(stderr, "failed to obtain secure random bytes\n");
return 1;
}
printf("32-byte key: ");
for (size_t i = 0; i < sizeof(key); i++) printf("%02x", key[i]);
printf("\n");
return 0;
}
Compiling and running this twice prints two different 32-byte hex keys, confirming fresh, non-repeating output. The mistake this avoids: calling rand() (seeded with srand(time(NULL)) or similar) for key material. rand() is a general-purpose generator with a small internal state and, critically, seeding it from the current time means an attacker who has even a rough idea of when the program started only has to search a small number of candidate seeds to reproduce every "random" value it ever produces.
Specific mistakes that quietly make the result insecure
- Python: using
random.random(),random.randint(), or anything else from therandommodule for keys, nonces, tokens, or passwords, it is a statistical generator, not a security one, and Python's own documentation says so explicitly. - C: using
rand()/srand(), especially seeded fromtime(),getpid(), or another low-entropy, guessable value, or reading raw bytes from/dev/randomand blocking indefinitely under the outdated assumption that it's "more secure" than/dev/urandom, on modern systems both draw from the same underlying kernel CSPRNG once it has been seeded at boot. - Both languages: not checking the return value of a random-byte function for failure. A CSPRNG call that can fail (
getrandom()returning an error, for instance) and is silently ignored can leave a buffer partially unfilled, with the unfilled portion holding whatever was previously in memory, which may not be random at all.
Trade-offs and pitfalls
The dangerous part of this class of mistake is that insecure and secure code look almost identical: both produce a byte string the same length, and neither raises an obvious error. The only reliable defense is a policy, never call a general-purpose random function for anything security-sensitive, enforced by code review or, better, a linter rule that flags random.random/rand() calls near variables named key, nonce, token, or secret.
You are responsible for randomness on a constrained IoT device with minimal entropy sources. Propose a robust design to initialize cryptographic keys and generate RNG material for TLS sessions and attestations. Include use of any available hardware TRNG, seeding and reseeding policies for a DRBG, secure storage for seeds, fallback behaviors, and approaches for validating entropy quality during manufacturing and in-field operation.
Sample Answer
Direct answer
On a constrained device, combine whatever hardware true random number generator (TRNG, a circuit
that samples genuine physical noise rather than computing a formula) is available with a
software deterministic random bit generator (DRBG, the algorithm that stretches a seed into as
much random-looking output as needed, standardized in NIST SP 800-90A), seed it once inside a
secure element at manufacturing time, reseed it conservatively, and always have a defined, safe
fallback for the case where the hardware source degrades or fails in the field.
Structured elaboration
- Layered entropy sources. Treat the hardware TRNG as the primary source, but mix its output
with other on-device jitter (analog-to-digital converter noise, clock jitter, boot-timing
variation) via a hash-based combiner before it ever seeds the DRBG; no single source is trusted
alone. - DRBG choice and reseeding. A CTR-DRBG (built around block-cipher counter mode) or HMAC-DRBG
(built around a Hash-based Message Authentication Code, HMAC) from the NIST SP 800-90A family is a
standard choice. NIST SP 800-90A defines a maximum number of outputs a DRBG mechanism may
produce before it must be reseeded; production designs reseed well before that theoretical
ceiling, not at it, triggering a reseed on a fixed byte-count budget, a time-based schedule, or
any detected anomalous state (unexpected sleep/wake, firmware update). - Secure storage. Store the DRBG's internal state and any long-term seed material only inside a
secure element or Trusted Execution Environment (TEE), never in general flash, and pair it with a
monotonic counter to detect state rollback (an attacker resetting the device to replay an old,
already-used random state). - Fallback behavior. If the TRNG's built-in health tests fail or its estimated entropy drops
below an acceptable threshold, the device should degrade safely: delay operations that need fresh
high-assurance randomness, request operator-assisted entropy input, or fetch authenticated
randomness from a provisioning server over a mutually authenticated channel, rather than silently
falling back to a weak, always-available source.
Worked example
Concretely, at manufacturing time: measure the on-chip TRNG's output during burn-in and run a NIST
SP 800-90B min-entropy estimate on it, recording the estimated entropy-per-bit for that specific
unit in its provisioning record, not just a whole-product-line average, since manufacturing
variance between individual chips is exactly what this per-device test is meant to catch. In the
field, run the same class of continuous health tests that SP 800-90B specifies (a repetition-count
test, catching a source stuck outputting the same value, and an adaptive-proportion test, catching
a source that has drifted toward a biased output) on a rolling basis; a device that fails these
tests reports the failure in its next attested check-in and enters the degraded fallback mode
described above rather than continuing to mint cryptographic material from an unhealthy source.
Trade-offs & pitfalls
- Complexity and cost: layered entropy sources, a secure element, and continuous field health
testing all add bill-of-materials and firmware complexity that a cost-constrained device may
resist; the trade-off is that a device generating its own long-term keys with weak entropy is
a durable, unpatchable liability once it ships. - Common wrong turn: seeding once at manufacturing and never reseeding in the field. A DRBG's
internal state can, in principle, be inferred if enough of its output or side-channel behavior is
observed over the device's lifetime; periodic reseeding limits how much any single compromise of
the DRBG state actually exposes. - Edge case: a device that never re-establishes network connectivity cannot fetch authenticated
remote entropy as a fallback; the design must specify what "safe but offline" degradation looks
like, not assume connectivity is always available when it is needed most.
Design the cryptographic architecture for a high-throughput VPN targetting ~100 Gbps aggregate throughput with many short-lived sessions. Choose cipher suites (AEAD choices), handshake mechanisms, KDF and session-resumption strategies, and describe how to leverage hardware acceleration (NIC offload, AES-NI, vectorized implementations). Provide a realistic benchmarking and instrumentation plan to validate throughput, latency, and connection setup performance under load.
Sample Answer
Direct answer
At 100 Gbps aggregate with many short-lived sessions, the design has two equally important halves: choosing an AEAD (Authenticated Encryption with Associated Data) cipher suite and handshake that are cheap to set up per session, and building enough horizontal parallelism across cores and NIC queues that the aggregate rate is even reachable, since no single CPU core's crypto throughput gets close to 100 Gbps on its own.
Cipher suite and handshake
| Choice | When it wins |
|---|---|
| AES-256-GCM | Ubiquitous hardware acceleration (AES-NI, and newer VPCLMULQDQ/GFNI instructions for the GHASH step) on modern server CPUs; the default choice for this deployment |
| ChaCha20-Poly1305 | Platforms without AES hardware acceleration (some embedded or older mobile silicon); a fallback cipher suite, not the primary one here |
For the handshake, favor a lightweight, forward-secret design with a short round-trip count, either TLS 1.3 with X25519 ephemeral key exchange, or a Noise-Protocol-Framework-style handshake (the basis of WireGuard): both complete key agreement in effectively one round trip using long-term static keys plus ephemeral per-session keys. Given many short-lived sessions, connection-setup latency and rate matter as much as bulk throughput, so session resumption (TLS 1.3's PSK-based resumption, or WireGuard's single-round-trip re-key) is a first-class design requirement, not an afterthought.
Key derivation
Derive per-direction traffic keys from the handshake's shared secret with HKDF-SHA256, so a single shared secret cleanly yields independent send and receive keys without reusing key material across directions.
Hardware acceleration and horizontal scaling
AES-NI and VPCLMULQDQ handle the cipher and authentication-tag math per core; a NIC with crypto offload (many modern NICs offload IPsec or TLS record processing directly) removes bulk encryption from the CPU entirely for supporting protocols. Because 100 Gbps aggregate exceeds what any single core can process even with hardware acceleration, spread sessions across cores using multi-queue NICs with Receive Side Scaling (RSS), so each core owns a disjoint slice of sessions and there's no cross-core contention on a shared crypto context.
Nonce and rekey policy
A 96-bit GCM nonce field creates a hard ceiling on how many packets a single key can protect (a nonce is a value used only once per packet under a given key, and reusing one under the same key catastrophically breaks GCM). Using a random nonce per packet risks a birthday-bound collision once a key has protected on the order of 232 packets (the birthday bound is the point where randomly chosen values start to collide: with a 96-bit field there are 296 possible nonces, a coin-flip chance of some collision arrives around 248 draws, so standard guidance keeps the count far below that, near 232, where the chance of any collision works out to a negligible 2−33, staying well below the 2−32 design threshold); using a deterministic per-session counter instead removes that birthday-bound risk entirely and only runs into the field's hard capacity limit, several orders of magnitude higher. Given sessions here are already short-lived, define an explicit rekey trigger (whichever comes first: a byte-count threshold or a packet-count threshold, set well below the safe limit for the chosen nonce construction) as defense in depth regardless of how unlikely a single session is to approach it.
Benchmarking and instrumentation plan
Drive sustained load with a standard throughput tool (such as iperf3) run through the VPN while ramping concurrent session count, and measure: aggregate throughput at each session-count step; handshake latency and connection-setup rate (P50/P95/P99) under load; per-core CPU utilization alongside hardware crypto-instruction counters (via a profiler like perf stat) to identify whether the crypto layer or the surrounding packet-processing path is the actual bottleneck; and a soak test at sustained high packet rates specifically checking that rekeying triggers correctly before any session's nonce counter approaches its defined threshold, a correctness check, not just a throughput one.
Trade-offs and pitfalls
AES-256-GCM's hardware-acceleration advantage disappears on platforms without AES-NI, where ChaCha20-Poly1305's software performance profile is often better, so the "default" choice above is conditional on the target hardware, not universal. Aggressive rekeying (a short byte or packet threshold) adds handshake overhead that competes directly with the connection-setup-rate goal for short-lived sessions, so the threshold has to be chosen as a genuine trade-off against setup cost, not set as low as theoretically possible.
Compare AES-GCM and ChaCha20-Poly1305 as practical AEAD choices for an application. Discuss performance differences on platforms with and without AES-NI (e.g., x86 server vs mobile ARM), implementation complexity and side-channel considerations, nonce and IV handling details, and typical protocol-level pitfalls for each algorithm. Recommend choice guidelines for a cross-platform service.
Sample Answer
Direct answer
Both AES-GCM (Galois/Counter Mode) and ChaCha20-Poly1305 are well-vetted authenticated encryption with associated data (AEAD) ciphers, and either is a safe default choice. The practical deciding factor for a cross-platform service is hardware: AES-GCM is extremely fast and side-channel-resistant only when the processor has dedicated AES instructions (AES-NI on x86, the Cryptography Extensions on newer ARM cores); without that hardware, a software AES implementation is both slower and at real risk of cache-timing side channels, while ChaCha20-Poly1305 was specifically designed to be fast and constant-time in ordinary software on any CPU.
Comparing the two
- Performance: on a modern x86 server with AES-NI, AES-GCM is typically the faster of the two, dedicated instructions do the block cipher rounds and GHASH, the polynomial-based authentication step inside Galois/Counter Mode, in hardware. On a mobile or embedded ARM chip without crypto extensions, AES in software is comparatively slow, and ChaCha20-Poly1305 (built entirely from additions, rotations, and exclusive-ors, no table lookups) is usually the faster and safer choice; this was the original reason ChaCha20-Poly1305 was adopted for many mobile-heavy protocols.
- Implementation complexity and side channels: a naive software AES implementation typically uses lookup tables indexed by secret data, which can leak key bits through cache-timing attacks if an attacker can measure memory-access patterns (shared-hardware scenarios like multi-tenant cloud or side-channel-exposed embedded devices). ChaCha20's arithmetic-only design has no data-dependent table lookups, so straightforward implementations are naturally closer to constant-time, this doesn't mean AES-GCM is unsafe with hardware acceleration, since AES-NI performs the round function in fixed-latency hardware circuits rather than table lookups.
- Nonce and initialization-vector handling: both use a 96-bit nonce in their standard forms and both fail catastrophically (loss of confidentiality and forgeable authentication tags) if that nonce ever repeats under the same key, the failure mode is essentially identical; ChaCha20 has an extended-nonce variant, XChaCha20-Poly1305, with a 192-bit nonce that is safe to generate fully at random for the life of a system, which AES-GCM has no equivalent for in standard use.
- Protocol-level pitfalls seen in practice: reusing a nonce due to a poorly designed counter (common in AES-GCM deployments that roll their own nonce construction), forgetting to authenticate context data that should be bound into the associated-data field (letting an attacker splice a valid ciphertext into a different context), and truncating the authentication tag to save bytes, which weakens the forgery-resistance guarantee and should be avoided unless a protocol has specifically analyzed the shortened length.
Recommendation for a cross-platform service
Default to AES-GCM on server fleets where AES-NI (or the ARM equivalent) is guaranteed present, and negotiate ChaCha20-Poly1305 as the fallback for clients or devices without hardware acceleration, exactly the pattern Transport Layer Security (TLS) 1.3 cipher-suite negotiation supports out of the box. Avoid hand-rolling nonce construction for either cipher; use a vetted cryptography library's high-level AEAD interface, which manages nonce size and tag length correctly by default.
Trade-offs and pitfalls
Choosing "the faster one" without checking the actual deployment hardware is a common mistake, AES-GCM on hardware without AES-NI can be dramatically slower than ChaCha20-Poly1305 and carries side-channel risk a benchmark alone won't reveal. Whichever is chosen, the nonce-uniqueness requirement is non-negotiable and identical between them; picking based on performance while neglecting nonce management solves the wrong problem.
That is every published Cryptography Fundamentals question for Embedded Developer so far. Browse the other topics in this category, or practice this one interactively.