Google Cryptographer (Junior Level) Interview Preparation Guide
Google's interview process for cryptography-focused roles typically follows a structured pipeline consisting of an initial recruiter screening, technical phone screening round(s) to assess cryptographic fundamentals and problem-solving ability, and multiple onsite interview rounds covering technical depth, protocol design, implementation security, and cultural fit. For a junior-level role, the process emphasizes learning potential, foundational cryptographic knowledge, hands-on implementation skills, and ability to work collaboratively with senior cryptographers and security teams.
Interview Rounds
Recruiter Screening
What to Expect
Initial conversation with a Google recruiter to assess basic qualifications, background in cryptography and security, career motivation, and alignment with the role. This round also covers logistical details, compensation expectations, and timeline for the interview process. The recruiter will discuss your experience with cryptographic projects, understanding of encryption concepts, and why you're interested in Google's cryptography team.
Tips & Advice
Prepare a clear 1-2 minute overview of your cryptographic background and key projects. Research Google's security initiatives and mention why you're interested in their specific approach to cryptography. Be honest about your experience level as a junior—emphasize your strong fundamentals and learning ability rather than claiming expertise you don't have. Ask thoughtful questions about the team's work, technologies they use, and growth opportunities for junior cryptographers. Mention familiarity with cryptographic tools and libraries you've used.
Focus Topics
Familiarity with Cryptographic Tools and Libraries
Practical experience with OpenSSL, libsodium, Node.js crypto module, or other cryptographic implementations and security testing tools.
Practice Interview
Study Questions
Motivation for Google Cryptography Role
Clear articulation of why you want to work on cryptography at Google specifically, your career goals in security, and alignment with the team's mission.
Practice Interview
Study Questions
Professional Background in Cryptography
Your educational background, internships, projects, and hands-on experience with cryptographic implementations and analysis.
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
A 45-60 minute technical interview conducted over phone or video with a senior cryptographer or security engineer. This round tests your understanding of cryptographic fundamentals, ability to analyze and design simple cryptographic systems, and problem-solving approach. You'll be asked to discuss cryptographic concepts, analyze potential vulnerabilities in simplified systems, and possibly solve a design problem related to encryption or key management. The interview assesses both your theoretical knowledge and practical implementation thinking.
Tips & Advice
Review symmetric and asymmetric encryption fundamentals thoroughly before this round. Be prepared to explain the difference between encryption, hashing, and digital signatures with real-world examples. If asked to design a system, start by clarifying requirements and threat model before proposing solutions. Show your reasoning step-by-step and be willing to discuss trade-offs (e.g., security vs. performance, complexity vs. usability). If you don't know something, acknowledge it honestly and explain how you'd approach learning it. Write pseudocode or equations on a shared doc if needed to clarify your thinking. Ask clarifying questions about constraints and security requirements.
Focus Topics
Cryptographic Vulnerability Analysis
Ability to identify common cryptographic weaknesses including deprecated algorithms (MD5, SHA-1, DES, RC4), weak modes of operation, and implementation flaws. Understanding of side-channel attacks and secure coding practices.
Practice Interview
Study Questions
Simple Cryptographic Protocol Design
Ability to design basic secure communication protocols considering authentication, confidentiality, and integrity requirements. Understanding protocol design principles and common pitfalls.
Practice Interview
Study Questions
Symmetric vs. Asymmetric Encryption Fundamentals
Deep understanding of how symmetric encryption (AES, DES) and asymmetric encryption (RSA, ECC) work, their use cases, security properties, and performance characteristics.
Practice Interview
Study Questions
Key Generation, Management, and Rotation
Best practices for cryptographic key generation using CSPRNGs, appropriate key lengths for different algorithms, key storage, secure key rotation procedures, and hardware security modules (HSMs).
Practice Interview
Study Questions
Hashing, Integrity, and Message Authentication
Differences between hashing and encryption, cryptographic hash functions (SHA-256, SHA-512), HMACs, digital signatures, and their applications in data integrity and authentication.
Practice Interview
Study Questions
Onsite Technical Interview 1: Cryptographic Algorithm Analysis and Implementation
What to Expect
First onsite interview focusing on deep understanding of cryptographic algorithms, their mathematical foundations, and implementation considerations. You may be asked to analyze an algorithm's security properties, explain how a specific cipher works, discuss implementation optimizations, or identify vulnerabilities in provided code. The interviewer will assess your ability to think rigorously about cryptographic systems, explain technical concepts clearly, and recognize security implications of implementation choices.
Tips & Advice
Come prepared with detailed knowledge of at least 2-3 algorithms you've studied in depth (e.g., AES, RSA, ECC, ChaCha20). Be ready to explain the mathematical principles, not just the mechanics. If shown code with a potential vulnerability, take time to analyze it systematically—check key handling, randomization, mode of operation, and error conditions. Discuss realistic attack scenarios and their feasibility. Show awareness of implementation challenges like timing attacks and side-channel vulnerabilities. Be clear about assumptions and threat models you're considering.
Focus Topics
Algorithm Security Evaluation Methodologies
Approach to assessing whether an algorithm or protocol is secure: reviewing published cryptanalysis, understanding security reductions, evaluating resistance to known attacks, and distinguishing between theoretical and practical security.
Practice Interview
Study Questions
Modes of Operation and Their Security Properties
Understanding of ECB, CBC, CTR, GCM, and other modes; why ECB is insecure for most purposes, how IV/nonce usage prevents attacks, authenticated encryption importance, and mode selection for different scenarios.
Practice Interview
Study Questions
Implementation Security and Side-Channel Attacks
Awareness of timing attacks, power analysis, cache attacks, and other side-channel vulnerabilities. Understanding of constant-time implementations, secure memory handling (zeroing after use), and defensive programming practices.
Practice Interview
Study Questions
AES (Advanced Encryption Standard) Deep Dive
Detailed understanding of AES structure, operations (SubBytes, ShiftRows, MixColumns, AddRoundKey), key schedule, different key sizes, and modes of operation (ECB, CBC, CTR, GCM). Knowledge of why AES is considered secure and its practical applications.
Practice Interview
Study Questions
Public-Key Cryptography: RSA and ECC
Understanding RSA's mathematical foundation (modular arithmetic, prime factorization difficulty), key generation, encryption/decryption process, and digital signatures. Comparable understanding of Elliptic Curve Cryptography advantages (smaller keys, faster operations) and use cases.
Practice Interview
Study Questions
Onsite Technical Interview 2: Secure Protocol Design and Cryptographic Systems
What to Expect
Interview focusing on your ability to design and analyze complete cryptographic systems and protocols. You may be presented with a security requirement and asked to design a protocol (e.g., secure key exchange, authenticated encryption for a messaging system, or secure session management). Alternatively, you might analyze an existing protocol for vulnerabilities or propose improvements. This round tests your ability to think holistically about security: combining multiple cryptographic primitives, considering threat models, and designing with defense-in-depth principles.
Tips & Advice
When designing a protocol, start by clearly stating your assumptions, threat model, and what you're protecting against. Explain each cryptographic component choice and why that choice was made. Consider both confidentiality and integrity. Walk through a message flow and describe what protections exist at each step. Be ready to identify potential weaknesses in your design or improvements suggested by the interviewer. If discussing an existing protocol (like TLS, Signal Protocol), know its key features and historical vulnerabilities that led to improvements. Discuss trade-offs between security, performance, and usability. Think about key management challenges and recovery scenarios.
Focus Topics
Authenticated Encryption and AEAD Ciphers
Understanding of why authenticated encryption (combining confidentiality and authenticity) is important. Knowledge of AEAD cipher modes (GCM, ChaCha20-Poly1305) and their advantages. Proper usage patterns to prevent vulnerability.
Practice Interview
Study Questions
Protocol Threat Modeling and Analysis
Approach to identifying threats in a protocol design: replay attacks, man-in-the-middle attacks, known plaintext attacks, and others. Understanding of Dolev-Yao threat model and formal verification concepts.
Practice Interview
Study Questions
TLS/SSL and Secure Communication Fundamentals
Understanding of how TLS provides confidentiality and authentication for web traffic. Knowledge of certificate verification, cipher suite negotiation, and the role of certificates in the PKI. Awareness of protocol evolution (TLS 1.0 → 1.3) and improvements made.
Practice Interview
Study Questions
Authentication and Digital Signatures in Protocols
How digital signatures provide authentication and non-repudiation in protocols. Design of authenticated key exchange. Understanding of certificate-based authentication and its role in preventing man-in-the-middle attacks.
Practice Interview
Study Questions
Secure Key Exchange Protocols
Understanding of Diffie-Hellman key exchange and its variants (ECDH), their security properties, and how they're used in protocols like TLS. Awareness of forward secrecy and perfect forward secrecy concepts.
Practice Interview
Study Questions
Onsite Technical Interview 3: Implementation and Code Review
What to Expect
Interview assessing your practical implementation skills and ability to recognize security issues in code. You may be presented with cryptographic code (potentially in Node.js, Python, or another language) and asked to identify vulnerabilities, explain what it does, propose improvements, or complete a partial implementation. This round evaluates your understanding of cryptographic libraries, common implementation mistakes, and ability to write or review security-critical code. The focus is on recognizing real-world vulnerabilities and understanding how cryptographic theory translates to practice.
Tips & Advice
Review cryptographic libraries (Node.js crypto module, OpenSSL, libsodium) before this interview. Be familiar with common APIs and correct usage patterns. When analyzing code, look for: improper random number generation, weak key management, use of deprecated algorithms, incorrect mode of operation, missing authentication, timing attack vulnerabilities, and improper error handling. Discuss why each issue matters and how to fix it. If asked to write code, prioritize correctness and security over optimization. Use library functions appropriately; don't try to implement crypto primitives from scratch. Be aware of language-specific security concerns.
Focus Topics
Cryptographic Error Handling and Logging
Best practices for handling cryptographic errors (decryption failures, signature verification failures) securely. Considerations for logging sensitive operations without exposing cryptographic material. Balancing security with debuggability.
Practice Interview
Study Questions
Secure Random Number Generation
Understanding of CSPRNGs (cryptographically secure pseudorandom number generators) vs. non-cryptographic RNGs. Knowledge of entropy sources, entropy collection in different environments (servers, embedded systems), and secure APIs for random generation.
Practice Interview
Study Questions
Key Derivation Functions and Password Hashing
Understanding of KDFs (PBKDF2, Argon2, scrypt), their parameters and security properties. Knowledge of why password hashing differs from regular hashing and proper usage in authentication systems.
Practice Interview
Study Questions
Common Cryptographic Implementation Vulnerabilities
Recognition of typical mistakes: insecure random number generation, hardcoded keys, weak key derivation, replay attack vulnerabilities, use of ECB mode, missing authentication, improper IV/nonce handling, and timing sidechannels in comparisons.
Practice Interview
Study Questions
Cryptographic Library Usage (Node.js crypto module and others)
Practical knowledge of Node.js crypto module APIs, OpenSSL command-line usage, and other cryptographic libraries. Understanding proper API usage for encryption, decryption, hashing, signing, and random number generation.
Practice Interview
Study Questions
Onsite Behavioral and Culture Fit Interview
What to Expect
Interview assessing your problem-solving approach, collaboration style, learning ability, and alignment with Google's culture. This round often includes questions about how you work in teams, respond to challenges, handle disagreements about security decisions, and stay current with cryptographic research. You'll be asked behavioral questions about past projects, situations where you had to learn quickly, and how you contribute to a team's security posture. The interviewer evaluates communication skills, humility, curiosity, and ability to work within a strong security culture.
Tips & Advice
Prepare specific examples from your experience using the STAR method (Situation, Task, Action, Result). Have stories ready about: learning a difficult cryptographic concept, discovering and fixing a security vulnerability, collaborating with teammates to solve a security problem, and handling disagreement about a security approach. Show genuine curiosity about cryptography and security—mention recent research or standards you've been following. Emphasize your interest in learning from senior cryptographers. Ask thoughtful questions about Google's approach to security, the team's research interests, and how junior cryptographers grow in the role. Show awareness of security's importance and responsibility that comes with cryptography work.
Focus Topics
Collaboration and Communication in Security Teams
Examples of working effectively with other cryptographers, security engineers, and product teams. Ability to explain complex cryptographic concepts to non-experts. Handling constructive disagreement about security decisions.
Practice Interview
Study Questions
Security Responsibility and Ownership Mentality
Understanding that cryptographic decisions have real impact on system security and user privacy. Examples of taking ownership of security improvements, suggesting better cryptographic practices, and seeing security as everyone's responsibility.
Practice Interview
Study Questions
Learning and Growth Mindset in Cryptography
Demonstrating eagerness to learn complex cryptographic concepts, examples of how you've mastered difficult topics, and commitment to staying current with cryptographic research and standards evolution.
Practice Interview
Study Questions
Frequently Asked Cryptographer Interview Questions
Explain the role of a key schedule in block cipher design. Use DES weak keys and related-key attacks as concrete examples to illustrate how a poor key schedule can reduce effective security. Finally, list properties a good key schedule should provide.
Sample Answer
Direct answer
A key schedule expands a block cipher's single master key into a separate round key for each round, and its job is to inject that key material in a way that has high diffusion (a small key change should ripple into many round-key bits), no exploitable algebraic relationships between different round keys, and no shortcuts that collapse rounds together. DES (the Data Encryption Standard)'s key schedule is the classic cautionary example: its simple bit-rotation-and-permutation structure produces a handful of documented weak keys and enables related-key attacks that a stronger schedule would prevent.
Structured elaboration
Why the key schedule matters independently of the round function. Even a cryptographically strong round function can be undermined if the round keys it receives are not independent enough of each other; symmetries in the SCHEDULE can propagate into symmetries in the CIPHER, regardless of how well-designed the round function itself is.
DES weak keys, concretely. DES derives 16 round subkeys from a 56-bit key (64 bits including parity) via a fixed sequence of bit-permutations and left-rotations of two 28-bit halves. For a small number of specific keys (4 documented "weak" keys, plus additional "semi-weak" key pairs), this rotation schedule collapses so that ALL 16 round subkeys end up identical (or, for semi-weak pairs, the two keys' schedules produce round keys that are reverses of each other). A weak key makes DES an INVOLUTION: encrypting a block twice with the same weak key returns the original plaintext, E_K(E_K(P)) = P for every plaintext P, which is a huge structural degradation from the intended one-way, non-self-inverting behavior.
Related-key attacks. Because DES's schedule is a simple, fully LINEAR-in-structure rotation of the key bits (no nonlinear mixing of key material with itself), a small, controlled difference between two related keys produces a PREDICTABLE difference between their round-key schedules. Differential and linear cryptanalysis techniques adapted to this related-key setting can then recover key bits far faster than brute force, something a nonlinear, well-diffused key schedule would resist.
Properties a good key schedule should provide.
- High diffusion: a single flipped bit in the master key should change roughly half the bits across the full set of derived round keys (an avalanche effect analogous to the round function's own diffusion goal).
- Nonlinearity: the derivation should mix key material through nonlinear operations (S-box-style substitutions), not just permutation and rotation, so related-key differentials do not propagate predictably.
- Round-key independence: no simple algebraic or structural relationship between round keys
K_iandK_jfori != j. - Full entropy usage: every bit of the master key should meaningfully influence the round keys; no bits should be effectively "wasted."
- Resistance to related-key attacks: a small, attacker-chosen difference in the master key should not produce a predictable difference in the derived round keys.
Worked example
The defining, checkable property of a DES weak key, verified by actually encrypting: E_K(E_K(P)) == P for every plaintext, for a documented weak key, contrasted with an ordinary key where this never holds:
from Crypto.Cipher import DES
weak_key = bytes.fromhex("0101010101010101") # a documented DES weak key
ordinary_key = bytes.fromhex("133457799bbcdff1") # the classic FIPS test key, not weak
def double_encrypt_is_identity(key, plaintext):
c1 = DES.new(key, DES.MODE_ECB).encrypt(plaintext)
c2 = DES.new(key, DES.MODE_ECB).encrypt(c1)
return c2 == plaintext
test_blocks = [bytes([i]) * 8 for i in range(8)] + [bytes(range(8))]
print("weak key: all E_K(E_K(P)) == P:", all(double_encrypt_is_identity(weak_key, p) for p in test_blocks))
print("ordinary key: all E_K(E_K(P)) == P:", all(double_encrypt_is_identity(ordinary_key, p) for p in test_blocks))
Output:
weak key: all E_K(E_K(P)) == P: True
ordinary key: all E_K(E_K(P)) == P: False
(This uses pycryptodome's Crypto.Cipher.DES, a third-party library, since DES has been retired from most modern crypto libraries' default APIs as an obsolete cipher; cryptography's own algorithms module, for example, only retains TripleDES for legacy compatibility.) For EVERY test plaintext, the weak key's double-encryption returns exactly the original input, while the ordinary key never does, confirming the schedule collapse structurally rather than just asserting it.
Trade-offs and pitfalls
- Weak and semi-weak DES keys affect a tiny fraction of the 2^56 key space; the practical risk in real deployments is usually low probability of hitting one by chance, but the DEEPER lesson (a key schedule with insufficient nonlinearity and diffusion creates exploitable structure) generalizes to any cipher design, not just DES.
- Modern ciphers (AES included) invest real design effort in their key schedules specifically to avoid this class of weakness; AES's schedule mixes S-box substitution and round constants precisely to break the kind of periodic symmetry DES's pure rotation schedule has.
- A common mistake is treating "the key schedule is public anyway, so it doesn't need to be strong" as a reason to under-invest in it; publicness is fine (Kerckhoffs's principle), but STRUCTURAL weakness in a public algorithm is just as exploitable as if it were secret.
State recommended key length guidance for common algorithms: AES (symmetric), RSA (classical public-key), and elliptic curve algorithms (ECDSA/ECDH). Explain why key length matters and what operational considerations (performance, lifespan, algorithm migration) influence your choice of length.
Sample Answer
Direct answer
Roughly: AES-128 or AES-256 for symmetric keys, at least RSA-2048 with RSA-3072 preferred for
longer-lived data, and a 256-bit elliptic curve (like P-256) for ECDSA/ECDH. These are not
interchangeable "bit strengths": because the underlying hard problems differ, RSA needs a
much larger key than AES or ECC to reach the same practical security level.
Structured elaboration
Key length matters because it determines how expensive the best known attack is. For a
symmetric cipher, the best general attack is brute force over the key space, so security
scales directly and exponentially with key bits. RSA's security instead rests on integer
factorization, which has sub-exponential algorithms (the General Number Field Sieve), so RSA
needs a much larger key to match a given symmetric strength. Elliptic-curve cryptography
relies on the discrete logarithm problem over an elliptic curve, which has no known
sub-exponential attack, so it reaches strong security with much smaller keys than RSA.
Roughly matched security levels (NIST SP 800-57 style guidance):
| Symmetric-equivalent strength | AES | RSA | ECC (ECDSA/ECDH) |
|---|---|---|---|
| ~128-bit | AES-128 | RSA-3072 | P-256 |
| ~192-bit | AES-192 | RSA-7680 | P-384 |
| ~256-bit | AES-256 | RSA-15360 | P-521 |
Operational considerations that influence the choice:
- Performance: RSA key generation and private-key operations get noticeably slower as key
size grows; ECC stays comparatively cheap even at higher security levels, which is why TLS
handshakes favor ECDHE over large-RSA key exchange today. - Lifespan: a key protecting data that must stay confidential for decades (a root CA, an
archival encryption key) should sit at a higher security margin than a short-lived TLS
session key, since attacker capability only improves over time. - Algorithm migration headroom: teams often deliberately keep some headroom above the
current minimum recommendation, so that a future algorithm migration (moving to a larger
key size, or off an algorithm entirely) can happen on a planned schedule rather than as an
emergency re-key after a break is announced.
Worked example
A document-signing service needs signatures that remain verifiable for 20-year regulatory
retention. A TLS session key, by contrast, only needs to resist attack for the minutes the
connection is open. Both could technically use "128-bit class" security today, but the
signing service deliberately picks RSA-3072 over RSA-2048 (or a 384-bit curve over a 256-bit
one) precisely because of the lifespan consideration: a 20-year window gives attacker capability
two decades to improve, so the team spends a bit more compute per signature now to buy margin
that a short-lived session key does not need.
Trade-offs & pitfalls
- RSA-2048 is still widely deployed and considered acceptable for most near-term use, but
guidance is trending toward RSA-3072 for anything expected to remain secure for many years;
treat any bare "RSA-2048 is fine forever" claim with suspicion. - Bumping AES from 128 to 256 bits has essentially no practical performance cost on modern
hardware (AES-NI accelerates both equally), so the "just use AES-256 to be safe" instinct is
cheap here in a way it is not for RSA.
Discuss security implications of using hash functions to accumulate entropy into an RNG (e.g., hashing multiple entropy sources into a single seed). When is simple hashing adequate versus when you should use a standardized DRBG (like HMAC-DRBG)? How do you design reseed and state-compromise recovery procedures?
Sample Answer
High-level point: Hashing multiple entropy sources is fine as an “entropy combiner” if you treat hashing as extraction (remove bias, collisions) and you have reliable, independent, min-entropy estimates. For a production RNG with long-lived state, prefer a standardized DRBG (e.g., HMAC-DRBG / CTR-DRBG / Hash-DRBG) because it provides proven extract-then-expand primitives, internal state management, reseeding logic, and forward/backward security guarantees.
Why simple hashing can be adequate
- Use-case: one-shot seed accumulation (e.g., boot seed) where you collect many independent sources and immediately seed a secure PRF-based generator.
- Requirements: each source has quantified min-entropy; adversary cannot control > allowed threshold; you apply a proper extractor (HMAC or HKDF-style keyed extract) rather than raw hash concatenation.
- Example: seed = HKDF-Extract(salt, concat(samples)) then key the PRF. HKDF provides proven entropy extraction even when some inputs are weak.
When to use a standardized DRBG
- Long-lived generator, repeated outputs, networked devices, or where state compromise is a real risk.
- DRBGs specify internal state update rules, reseed intervals, prediction resistance modes, and limits on output-per-seed (e.g., NIST SP800‑90A constraints).
- HMAC-DRBG gives a keyed PRF with internal K/V state and standardized reseed_counter semantics—this yields established forward secrecy on rekey and bounds on prediction after partial compromise.
Designing reseed policy and procedures
- Entropy thresholds: require reseed when accumulated fresh entropy ≥ security_strength bits (e.g., 128/256).
- Event triggers: periodic (time-based), request-count-based, health-test failure, or external event (network compromise).
- Parameters: follow standards—e.g., limit output-per-reseed and reseed intervals (NIST suggests a reseed_counter limit like 2^48 for DRBGs; choose conservative values for embedded or high-risk systems).
- Practical: reseed if either T seconds elapsed OR R outputs produced OR new hardware event; require at least X bits of estimated entropy from independent sources before accepting reseed.
State-compromise recovery
- Detection: monitor entropy source health tests, seed-usage anomalies, and external alerts.
- Immediate steps on suspected compromise:
- Stop using existing generator output.
- Gather fresh entropy from multiple independent sources; require conservative min-entropy (exceed security strength).
- Reinitialize using an extractor (HKDF/HMAC) with fresh randomness and a new unique salt/nonce.
- If possible, perform forward-recovery: rotate keys derived from the DRBG only after reseeding with entropy that adversary cannot have seen.
- Additional mitigations:
- Use forward-secure constructions (periodic rekey) so past outputs remain safe after compromise.
- Keep an immutable audit log of reseed events and entropy estimates.
- Zeroize internal state on compromise and enforce secure erasure.
Practical recommendations for a cryptographer
- Prefer extract-then-expand (HMAC/HKDF) over raw hash concatenation.
- Use a standardized DRBG for long-lived use and follow NIST/ISO guidance for limits and reseed policies; tune reseed counters conservatively for your threat model.
- Always perform health tests, conservative entropy estimation, and require entropy from diverse, independent sources for reseed.
- Define and document an incident response (stop, gather >security_strength bits, reinit, rotate dependent keys, log).
This approach balances theoretical soundness (provable extraction and PRF security) with operational resilience (reseed, detection, recovery).
Implement HKDF (RFC 5869) in Python 3 using HMAC-SHA256 and only the standard library. Provide two functions: hkdf_extract(salt, ikm) -> prk and hkdf_expand(prk, info, length) -> okm. Describe safe defaults for salt and info and state the maximum expand length constraint.
Sample Answer
Approach
RFC 5869 defines HKDF (HMAC-based key derivation function) as two independent steps: hkdf_extract(salt, ikm) -> prk concentrates the input keying material's entropy into a fixed-length pseudorandom key using one HMAC call, and hkdf_expand(prk, info, length) -> okm stretches that key into length bytes of output using a chain of HMAC calls, each one feeding the previous block's output, the context string info, and a one-byte counter back into the next call. Both steps are just HMAC-SHA256 calls in the specific arrangement the RFC defines; no other cryptographic machinery is needed.
Implementation
import hmac
import hashlib
HASH = hashlib.sha256
HASH_LEN = HASH().digest_size # 32
def hkdf_extract(salt: bytes, ikm: bytes) -> bytes:
if salt is None or len(salt) == 0:
salt = b"\x00" * HASH_LEN
return hmac.new(salt, ikm, HASH).digest()
def hkdf_expand(prk: bytes, info: bytes, length: int) -> bytes:
if info is None:
info = b""
if length < 0 or length > 255 * HASH_LEN:
raise ValueError(f"length must be in 0..{255 * HASH_LEN}")
n = -(-length // HASH_LEN) # ceil division
okm = b""
t = b""
for i in range(1, n + 1):
t = hmac.new(prk, t + info + bytes([i]), HASH).digest()
okm += t
return okm[:length]
if __name__ == "__main__":
# RFC 5869 Appendix A.1: Test Case 1 (Basic test case with SHA-256)
ikm = bytes.fromhex("0b" * 22)
salt = bytes.fromhex("000102030405060708090a0b0c")
info = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9")
length = 42
prk = hkdf_extract(salt, ikm)
okm = hkdf_expand(prk, info, length)
print("PRK =", prk.hex())
print("OKM =", okm.hex())
# cross-check against the `cryptography` library's HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
ref = HKDF(algorithm=hashes.SHA256(), length=length, salt=salt, info=info).derive(ikm)
print("ref =", ref.hex())
print("okm matches cryptography-lib HKDF:", okm == ref)
# default-salt path: salt=None must behave like salt of HASH_LEN zero bytes
prk_default = hkdf_extract(None, ikm)
prk_zero_salt = hkdf_extract(b"\x00" * HASH_LEN, ikm)
print("default-salt PRK matches explicit zero-salt PRK:", prk_default == prk_zero_salt)
# max-length boundary check
try:
hkdf_expand(prk, info, 255 * HASH_LEN + 1)
print("no error raised (BUG)")
except ValueError as e:
print("length overflow correctly rejected:", e)
Output (verified against RFC 5869 Appendix A.1 Test Case 1, the standard's own basic SHA-256 vector, and cross-checked against the cryptography library's independent HKDF implementation):
PRK = 077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5
OKM = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865
ref = 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865
okm matches cryptography-lib HKDF: True
default-salt PRK matches explicit zero-salt PRK: True
length overflow correctly rejected: length must be in 0..8160
Key points
hkdf_extracttreatssaltas the HMAC KEY andikm(input keying material) as the message; per the RFC, a missing or empty salt is replaced with a string ofHashLenzero bytes (32 for SHA-256), never with an empty key, since HMAC's key-handling for an empty key is a different code path than "key of zero bytes."hkdf_expandbuildsOKM(output keying material) by chainingT(i) = HMAC(PRK, T(i-1) || info || byte(i)), starting fromT(0) = empty string, and concatenatingT(1), T(2), ...until enough bytes are produced, then truncating to exactlylength. The one-byte counter is what bounds the maximum output length: it can only take values1..255.- The maximum-length constraint follows directly from the counter: with a 32-byte hash, the absolute ceiling is
255 * 32 = 8160bytes; requesting more is a caller error the function must reject, not silently truncate or wrap. - Safe defaults:
saltshould be a fresh random value when the calling protocol can provide one (it adds domain separation between uses of the same underlying secret), andinfoshould be a short, protocol-specific, human-readable context string (e.g.b"my-protocol v1 encryption key") that differs between every logically distinct derived key drawn from the samePRK, so that deriving an encryption key and a MAC key from the same secret never produces the same bytes.
Complexity
hkdf_extract is a single HMAC call: O(len(ikm)) time, O(HashLen) space. hkdf_expand performs ceil(length / HashLen) HMAC calls, each over a bounded-size input (one hash-length block plus info plus one byte), so time is O(length) and space is O(length) for the accumulated output (or O(HashLen) if the caller streams it in hash-length chunks rather than buffering the whole result, which this reference implementation does not do, for clarity).
Edge cases
length == 0: the loop runs zero times andhkdf_expandcorrectly returns an empty string, which the implementation above already handles becausen = ceil(0 / 32) = 0.length > 255 * HashLen: must raise, not silently cap or wrap the counter byte, since a wrapped counter would produce colliding/predictable output blocks; the reference implementation checks this explicitly before the loop starts.salt = Noneorsalt = b"": both must be treated identically (substituted withHashLenzero bytes), verified above by confirming the default-salt code path produces the exact samePRKas an explicit all-zero salt of the correct length.
You are designing a low-latency microservices mesh that requires mutual authentication and encrypted channels between services handling millions of requests per second. Recommend cryptographic handshake designs (e.g., 0-RTT, resumed sessions), appropriate algorithms, session caching, and techniques to limit CPU cost while preserving forward secrecy and preventing replay attacks.
Sample Answer
Direct answer
At millions of requests per second the goal is to make the expensive asymmetric (public-key) handshake happen as rarely as possible per pair of communicating services, while every connection is still mutually authenticated and forward-secret. Do one full mTLS (mutual TLS: both sides present and verify a certificate) handshake with an ephemeral (EC)DHE key exchange per service-pair "warm" period, then resume the great majority of connections from a cached session so per-connection cost is dominated by cheap symmetric AEAD (authenticated encryption with associated data) operations, not public-key math, and amortize even that over many requests per connection with keep-alive/multiplexing.
Structured elaboration
- Identity. Short-lived, workload-scoped certificates (SPIFFE/SPIRE-style X.509 identities, for example: SPIFFE/SPIRE is an open-source identity framework that automatically issues and rotates a short-lived certificate per workload, rather than a brand of certificate itself) issued by a mesh-internal CA (certificate authority) rather than a public one, rotated on the order of minutes to hours so a leaked certificate self-expires quickly. Mutual authentication happens on the full handshake, where both sides present and verify one of these certificates.
- Algorithm choice for the full handshake. TLS 1.3, X25519 for the (EC)DHE key exchange (a small, fixed-cost elliptic-curve scalar multiplication rather than a large modular exponentiation, so it is structurally far cheaper per operation than an RSA-2048 private-key operation), and an AEAD (authenticated encryption with associated data) cipher, AES-128-GCM where AES-NI hardware acceleration is available, ChaCha20-Poly1305 otherwise.
- Session resumption. After the first full handshake between a pair of proxies, the server issues a resumption PSK (pre-shared key: a symmetric secret both sides already share) via a session ticket. Resumed connections should use TLS 1.3's
psk_dhe_kemode, PSK combined with a fresh ephemeral (EC)DHE contribution, rather than pure PSK, so a resumed session still gets forward secrecy at the cost of one cheap EC operation instead of a full certificate-based handshake with signature verification. - 0-RTT (zero round-trip time: sending encrypted application data in the very first flight, before the handshake finishes). Reserve it for calls that are idempotent by construction (health checks, cache reads, metrics scrapes), paired with the same anti-replay discipline as an early-data design: a single-use ticket check plus an idempotency key at the application layer. Never use it for anything that mutates state, since 0-RTT data is replayable by construction and is not forward-secret until the rest of the handshake, including the fresh DH contribution, completes.
- Connection reuse. Multiplex many RPCs over one already-authenticated HTTP/2 or gRPC connection with keep-alive. This dominates the CPU-cost goal more than any single algorithm choice, because it amortizes the (already cheap, resumed) handshake cost over many requests instead of paying it per request.
- Session/ticket key management. Rotate the server's ticket-encryption key on a schedule (for example hourly) while still accepting tickets from the last one or two rotations, and shard the resumption cache per worker/core rather than sharing one global cache, trading a slightly lower resumption hit rate for far less lock contention at this request volume.
Worked example
A back-of-envelope estimate for how rare a full handshake can be made, with the assumptions stated explicitly:
full handshakes/s=avg. requests reused per connectiontotal requests/sAssume a mesh doing 2{,}000{,}000 requests/s in total, and that each established, kept-alive connection between a given pair of proxies serves 500 requests on average before being recycled:
5002,000,000=4,000 new connections/sOf those 4,000 new connections per second, only the ones outside their pair's resumption window need a full asymmetric handshake; the rest resume via psk_dhe_ke, which is one EC operation, not a certificate verification plus signature. If, say, only 5% of new connections fall outside the resumption window (a stated assumption, not a measurement), that is 200 full handshakes/s mesh-wide, versus 2,000,000 encrypted requests/s: the expensive operation is pushed roughly four orders of magnitude below the request rate purely through connection reuse and resumption, before any algorithm-level optimization is even considered.
Trade-offs and pitfalls
- Resuming with pure PSK (no fresh DH contribution) forfeits forward secrecy for that session's data if the resumption secret is later stolen. Prefer
psk_dhe_keunless the CPU budget genuinely cannot afford the extra EC operation. - A stolen resumption PSK or ticket-encryption key has a larger blast radius than a single leaked session key, since it lets an attacker resume or forge tickets against many connections until the next rotation; this is why rotation windows matter more here than in a typical public-facing service.
- A single shared session/ticket cache becomes a lock-contention bottleneck at this request volume; sharding per worker trades a slightly lower resumption hit rate for removing that contention.
- Enabling 0-RTT on every service-to-service call rather than only idempotent ones reintroduces the exact replay class that a stateful anti-replay design has to defend against, and at mesh scale that replay surface is much larger than a single public API endpoint.
Some cross-functional work benefits from a standing recurring ritual rather than ad hoc meetings, for example a regular review or working session that brings the same group together on a schedule. Walk me through how you'd design one from scratch: who's in the room, how often it runs, and how you'd know it's actually working.
Sample Answer
Direct answer
Start from the decision the ritual has to produce, not the calendar slot. Invite only the people who can actually make or unblock that decision, not everyone with an interest in the topic. Set the cadence to match how fast the underlying work changes, and instrument the ritual itself so you can tell whether it is producing decisions or just producing a meeting.
Structured elaboration
- Name the single output first. Before picking attendees or a cadence, write down the one decision or artifact the ritual exists to produce (for example, "which cross-team dependencies get prioritized this cycle"). If you cannot name it, you are designing a status meeting, not a working ritual.
- Minimum viable roster. Invite decision-owners, not stakeholders who only want visibility. A rule of thumb: if someone in the room has to say "let me check with my team" before committing to anything, they are a proxy, not an owner, and the room is one person too big.
- Cadence tied to decision half-life. Match the frequency to how fast the thing being decided actually changes, not to habit. Too frequent and there is nothing new to decide between sessions; too infrequent and blockers age past the point where the ritual could have caught them early.
- Session shape. Require light pre-work (so room time is spent deciding, not getting everyone up to speed), time-box the agenda to the decision at hand, and keep a running decision log so the group is not re-litigating the same question every time.
- How you would know it is working (leading indicators, not attendance):
| Signal | What it means it is healthy | What decay looks like |
|---|---|---|
| Decisions logged per session | Room is resolving things, not deferring them | Every item gets "let's take this offline" |
| Attendee mix | Mostly decision-owners | Mostly proxies or spectators |
| Time from flagged to resolved | Short, items do not sit | Items raised in one session reappear unresolved next time |
| Pre-work completion | People show up prepared | Pre-reads are consistently skipped |
| Reaction to a cancelled session | Someone objects, the ritual was load-bearing | Nobody notices, it was status theater |
Worked example
Say the ritual is a recurring dependency review for a platform initiative touching four delivery teams. The roster is the four team leads plus the program owner as facilitator, five to six people, not the fifteen who are merely affected. The teams plan in two-week sprints, so a dependency raised today needs to be resolved before the next sprint's planning starts or it blocks that team. That reasoning sets the floor: the review has to run at least once per sprint, so biweekly, thirty minutes, is the minimum cadence that keeps blockers from aging past one planning cycle. A weekly cadence would mean showing up with nothing new most weeks; a monthly one would let a blocker sit for up to two sprints before anyone with authority to fix it even hears about it.
Trade-offs & pitfalls
- The most common wrong turn is defaulting the invite list to "everyone affected." The ritual becomes a broadcast, decision-owners tune out because nothing gets decided with fifteen people in the room, and the ritual quietly becomes theater.
- Choosing cadence by convention ("let's do it weekly like standup") instead of the decision's actual refresh rate produces either a hollow meeting or a slow one, and both erode trust in the ritual over time.
- Junior candidates describe running the meeting well. Senior candidates describe designing the meeting so it can be evaluated and retired: a built-in check for whether it is still adding value, and a plan for what replaces it if it is not.
- Skipping the decision log is a quiet failure mode: without a record of what was already decided and why, the group re-opens the same debate every session and the ritual's real cost shows up as fatigue, not as an obvious complaint.
Implement a constant-time modular exponentiation routine or describe in detail a constant-time algorithm for modular exponentiation (e.g., for RSA or Diffie-Hellman) that avoids secret-dependent branches and memory accesses. Explain how to choose a sliding-window or fixed-window approach that preserves constant-time properties, and how to test for timing leaks.
Sample Answer
Direct answer
Use a Montgomery ladder: for every bit of the exponent, regardless of whether that bit is 0 or 1, perform exactly one squaring, one multiplication, and one conditional swap chosen with a branch-free mask rather than an if. A naive square-and-multiply implementation skips the multiplication step entirely on a 0-bit, so the number of multiplications it performs, and therefore its running time and instruction trace, directly reveals the exponent's Hamming weight (the number of 1-bits) and, with more work, the exponent's actual bit pattern. The ladder removes that data dependency by always doing the same fixed amount of work per bit position, selecting which intermediate value is "active" with a mask instead of a branch.
Structured elaboration
Approach
- Maintain two running values, r0 and r1, initialized to 1 and the base. The invariant is that at each step, r1=base×r0 in the group, so advancing by one exponent bit means either squaring r0 (if the bit is 0) or squaring r1 while also folding it into r0 (if the bit is 1), but written branch-free: swap the pair into a canonical order using a constant-time conditional swap keyed on the bit, perform the SAME squaring-plus-multiplication step unconditionally, then swap back.
- A constant-time conditional swap is built from a bitmask, not a comparison: given a 0/1 selector, build an all-zero or all-one mask arithmetically and use it to blend the two candidate values with bitwise AND/OR, so no branch instruction, and no data-dependent memory address, is involved in the selection itself.
- A fixed-window variant works the same way at a coarser granularity: instead of one bit at a time, process k bits at a time using a lookup table of precomputed powers, selected with the same constant-time masked-selection technique (touch every table entry, mask out the ones you don't want) rather than direct indexing, since direct indexing by a chunk of the exponent reopens the exact secret-indexed-memory-access problem constant-time code exists to avoid. A sliding window (choosing window boundaries based on where the exponent's bits happen to be non-zero) is faster on average, but that variable positioning is itself secret-dependent and is NOT safe for a secret exponent, a fixed window (identical window boundaries regardless of exponent value) is what constant-time implementations use.
- To test for timing leaks: the same statistical CI (continuous integration)-gate approach used to verify any constant-time implementation applies here, and additionally, operation-count analysis (as in the worked example below) is a useful non-statistical sanity check specific to modular exponentiation, since it directly measures the property that matters (does the multiply count depend on the exponent) without needing any timing measurement at all.
Worked example
import random
def naive_modpow(base, exponent, modulus, counter=None):
"""Textbook square-and-multiply. Branches on each secret exponent bit."""
result = 1
base = base % modulus
for bit in bin(exponent)[2:]: # MSB to LSB
result = (result * result) % modulus
if counter is not None:
counter[0] += 1 # the squaring always happens
if bit == '1':
result = (result * base) % modulus
if counter is not None:
counter[0] += 1 # the multiply is SKIPPED for a 0-bit
return result
def cswap(swap, a, b):
"""Constant-time conditional swap: always touches both a and b, chooses the
result with a mask rather than a data-dependent branch."""
mask = -swap # swap in {0,1} -> mask is 0 (0b000...0) or -1 (0b111...1)
a2 = (a & ~mask) | (b & mask)
b2 = (b & ~mask) | (a & mask)
return a2, b2
def ladder_modpow(base, exponent, modulus, bit_length, counter=None):
"""Montgomery ladder: for EVERY bit position, regardless of its value, perform
exactly one squaring, one multiplication, and one conditional swap."""
base = base % modulus
r0, r1 = 1, base
for i in range(bit_length - 1, -1, -1):
bit = (exponent >> i) & 1
r0, r1 = cswap(bit, r0, r1) # bring the "active" pair into (r0, r1)
r1 = (r0 * r1) % modulus
r0 = (r0 * r0) % modulus
r0, r1 = cswap(bit, r0, r1) # swap back to the canonical slots
if counter is not None:
counter[0] += 2 # one square + one multiply, every bit, always
return r0
MODULUS = 3233 # toy RSA-style modulus (61 * 53), small on purpose for a readable demo
BIT_LEN = MODULUS.bit_length()
rng = random.Random(2026) # seeded for reproducibility
print(f"modulus = {MODULUS} ({BIT_LEN}-bit), base fixed at 7\n")
print(f"{'exponent':>10} {'bin(exponent)':>14} {'naive result':>13} {'ladder result':>14} {'match':>6} {'naive mults':>12} {'ladder mults':>13}")
for _ in range(6):
exponent = rng.randrange(1, MODULUS)
expected = pow(7, exponent, MODULUS)
naive_counter = [0]
naive_result = naive_modpow(7, exponent, MODULUS, naive_counter)
ladder_counter = [0]
ladder_result = ladder_modpow(7, exponent, MODULUS, BIT_LEN, ladder_counter)
assert naive_result == expected == ladder_result, "mismatch against pow()"
print(f"{exponent:>10} {bin(exponent)[2:]:>14} {naive_result:>13} {ladder_result:>14} "
f"{'yes' if naive_result == ladder_result else 'no':>6} {naive_counter[0]:>12} {ladder_counter[0]:>13}")
print("\nAll results agree with pow(base, exponent, modulus). Naive's multiply count")
print("tracks the exponent's Hamming weight (varies per call); the ladder's is a")
print(f"constant 2 * {BIT_LEN} = {2*BIT_LEN} for every exponent of this bit-length.")
Output:
modulus = 3233 (12-bit), base fixed at 7
exponent bin(exponent) naive result ladder result match naive mults ladder mults
488 111101000 1826 1826 yes 14 24
1309 10100011101 1527 1527 yes 17 24
2059 100000001011 59 59 yes 16 24
2097 100000110001 3164 3164 yes 16 24
2651 101001011011 2105 2105 yes 19 24
421 110100101 2020 2020 yes 14 24
All results agree with pow(base, exponent, modulus). Naive's multiply count
tracks the exponent's Hamming weight (varies per call); the ladder's is a
constant 2 * 12 = 24 for every exponent of this bit-length.
Every ladder result matches both naive_modpow and Python's own pow(), confirming correctness. The naive multiply count (14, 17, 16, 16, 19, 14) visibly tracks each exponent's number of 1-bits, exactly the secret-dependent operation count the attack exploits; the ladder's count is a flat 24 every single time, for every one of these different secret exponents. Operation count is used here instead of wall-clock timing specifically because wall-clock numbers are environment-dependent and not reproducible; the exact number of multiply calls executed is a deterministic, verifiable proxy for the same underlying property.
Complexity and edge cases
- Both implementations run in O(n) modular multiplications for an n-bit exponent; the ladder's constant factor (always 2 multiplications per bit) is at worst 2x the naive version's best case (an all-zero exponent) and equal to its worst case (an all-one exponent), so the safety comes essentially for free relative to naive's own worst case.
- Edge cases: an exponent of 0 (the ladder must still iterate over the full fixed bit-length, producing 1, not skip iterations), a base that shares a factor with the modulus (the algorithm still executes the same fixed sequence of operations; correctness of the underlying group arithmetic is a separate concern from the constant-time property), and the CHOICE of bit-length itself, it must be fixed to the maximum possible exponent size for the key type in use, not derived from the actual secret exponent's bit length, or the number of loop iterations itself becomes a secret-dependent leak.
Trade-offs and pitfalls
The ladder's fixed per-bit cost is the whole point, but it means giving up the naive version's free win on exponents with few 1-bits, which is a real, measurable performance cost for the common case, not merely a worst-case one. A fixed window trades a further slowdown (more table storage, one masked-selection scan per window) for meaningfully fewer loop iterations than the pure single-bit ladder; a SLIDING window recovers more speed but does so by making window boundaries a function of the exponent's actual bit pattern, exactly the leak this whole exercise exists to remove, so it is safe for a public exponent (RSA encryption/verification) but never for a private one (RSA decryption/signing, Diffie-Hellman key agreement). The most common implementation pitfall is writing the branch-free arithmetic correctly in source and then trusting it stays that way after compilation; the same compiler-hazard verification principle applies directly here: disassemble the actual compiled output per target rather than trusting the source pattern.
Pick one of your protocol-design contributions and walk me through it: who the participants were, the high-level message flow, the primitives and key-management approach you chose, and the threat model you were defending against. Then justify each cryptographic choice: why did it meet the security properties and performance goals you needed?
Sample Answer
Direct answer
A strong answer narrates one protocol you actually shaped: who talks to whom, the shape of the message exchange at a level someone could sketch on a whiteboard, which primitives and key-management approach you chose, the threat model you were defending against, and, critically, a justification tying each choice back to a specific security property or performance number rather than "it's standard practice." The interviewer is scoring whether you can connect a design decision to the property it buys, in both directions: given the property, why this primitive, and given the primitive, what property it actually guarantees.
Structured elaboration
- Participants: who is on each end of the protocol, and whether there's a third party, a server, an auditor, a signing authority, with its own trust role.
- Message flow: the high-level exchange, described as steps ("device sends a nonce and its identity, server responds with an ephemeral key and a signature over both nonces") rather than a wall of prose.
- Primitives and key management: what key-exchange, authentication, and key-derivation choices were made, and where long-term keys live versus session keys.
- Threat model: an explicit statement of what the adversary can do, read traffic, modify traffic, replay old messages, compromise one endpoint, and just as importantly, what they can't.
- Justification: for each primitive, name the property it buys (forward secrecy: a compromise of a long-term key later doesn't expose past session traffic; replay protection: an attacker who captured a past message can't resend it to trick the system into repeating an action; mutual authentication: both sides prove their identity to each other, not just the client checking the server) and the cost it imposes (an extra round trip, extra bytes, extra processing), and say why that trade held for this system.
Worked example (illustrative, not a specific real case)
Take a protocol for pushing signed firmware updates to network devices in the field. Participants: the device, an update server, and an offline signing authority that never touches the network directly. Message flow: the device requests the latest version number, the server returns a signed manifest and the encrypted update package, and the device verifies the signature against a key baked in at manufacturing before installing anything. Primitives: an offline long-term signing key held by the signing authority, a per-release ephemeral encryption key for the package itself, and a hash-based integrity check on the decompressed firmware. Threat model: an attacker who fully controls the network path and can serve a malicious update, but who does not have access to the offline signing key or physical access to the device. Justification: the offline signing key means a compromised update server can serve stale or bad content but cannot forge a valid update, the property this system actually needed given that the update server, not the signing authority, was the internet-facing component. The cost was an extra manual step in the release pipeline to get each manifest signed offline, accepted because the alternative, an online signing key, would have turned the update server into a single point of total compromise.
Trade-offs and pitfalls
- Describing primitives without connecting them to a threat they defend against reads as vocabulary, not design. "I used a signature" is incomplete; "I used an offline signature so a compromised online server can't forge updates" is the actual answer.
- A protocol narrative with no accepted cost anywhere is implausible; every real design gives something up, latency, operational complexity, a manual step, for a property.
- Confusing "the threat model I defended against" with "every threat that theoretically exists" is a common overreach; naming what's explicitly out of scope, physical access, a compromised signing authority, is part of a mature answer.
Design an audit-logging architecture for all key-management operations (create, use, rotate, export, delete) that meets compliance expectations and enables detection of anomalous behavior, without leaking key material into the logs themselves. Define the event types and mandatory fields, your redaction/hashing strategy for sensitive fields, retention and access controls, log integrity protections (tamper-evident or append-only, HSM-signed), and how this feeds a SIEM.
Sample Answer
Direct answer
Log the operation and its context, never the key material or anything that could reconstruct it: every event carries a principal, a key identifier and version, never the key itself, the action, the result, and enough request context to answer who touched what, from where, with what outcome. Make the log tamper-evident, append-only and cryptographically chained or HSM-signed, and route it into a SIEM with baseline-deviation rules, since catching a compromised credential misusing a key is mostly a question of whether behavior looks unusual for that specific principal and that specific key.
Structured elaboration
Event types and mandatory fields
- Event types: KeyCreated, KeyUsed tagged by operation (Encrypt, Decrypt, Sign, Verify, Wrap, Unwrap), KeyRotated, KeyExported, KeyArchived, KeyDeletionScheduled and KeyDeleted, PolicyChanged, and AccessDenied.
- Mandatory fields on every event: timestamp from a synchronized clock, principal identity and how it authenticated, source network or service identity, key identifier and version, the operation attempted, request context (which resource the operation was protecting, where known), result with a coarse reason, and a correlation ID tying the event to the calling application's own trace.
Redaction and hashing strategy
- Never log plaintext key material, DEKs, wrapped-key ciphertext bytes, or the plaintext or ciphertext of the data being protected.
- Log the key ID and version, a stable, non-secret handle, instead. Where you need to detect duplicate or reused metadata without exposing the key, hash the metadata itself, never the key material, since a high-entropy key doesn't need protecting by hashing and there is no reason to have logged it in the first place.
- Truncate, tokenize, or hash any privacy-sensitive request context (a customer identifier, a document name) per the org's existing log-classification rules, key-management logs are not exempt from those rules just because their primary purpose is security.
Retention and access controls
- Retain for the strictest applicable requirement, commonly a year or more for key-management events specifically, since an investigation into a compromise discovered late needs history that predates discovery.
- Access to the log store is a separate, narrower permission from access to the KMS itself: an auditor can read logs with no operational KMS permission, and a key-admin cannot modify or delete log entries, ever, that separation is what makes the log trustworthy as evidence rather than something the person under investigation could have edited.
Log integrity protections
- Append-only storage, write-once or a storage class that structurally disallows overwrite or delete within the retention window, so a compromised admin credential can't cover its tracks by editing history.
- Tamper evidence via hash-chaining, each entry carries the hash of the previous one so any retroactive edit breaks the chain, or HSM-signed batches of entries, giving a cryptographic guarantee of integrity, not just an access-control one.
- Ship logs to a destination outside the blast radius of the systems being audited, a separate account or administrative domain, so compromising the KMS's own infrastructure doesn't also compromise the record of what happened on it.
Feeding a SIEM and detecting anomalous behavior
- Stream every event, not a sampled subset, to the SIEM in near real time; sensitive-operation logs are exactly the low-volume, high-value signal where sampling would silently throw away the one event that mattered.
- Alert specifically on: a key-admin or backup-operator credential performing an actual decrypt or sign operation, which should structurally never happen if role separation is enforced, so its mere occurrence is a red flag regardless of intent; a sudden volume spike in decrypt or export operations from a single principal or an unfamiliar source network; a burst of access-denied events followed by a success, a classic credential-guessing or privilege-escalation pattern; unusually high key usage immediately preceding a scheduled deletion; and access from a geography or network segment that principal has never used before.
- Baseline these per principal and per key, not globally, since normal decrypt volume for a high-traffic service and a rarely-used administrative key are wildly different, and a single global threshold either misses real anomalies or drowns the SIEM in false positives.
Worked example
If a service principal's decrypt calls for a specific key have averaged 500 an hour over the trailing 30 days, an hour with 5,000 calls from that same principal against the same key, ten times its own established baseline, especially if paired with an unfamiliar source network, is exactly the kind of signal that should page someone. The trigger isn't that 5,000 calls an hour is inherently suspicious in isolation, it's that it is a tenfold deviation from that specific principal's own pattern.
Trade-offs and pitfalls
Common wrong turn: logging the wrapped key ciphertext just in case it's useful for debugging, which expands the log store's own blast radius for no operational benefit, since a wrapped key that later needs debugging can be re-fetched from the KMS by an authorized principal instead. Common wrong turn: a single global anomaly threshold across all principals and keys, which either misses a genuine deviation for a low-volume key or generates so many false positives on a high-volume one that the alert gets tuned out. Senior signal: designing what is not logged, the redaction list, with the same rigor as what is logged, an architecture that only thinks about coverage and never about leakage tends to eventually log the thing it was built to protect.
A service signs the exact same message under RSA to several different recipients, each with their own public key but the same small exponent e = 3, and without randomized padding. Explain what's wrong with this setup, name the attack it exposes the service to, and what you'd change about the exponent choice or the padding scheme to fix it.
Sample Answer
Direct answer
Broadcasting the identical message under RSA to several recipients, each with a different modulus but the same tiny public exponent e=3, and no randomized padding, lets an attacker who intercepts all the ciphertexts reconstruct the exact integer m3 using the Chinese Remainder Theorem (CRT) and then simply take an integer cube root. No factoring, and no private key, is needed at all. This is Hastad's broadcast attack.
Structured elaboration
For each recipient i, the attacker sees ci=m3modNi. Because the moduli N1,N2,N3 are (with overwhelming probability) pairwise coprime, the Chinese Remainder Theorem guarantees a unique value modulo the product:
M≡m3(modN1N2N3)The key observation is that this is not just "M equals m3 reduced by something", once you have three moduli of similar size and a message small enough that the true integer m3 is already smaller than N1N2N3 (which happens easily in practice, since m3 is one modulus-width and the product of three moduli is roughly three modulus-widths), CRT reconstruction gives back that exact integer, no modular reduction ever occurred. An ordinary integer cube root then recovers m directly:
m=3MWhy the exponent choice alone doesn't fix this. This generalizes to any fixed low exponent broadcast to at least e recipients with distinct, coprime moduli, an unpadded message, no matter what e is, is vulnerable the same way once enough copies are collected; a larger e (like the standard e=65537) just raises the number of colluding ciphertexts an attacker would need from 3 to 65537, astronomically impractical for a real broadcast, but the underlying algebraic weakness, determinism plus no randomization, is exactly the same regardless of e.
Worked example
Three small, distinct moduli, message m=4321, exponent e=3:
import math
def is_prime(n):
if n < 2: return False
for i in range(2, int(math.isqrt(n)) + 1):
if n % i == 0: return False
return True
def crt(residues, moduli):
M = 1
for m in moduli: M *= m
total = 0
for r, m in zip(residues, moduli):
Mi = M // m
total += r * Mi * pow(Mi, -1, m)
return total % M
primes = [p for p in range(200, 400) if is_prime(p)]
chosen = primes[:6]
N1, N2, N3 = chosen[0]*chosen[1], chosen[2]*chosen[3], chosen[4]*chosen[5]
print("moduli: N1=%d N2=%d N3=%d (built from primes %s)" % (N1, N2, N3, chosen))
e = 3
m = 4321 # the SAME plaintext, broadcast to all three recipients, unpadded
c1, c2, c3 = pow(m, e, N1), pow(m, e, N2), pow(m, e, N3)
print("ciphertexts: c1=%d c2=%d c3=%d" % (c1, c2, c3))
M = crt([c1, c2, c3], [N1, N2, N3])
print("CRT-combined value M =", M, " (equals m^3 exactly:", M == m**e, ")")
def integer_cube_root(v):
x = round(v ** (1/3))
for cand in (x-2, x-1, x, x+1, x+2):
if cand**3 == v:
return cand
print("recovered plaintext m =", integer_cube_root(M), " (true m was", m, ")")
Running this prints:
moduli: N1=47053 N2=51983 N3=55687 (built from primes [211, 223, 227, 229, 233, 239])
ciphertexts: c1=23831 c2=4144 c3=24545
CRT-combined value M = 80677568161 (equals m^3 exactly: True )
recovered plaintext m = 4321 (true m was 4321 )
The CRT-combined value comes back exactly equal to 43213, confirming no modular reduction happened, and the integer cube root recovers 4321 precisely. Real RSA moduli run to hundreds of decimal digits rather than five or six, but the attack's mechanics are identical at any size, this is a pure integer/CRT argument, not a size-dependent cryptanalysis.
Trade-offs and pitfalls
The actual fix is randomization, not exponent size: migrate to Optimal Asymmetric Encryption Padding (OAEP), whose random seed makes the padded value different at every recipient even for an identical underlying plaintext, which breaks the CRT reconstruction outright, the values being combined are no longer literally equal across recipients, so there is no single m3 to recover. If changing the padding scheme genuinely isn't an option, moving only to a larger conventional exponent like 65537 is not sufficient by itself, as shown above. The cleanest protocol-level fix is to never encrypt the same payload directly to multiple public keys at all: generate a fresh, unique symmetric session key per recipient and use RSA only to wrap that small, unique key (hybrid encryption), so there is never an identical plaintext to collude across in the first place.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths