Senior Cryptographer Interview Preparation Guide for DoorDash
DoorDash's cryptographer interview process typically follows a multi-stage format designed to assess deep mathematical expertise, practical cryptographic implementation skills, protocol design thinking, and system-level security architecture. The process emphasizes both theoretical knowledge and real-world application, with particular attention to threat modeling, algorithm selection, and secure system design. Senior-level candidates are expected to demonstrate leadership in cryptographic decisions and ability to mentor other engineers.
Interview Rounds
Recruiter Screening
What to Expect
Initial 30-minute call with recruiter to confirm role fit, discuss background, and answer procedural questions. May include a brief follow-up recruiter call to discuss next steps after initial technical screening. This round assesses communication skills, enthusiasm for the role, and alignment with DoorDash's mission.
Tips & Advice
Prepare a clear 2-3 minute summary of your cryptography background and why you're interested in the role. Research DoorDash's business model and how security/cryptography impacts their platform. Be ready to discuss your availability and any scheduling constraints. Ask thoughtful questions about the team structure and current cryptographic challenges.
Focus Topics
Understanding DoorDash's Security Challenges
Knowledge of DoorDash's business model (food delivery platform) and associated security/cryptography needs (payment processing, user data protection, merchant trust, etc.).
Practice Interview
Study Questions
Background and Experience Summary
Concise overview of your cryptography background, major projects, key achievements, and areas of expertise.
Practice Interview
Study Questions
Career Motivation and Fit
Articulate why you're interested in cryptography, why DoorDash specifically, and what attracts you to their engineering culture.
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
60-minute technical phone interview with a senior engineer or cryptographer. Assesses fundamental cryptographic knowledge, problem-solving approach, and ability to communicate complex concepts clearly. Typically covers symmetric/asymmetric cryptography, hash functions, key management concepts, and potentially a code-based cryptographic implementation challenge.
Tips & Advice
Have a laptop ready for any coding components. Be prepared to discuss cryptographic concepts with mathematical rigor but also practical clarity. Use a whiteboard or collaborative tool if code is required. Explain your thinking step-by-step. For senior candidates, interviewers expect you to reason about security properties, not just implement algorithms. If you don't know an answer, acknowledge it honestly and discuss how you'd approach learning it. Reference concrete examples from your work.
Focus Topics
Key Management Principles
Key generation, rotation policies, storage mechanisms (HSMs, key servers), access controls, and lifecycle management. Understanding of key derivation and key separation.
Practice Interview
Study Questions
Security Analysis and Threat Modeling
Identifying cryptographic vulnerabilities in systems, understanding attack vectors (brute force, side-channel, cryptanalysis), and assessing algorithm choices against threats.
Practice Interview
Study Questions
Symmetric Encryption Fundamentals
Deep understanding of symmetric algorithms (AES, ChaCha20), block cipher modes of operation (ECB, CBC, CTR, GCM), key derivation functions, and when to use each.
Practice Interview
Study Questions
Asymmetric Encryption and Key Exchange
RSA, elliptic curve cryptography (ECC), Diffie-Hellman, and modern key exchange protocols. Understanding of public key infrastructure (PKI) and certificate management.
Practice Interview
Study Questions
Cryptographic Hashing and Message Authentication
Cryptographic hash functions (SHA-256, SHA-3), their security properties, and applications. Message Authentication Codes (MACs) and authenticated encryption (AEAD).
Practice Interview
Study Questions
Technical Interview - Cryptographic Implementation and Analysis
What to Expect
90-minute onsite technical interview focusing on cryptographic algorithm implementation, code review of real cryptographic code, and security analysis. May involve implementing a simplified cipher, analyzing existing cryptographic code for vulnerabilities, or designing a protocol with specific security properties. Evaluates both theoretical knowledge and practical engineering skills.
Tips & Advice
Expect to write actual code (likely Python, C, or Go depending on interviewer preference). Focus on security properties over elegance—discuss why you're making each implementation choice. Be prepared to identify common cryptographic pitfalls (improper mode usage, weak RNG, timing attacks, etc.). For senior candidates, interviewers want to see architectural thinking: how does this fit into a larger system? What are the security guarantees and failure modes? Ask clarifying questions about requirements and constraints.
Focus Topics
Side-Channel Attack Resistance
Understanding and mitigating side-channel attacks (timing, power analysis, electromagnetic). Implementing constant-time operations and understanding hardware considerations.
Practice Interview
Study Questions
Cryptographic Protocol Design
Designing simple protocols with specific security properties (confidentiality, authenticity, forward secrecy). Understanding of protocol composition and potential interaction issues.
Practice Interview
Study Questions
Code Review and Vulnerability Assessment
Analyzing cryptographic code for vulnerabilities, understanding CVEs in cryptographic libraries, and recommending fixes. Experience with cryptographic library selection (OpenSSL, libsodium, etc.).
Practice Interview
Study Questions
Cryptographic Algorithm Implementation
Implementing simplified versions of well-known algorithms or protocols. Understanding algorithm structure, mathematical operations required, and security-critical implementation details.
Practice Interview
Study Questions
Secure Coding Practices for Cryptography
Avoiding common pitfalls: timing attacks, improper randomness, buffer overflows, side-channel vulnerabilities, weak parameter choices, incorrect IV/nonce usage.
Practice Interview
Study Questions
Technical Interview - Protocol Design and Security Analysis
What to Expect
90-minute onsite interview focusing on designing cryptographic protocols for real-world scenarios and conducting security analysis of systems. May involve designing an authentication protocol, analyzing a communication protocol for vulnerabilities, or creating a secure key exchange mechanism for a specific threat model. Tests ability to think about cryptography in context of complete systems.
Tips & Advice
Think out loud about security assumptions and threat models. Draw diagrams of protocol flows. Explicitly state what properties you're achieving (confidentiality, authenticity, forward secrecy, etc.) and what you're not claiming. For senior roles, interviewers expect you to consider practical deployment: how is this used in production? What happens if X fails? Be familiar with formal protocol analysis and standard protocol mistakes. Reference real protocols (TLS, Signal, WireGuard) when appropriate.
Focus Topics
Cryptographic Agility and Algorithm Evolution
Designing protocols that accommodate algorithm changes, transitioning to post-quantum cryptography, managing algorithm deprecation, and version negotiation.
Practice Interview
Study Questions
Forward Secrecy and Key Exchange Protocols
Designing protocols with forward secrecy properties, ephemeral key generation, session key derivation, and resistance to key compromise. Understanding Double Ratchet and similar constructs.
Practice Interview
Study Questions
Threat Modeling and Protocol Security Analysis
Identifying attack scenarios, analyzing protocol robustness against specific threats, understanding Dolev-Yao model, formal verification of security properties.
Practice Interview
Study Questions
Cryptographic Protocol Design Methodology
Systematic approach to designing secure protocols: defining security goals, identifying trust boundaries, selecting appropriate primitives, considering attack scenarios, and formal verification approaches.
Practice Interview
Study Questions
Authentication Protocol Design
Designing protocols that establish and verify identity: challenge-response protocols, zero-knowledge proofs, signature-based authentication, multi-party authentication.
Practice Interview
Study Questions
System Design Interview - Cryptographic Architecture
What to Expect
90-minute onsite interview on designing large-scale cryptographic systems from scratch. Likely scenarios: designing end-to-end encryption for user communications, architecting a PKI for DoorDash's microservices, building a secure key management system, or designing encryption for sensitive payment/delivery data. Assesses ability to think about cryptography at system scale, considering performance, usability, compliance, and operational requirements.
Tips & Advice
Use the SALT framework (mentioned in search results): Scope, Assets, Layers, Tradeoffs. Start by clarifying requirements and constraints rather than jumping to architecture. Draw system diagrams showing data flows and trust boundaries. For senior candidates, interviewers expect you to balance security with performance, usability, and operational complexity. Discuss monitoring, key rotation, failure modes, and incident response. Reference industry standards (NIST, OWASP) and explain your architectural choices. Consider compliance implications (PCI-DSS for payments, data residency, etc.).
Focus Topics
Compliance and Regulatory Alignment
Designing systems meeting PCI-DSS, GDPR, local data residency requirements, and industry standards. Understanding audit trails and cryptographic compliance evidence.
Practice Interview
Study Questions
Performance and Scalability Considerations
Balancing cryptographic security with performance requirements, optimizing encryption/decryption operations, caching strategies for cryptographic operations, and monitoring throughput.
Practice Interview
Study Questions
Public Key Infrastructure (PKI) Design
Designing or improving PKI for service authentication, certificate lifecycle management, certificate revocation strategies, and trust establishment in distributed systems.
Practice Interview
Study Questions
Encryption Architecture for Sensitive Data
Designing end-to-end encryption systems for payment data, user communications, or delivery information. Including decisions on encryption at rest vs. in transit, key management, and data lifecycle.
Practice Interview
Study Questions
Enterprise Key Management Systems
Architecting hierarchical key structures (root, master, data keys), HSM integration, key derivation strategies, key rotation policies, and access control for enterprise-scale systems.
Practice Interview
Study Questions
Behavioral Interview - Leadership and Collaboration
What to Expect
60-minute onsite behavioral interview assessing collaboration, leadership, communication, and decision-making in ambiguous situations. Questions focus on past experiences handling security incidents, managing stakeholder conflicts, mentoring junior engineers, influencing technical decisions, and navigating complex organizational dynamics. For senior-level candidates, evaluates strategic thinking and impact beyond individual contributions.
Tips & Advice
Use STAR method (Situation, Task, Action, Result) for concrete examples. Prepare 5-7 stories demonstrating: handling security trade-offs, mentoring others, communicating complex concepts to non-technical stakeholders, leading design decisions, and recovering from mistakes. For senior candidates, emphasize strategic thinking: how did you influence direction? What broader impact did your work have? Discuss collaboration with product, engineering, and security teams. Be honest about challenges and what you learned. Research DoorDash's values and culture to align your examples.
Focus Topics
Incident Response and Problem-Solving Under Pressure
Handling security incidents, cryptographic vulnerabilities, or system failures. Demonstrating ability to stay calm, investigate root causes, and communicate clearly.
Practice Interview
Study Questions
Security Trade-offs and Decision-Making
Navigating situations requiring security vs. performance, usability, or business needs. Examples of pushing for higher security and examples of accepting practical compromises.
Practice Interview
Study Questions
Communication and Influence
Ability to explain complex cryptographic concepts to engineers of varying expertise levels and non-technical stakeholders. Demonstrating influence over architectural decisions despite disagreement.
Practice Interview
Study Questions
Mentorship and Team Development
Experiences mentoring junior engineers, growing their cryptographic expertise, leading code reviews, and fostering learning culture within security team.
Practice Interview
Study Questions
Hiring Manager Interview
What to Expect
45-60 minute final onsite interview with the hiring manager (likely a Director or VP of Security/Engineering). Focuses on long-term fit, career goals, management style expectations, and team dynamics. Assesses mutual interest and ensures alignment on role expectations, growth opportunities, and reporting structure. Final opportunity for candidate to ask strategic questions.
Tips & Advice
This is as much about evaluating fit for you as for DoorDash. Ask thoughtful questions about team structure, current priorities, growth opportunities, and how cryptography is valued within the organization. Be authentic about your career goals and what you're looking for in a role. Discuss what success looks like in the first 90 days and first year. If offered, this is your chance to negotiate on compensation, title, and role scope. Come prepared with questions about DoorDash's technology strategy, security vision, and how cryptography contributes to business goals.
Focus Topics
Team Dynamics and Collaboration Style
Discussion of how you work with teams, your collaboration preferences, and questions about the team's current composition and dynamics.
Practice Interview
Study Questions
Onboarding and First 90 Days
Discussion of expectations for first quarter: what are priorities? Who will you work with? What should success look like?
Practice Interview
Study Questions
Career Goals and Growth Trajectory
Articulating where you want to go professionally, what you want to learn at DoorDash, and how this role fits your long-term vision.
Practice Interview
Study Questions
Strategic Questions About Role and Organization
Asking informed questions about DoorDash's cryptographic priorities, current challenges, resource allocation, and role impact on broader security strategy.
Practice Interview
Study Questions
Frequently Asked Cryptographer Interview Questions
Design a leveling framework or promotion rubric for your discipline, from mid-level through staff or principal. What are the competency dimensions, what evidence counts as proof at each level, and how would you calibrate it across managers to keep it fair?
Sample Answer
Direct answer
A workable leveling framework names a small set of competency dimensions, defines observable evidence for each level within each dimension rather than a single blended score, and is calibrated across managers with a shared evidence bar, not left to individual judgment. Where a discipline splits into technical and people-leadership paths, the framework should offer parallel individual contributor (IC) and management tracks rather than forcing everyone toward a single ladder.
Structured elaboration
Choose the dimensions. Distinct competencies that don't collapse into one another, scope and ownership, domain judgment, execution and delivery reliability, collaboration and influence, and further up the ladder, mentorship or people development. Five to seven is a common ceiling so a rater can hold them all in mind for one candidate.
Define evidence per level per dimension, not an adjective. A vague label like strong technical judgment isn't gradable. A concrete description of what a rater should be able to point to is:
| Dimension | Signal at current level | Signal at next level |
|---|---|---|
| Scope & ownership | Delivers assigned work reliably with some guidance | Independently scopes new work that others rely on |
| Domain judgment | Follows established patterns | Identifies and justifies trade-offs across viable approaches, and the choice holds up under later review |
| Collaboration & influence | Works well within the immediate team | Actively shapes outcomes across teams |
Offer a dual-track structure. At the point where a discipline splits, describe both the individual contributor track and the management track explicitly, with a shared foundation up to that split and different evidence after it. The IC track keeps rewarding deep domain ownership, the management track shifts the evidence toward people outcomes, without treating either as a lesser or forced default.
Calibrate across managers. A rubric applied differently by different managers isn't a shared standard. Build in a norm-setting session before each cycle where managers score anonymized example write-ups and discuss disagreement, and keep a documented set of example evidence per level that managers can compare their own candidates against.
Keep the promotion bar distinct from the good-performance bar. Conflating the two is a common source of drift, where strong performers get promoted for consistency rather than demonstrated readiness for the next level.
Worked example
"When I sketched a rubric for my own discipline I started with five dimensions, scope and ownership, domain judgment, delivery reliability, collaboration and influence, and from a certain level up, mentorship. For each I wrote concrete evidence per level, distinguishing consistently delivers assigned work with occasional guidance from identifies and scopes new work independently, and others rely on that scoping. I built in the dual-track split at the point where the discipline typically forks, the individual contributor track kept the domain-judgment dimension weighted heavily, the management track replaced the mentorship dimension with a people-outcomes dimension covering retention, growth, and team health. To calibrate, I proposed a norm-setting session before each cycle where a handful of managers scored the same two anonymized write-ups independently and discussed any gap before applying the rubric to their own teams, so the same evidence wouldn't land a promotion on one team and a not yet on another."
Trade-offs & pitfalls
- Too many dimensions makes the rubric unusable in practice, raters default back to gut feel.
- Too few collapses distinct competencies together and hides real gaps, blending technical judgment and delivery reliability can let someone who's reliable but making poor trade-off calls slide through.
- Skipping calibration is the most costly gap. Without it, the same rubric produces different outcomes on different teams, which is precisely the fairness problem it's meant to solve.
- Forcing a single ladder onto a discipline that naturally splits pushes people toward management for the promotion rather than the fit, a bad outcome for both the person and the team they might end up managing.
Describe a robust session-key derivation scheme for a protocol that needs separate keys for encryption, authentication, IV generation, and application export. Specify how you would use HKDF (extract and expand), what salts and info/context strings you would include, and how you prevent cross-protocol or cross-layer key reuse.
Sample Answer
Direct answer
Run HKDF (HMAC-based key derivation function, HMAC itself being a keyed cryptographic checksum; HKDF is a two-stage construction for turning raw shared-secret material into one or more well-distributed keys) in its standard two steps: Extract once, to concentrate the raw shared secret and a salt into a single uniformly-random pseudorandom key (PRK), then Expand multiple times from that same PRK, once per purpose (encryption, authentication, IV, export), each call using a distinct info string that names the purpose and the protocol. Reusing one key, or deriving all of them from the shared secret directly instead of through domain-separated Expand calls, is exactly what lets a bug or an attacker-influenced input in one derivation leak information about another.
Structured elaboration
- Extract.
PRK = HMAC(salt, input_key_material). The salt should be something both sides already agree on and that changes per session, a transcript hash is ideal, since it also binds the derived keys to that specific handshake and not merely to the raw shared secret. - Expand.
HKDF-Expand(PRK, info, length), called once per key needed.infoshould encode, at minimum: a protocol name and version (prevents a key derived here from ever colliding with a key derived by a different protocol from the same raw secret), a session or connection identifier, and the specific purpose string (enc,auth,iv,export). - Cross-protocol and cross-layer separation. The entire point of varying
infois that HKDF-Expand is a pseudorandom function: differentinfoinputs produce computationally independent outputs even from the identicalPRK. Two different protocols that accidentally share a raw secret (a misconfiguration, or a deliberately shared root secret in a layered design) still get unrelated keys, as long as each includes its own protocol label ininfo; omitting that label is precisely how a key gets reused across contexts it was never meant for.
Worked example
"""
derive separate session keys (encryption, authentication, IV, application
export) from one shared secret using HKDF (RFC 5869: HKDF-Extract then
HKDF-Expand), with distinct info/context strings and a protocol-identifying
label to prevent cross-protocol / cross-layer key reuse. Stdlib only.
"""
import hashlib
import hmac
HASH = hashlib.sha256
HASH_LEN = HASH().digest_size # 32 bytes
def hkdf_extract(salt: bytes, ikm: bytes) -> bytes:
if salt == b"":
salt = b"\x00" * HASH_LEN
return hmac.new(salt, ikm, HASH).digest()
def hkdf_expand(prk: bytes, info: bytes, length: int) -> bytes:
t = b""
okm = b""
counter = 1
while len(okm) < length:
t = hmac.new(prk, t + info + bytes([counter]), HASH).digest()
okm += t
counter += 1
return okm[:length]
PROTOCOL_LABEL = b"myproto/v1" # domain separation: unique per protocol+version
def derive_session_keys(shared_secret: bytes, salt: bytes, session_id: bytes) -> dict:
prk = hkdf_extract(salt=salt, ikm=shared_secret)
context = PROTOCOL_LABEL + b"|" + session_id
return {
"enc_key": hkdf_expand(prk, context + b"|enc", 32),
"auth_key": hkdf_expand(prk, context + b"|auth", 32),
"iv": hkdf_expand(prk, context + b"|iv", 12),
"export_key": hkdf_expand(prk, context + b"|export", 32),
}
if __name__ == "__main__":
shared_secret = bytes.fromhex("a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff")
salt = b"handshake-transcript-hash-synthetic"
session_id = b"session-0001"
keys = derive_session_keys(shared_secret, salt, session_id)
for name, val in keys.items():
print(f"{name:10s} = {val.hex()}")
print("\n-- all four outputs are pairwise distinct --")
values = list(keys.values())
print("unique count:", len({v.hex() for v in values}), "/", len(values))
print("\n-- cross-protocol separation: same secret, different protocol label --")
global PROTOCOL_LABEL_ORIG
PROTOCOL_LABEL_ORIG = PROTOCOL_LABEL
import types
def derive_with_label(shared_secret, salt, session_id, label):
prk = hkdf_extract(salt=salt, ikm=shared_secret)
context = label + b"|" + session_id
return hkdf_expand(prk, context + b"|enc", 32)
key_proto_a = derive_with_label(shared_secret, salt, session_id, b"myproto/v1")
key_proto_b = derive_with_label(shared_secret, salt, session_id, b"otherproto/v1")
print("myproto/v1 enc_key =", key_proto_a.hex())
print("otherproto/v1 enc_key =", key_proto_b.hex())
print("identical shared_secret+salt+session_id, different protocol label -> different key:",
key_proto_a != key_proto_b)
Output:
enc_key = 7978d15be41f56b4f2374e4eeecaf880f65633255e0f76560668a73499f16c3b
auth_key = 4f7380c0ffbc7c5f8fabc3727793d99764b8e2c1b81b1f12cf6f1d90c97e2482
iv = fb9991ec4eede93fbb7c1854
export_key = c39ec418a7b055e7a0825ae8f40ee3ca5c8ac877184a2c00609083a268f97004
-- all four outputs are pairwise distinct --
unique count: 4 / 4
-- cross-protocol separation: same secret, different protocol label --
myproto/v1 enc_key = 7978d15be41f56b4f2374e4eeecaf880f65633255e0f76560668a73499f16c3b
otherproto/v1 enc_key = 6bf6e1d1ae230bb45efc8e0b6e7bc67cfb7b6d79524c460c84378aad033bdef4
identical shared_secret+salt+session_id, different protocol label -> different key: True
All four derived values, encryption key, authentication key, IV, and export key, come out pairwise distinct from a single shared secret. Changing only the protocol label in info, with every other input held identical, produces a completely different encryption key, which is exactly the cross-protocol separation property the label exists to provide.
Trade-offs and pitfalls
infoshould be a structured, unambiguous encoding (fixed-format labels with explicit delimiters or length prefixes), not free-form string concatenation;"enc" + "key1"and"enckey" + "1"collide if concatenation isn't unambiguous, silently merging two supposedly-separate derivations.- The salt is not a secret and does not need to be, but it must be unpredictable or session-specific to get the full benefit of Extract's concentration step; reusing a fixed, hardcoded salt across every session removes most of that benefit and makes it closer to omitting Extract altogether.
- IV derivation via HKDF is fine, but the usage of the IV still matters: an AEAD (authenticated encryption with associated data) cipher's security still depends on never reusing the same key+IV pair for two different messages, which is a separate discipline from key derivation and has to be enforced at the encryption call site, not just at key-generation time.
Explain the elliptic curve equation y^2 = x^3 + ax + b defined over a prime field F_p and the group law on curve points. In your explanation describe the point at infinity, the inverse of a point, the geometric chord-and-tangent interpretation, and the algebraic conditions (e.g., discriminant != 0) required for the set of solutions to form an abelian group. Also mention how the equation and group law are modified when moving to binary fields F_{2^m}.
Sample Answer
Direct answer
Over a prime field Fp, a short Weierstrass elliptic curve is the set of solutions (x,y) to y2=x3+ax+b(modp), together with one extra formal element, the point at infinity, which acts as the group's identity (the elliptic-curve analogue of the number 0). Those points, combined with a specific addition rule (the "group law"), form an abelian group, meaning addition is closed, associative, commutative, has an identity, and every element has an inverse, PROVIDED the curve is non-singular, which is a single algebraic condition on a and b: the discriminant must be nonzero.
Structured elaboration
Point at infinity. O is the identity element: P+O=P for every point P. It does not correspond to any actual (x,y) solution; it is a formal point added to close the group under addition (two points on a vertical line "meet" only at infinity in the geometric picture).
Inverse of a point. The inverse of P=(x,y) is −P=(x,−ymodp); O is its own inverse. P+(−P)=O by construction, since a vertical line through P and −P meets the curve nowhere else in the affine plane.
Geometric chord-and-tangent interpretation. To add two distinct points P and Q (neither the inverse of the other): draw the straight line through them; by Bezout's-theorem-style counting a cubic curve meets any line in exactly three points (with multiplicity), so that line meets the curve at one more point R; reflect R across the x-axis to get P+Q. Doubling a point P uses the TANGENT line at P instead of a chord, the same picture in the limit as Q approaches P.
Non-singularity condition. The curve must have no cusps or self-intersections, which the discriminant condition captures algebraically:
Δ=−16(4a3+27b2)=0(modp)A zero discriminant means the cubic x3+ax+b has a repeated root, producing a singular point where the tangent line is not well-defined, breaking the chord-and-tangent construction at that point and, with it, associativity of the group law. Given non-singularity, closure, identity, and inverses are immediate from the construction above; associativity is the one property that is genuinely nontrivial to prove directly (it follows from deeper algebraic-geometry results, not from a short calculation), and commutativity is immediate since the chord through P,Q is the same line as the chord through Q,P.
Binary fields F2m. Characteristic 2 breaks the short Weierstrass form (some of its formulas divide by 2, undefined in characteristic 2), so binary-field curves use a different equation, commonly
y2+xy=x3+ax2+b(modF2m)Because subtraction equals addition in characteristic 2 (every element is its own additive inverse), the point inverse becomes −P=(x,x+y) instead of (x,−y), and the addition and doubling formulas change accordingly, though the same group-of-points structure and identity-at-infinity picture still holds.
Worked example
# Discriminant non-degeneracy check and point inverse on the toy curve.
p, A, B = 97, 2, 3
def point_add(P, Q):
def inv_mod(a, m=p):
return pow(a % m, m - 2, m)
if P is None: return Q
if Q is None: return P
x1, y1 = P; x2, y2 = Q
if x1 == x2 and (y1 + y2) % p == 0: return None
if P == Q:
lam = (3 * x1 * x1 + A) * inv_mod(2 * y1) % p
else:
lam = (y2 - y1) * inv_mod(x2 - x1) % p
x3 = (lam * lam - x1 - x2) % p
y3 = (lam * (x1 - x3) - y1) % p
return (x3, y3)
def is_on_curve(P):
x, y = P
return (y * y - (x**3 + A * x + B)) % p == 0
if __name__ == "__main__":
disc = (-16 * (4 * A**3 + 27 * B**2)) % p
print(f"curve: y^2 = x^3 + {A}x + {B} (mod {p})")
print(f"discriminant Delta = -16*(4*{A}^3 + 27*{B}^2) mod {p} = {disc} (nonzero: {disc != 0})")
P = (3, 6)
neg_P = (P[0], (-P[1]) % p)
print(f"P = {P}, -P = (x, -y mod p) = {neg_P}, on_curve(-P) = {is_on_curve(neg_P)}")
print(f"P + (-P) = {point_add(P, neg_P)} (expect the point at infinity, represented as None)")
Output:
curve: y^2 = x^3 + 2x + 3 (mod 97)
discriminant Delta = -16*(4*2^3 + 27*3^2) mod 97 = 62 (nonzero: True)
P = (3, 6), -P = (x, -y mod p) = (3, 91), on_curve(-P) = True
P + (-P) = None (expect the point at infinity, represented as None)
Trade-offs and pitfalls
A common conceptual slip is treating the discriminant check as a one-time curve-selection detail rather than a live invariant: standard named curves (P-256, secp256k1, and similar) are chosen with this already satisfied, so implementers rarely compute it themselves, but if you are ever generating curve PARAMETERS yourself (for a novel design or an audit), skipping this check risks silently working with a singular "curve" where the group law is not actually well-defined at some point, and operations near that point can behave inconsistently. Binary-field curves are largely a historical/legacy branch at this point, most modern deployments use prime-field curves, so knowing the characteristic-2 formulas differ is more useful for reading older standards and reasoning about GF(2^m)-based cryptography than for new design work.
You are designing threat modeling for a small web service that stores user secrets (API keys and encrypted documents). Describe the step-by-step threat-modeling process you would apply, including assets, actors, entry points, trust boundaries, potential attack scenarios, and practical mitigations for each major risk.
Sample Answer
Direct answer
Threat modeling a small web service that stores user secrets (API keys and encrypted documents) follows a repeatable sequence: list what actually needs protecting, list who interacts with the system, map every place an outsider can reach it, mark where trust level changes as data moves through the system, walk each entry point for a realistic attack scenario, and propose a specific mitigation for each one. The step that most often gets rushed on a small service like this is trust boundaries, because it is tempting to treat "our backend" as one uniform trusted zone, when in practice the boundary between "authenticated as some user" and "authorized to see this specific user's secrets" is exactly where the highest-impact bugs tend to live.
Structured elaboration
1. Assets. What actually needs protecting, ranked by what a compromise would cost: user API keys (secrets that, if leaked, let an attacker impersonate the user to whatever third-party service the key belongs to), the encrypted documents and the keys used to encrypt them, user session tokens and credentials, and the service's own master key or key-management-service (KMS) key used to protect everything else.
2. Actors. Who legitimately or illegitimately interacts with the system: the authenticated end user (owns their own secrets, should never reach anyone else's), an external, unauthenticated attacker attempting to reach the service directly, and the service's own operators or administrators, who hold elevated access and are therefore both a legitimate actor and a potential insider-risk actor.
3. Entry points. Every place an outsider can touch the system: the login/authentication endpoint, the application programming interface (API) endpoints that create, retrieve, or delete a user's stored API keys, the document upload and download endpoints, and any administrative panel used for support or operations.
4. Trust boundaries. Where the level of trust actually changes, not merely where a network hop happens: the boundary between the public internet and the authentication layer (nothing is trusted yet); the boundary between "successfully authenticated" and "authorized for this specific resource," which is the boundary a small service most often gets wrong, since being logged in proves who you are but says nothing about which records you should be allowed to touch; and the boundary between the application backend and the KMS or database holding the actual secret material, which should require its own scoped credential rather than inheriting the application's general database access.
5. Attack scenarios, one plausible path per major risk:
- Credential stuffing against the login endpoint, using credentials leaked from an unrelated breach, to gain a legitimate session and then reach that user's own stored secrets legitimately (from the system's point of view).
- Broken authorization on the key-retrieval endpoint (an insecure direct object reference, where the endpoint checks that a request is authenticated but not that the requested key actually belongs to the requesting user), letting an authenticated attacker enumerate and read other users' API keys just by changing an identifier in the request.
- Secrets leaking into logs or error messages, where a stack trace or debug log inadvertently includes a plaintext API key or document content during a failure, creating a copy of the secret outside the system's actual protection boundary.
- Admin panel compromise, where an attacker who gains access to an operator account (phishing, credential reuse) inherits whatever broad access that panel grants, potentially including the ability to read secrets directly rather than only manage accounts.
6. Mitigations, mapped to the scenarios above:
- Rate limiting and multi-factor authentication (MFA) on the login endpoint blunts credential stuffing even when the attacker holds valid, leaked credentials.
- Explicit per-resource authorization checks on every key and document endpoint (confirming the authenticated user actually owns the specific record requested, not only that they are logged in) closes the broken-authorization path; this needs to be enforced consistently for every operation, not only the ones that seemed obviously sensitive during initial development.
- Structured logging with automatic secret redaction, and treating any logging or error-handling code path that touches a secret value as security-sensitive code requiring its own review, prevents the accidental-leak path.
- Scoping admin access narrowly (support staff should not, by default, be able to view raw secret values, only metadata needed for support) and requiring a separate, logged, deliberate action for the rare case where raw access is genuinely needed, reduces both the insider-risk surface and the value of a single compromised admin account.
Worked example
Trace the broken-authorization scenario concretely, since it is the one most specific to this exact system and the easiest to introduce accidentally. The API key retrieval endpoint is implemented as GET /api/keys/{keyId}, and the handler checks that the request carries a valid session token before returning the key, but does not check that keyId belongs to the session's user, an easy omission when the same handler pattern is copied from an earlier endpoint that happened not to need per-owner scoping. An authenticated attacker, who only needs their own valid, low-privilege account to reach the endpoint at all, can then increment or guess keyId values across other users' records and retrieve API keys that were never theirs, entirely within what looks like a normal authenticated request from the system's perspective, since authentication succeeded correctly every time. The mitigation is not "add more authentication," since the failure was never in authentication, it was in authorization: the fix is adding an explicit ownership check, confirming the key referenced by keyId belongs to the authenticated session's user before returning it, and applying that same check pattern to every other resource-scoped endpoint (documents, any future secret type) rather than fixing this one endpoint in isolation.
Trade-offs and pitfalls
The most common mistake on a service like this is conflating authentication with authorization, treating "the request has a valid session" as equivalent to "the request should see this specific data," which is exactly the gap the worked example exploits; the two checks need to be separate, deliberate steps on every resource-scoped endpoint. A second pitfall is under-scoping trust boundaries to only the perimeter (public internet versus backend) and skipping the internal one between "authenticated" and "authorized," which is where this class of bug actually lives on most small services, not at the network edge. A third, easy to miss on a small team, is treating logging and error handling as separate from the security review process; the code path that formats an error message is not obviously security-sensitive, but if it can ever include a secret value in its output, it is exactly as sensitive as the code that stores that secret in the first place, and deserves the same scrutiny.
When someone you're mentoring is stuck, how do you decide whether to just give them the answer, ask a guiding question, or let them keep struggling with it?
Sample Answer
Direct answer
This isn't a single rule, it's a judgment call driven by stakes, time pressure, and whether the struggle is actually productive. My default is a graduated ladder: ask an orienting question first, then narrow the search space with a hint, and only hand over the answer if that hasn't worked or the situation doesn't allow more time.
Decision criteria
- Stakes and time pressure. A production incident, a hard external deadline, or anything safety or compliance critical pushes toward giving the answer sooner. A practice task or routine work with slack in the schedule can absorb more struggle.
- Productive vs. unproductive struggle. Productive struggle looks like forming a hypothesis, trying something, narrowing the possibilities, and making incremental progress, even slowly. Unproductive struggle looks like repeating the same failed attempt, or restating the same confusion without new information. The first is worth protecting, the second isn't.
- Type of gap. If the person is missing a concept entirely, guiding questions can circle for a long time without landing. If they have the concept but haven't applied it here, a nudge is usually enough.
- Trust and frustration level. Visible frustration that's starting to tip into disengagement is a signal to step in, even on a low-stakes task, because the cost of pushing further is now higher than the learning value.
Worked example
A mentee was stuck for a while on why a piece of work was producing an unexpected result. First move: an orienting question ("What did you expect to happen here, and where does the actual behavior diverge from that?"). They could describe the divergence but not explain it, so the second move was a narrowing hint pointing at the specific area to look at, without naming the cause. They investigated that area and found it themselves. If that hint hadn't landed, the next step would have been to explain the underlying cause directly, then ask them to restate it in their own words and apply it once more on a related case, so the session still ends with them exercising the skill rather than just receiving an answer.
Trade-offs and pitfalls
Always rescuing produces a mentee who never builds independent judgment and starts routing every decision through you. Always withholding produces frustration, slower delivery, and eventually disengagement, especially under real time pressure. A common junior mistake is judging "stuck" purely by elapsed time rather than by whether new information is being generated. A more senior habit is calibrating a default line per person (some people need more room, others need more scaffolding early on) and deliberately moving that line as the person gains experience, so the same person gets less hand-holding a year in than they did in week one.
A cloud VM image was restored from a snapshot, and you discover weak keys were generated on boot because of low entropy. Describe your immediate detection and mitigation steps for affected instances, and the longer-term architectural changes you'd make to prevent entropy-starvation problems across the fleet.
Sample Answer
Direct answer
This is the classic virtual machine (VM) snapshot entropy-starvation failure: restoring multiple
instances from the same snapshot can leave each one's random-number generator seeded from the exact
same internal state, so keys generated shortly after boot can be identical or closely related across
"different" instances. Immediately rotate every key generated at or near first boot on any instance
restored from that snapshot lineage, and longer term, stop generating long-lived keys until the
operating system's cryptographically secure pseudo-random number generator (CSPRNG) is confirmed to be
seeded with genuinely fresh, instance-unique entropy.
Structured elaboration
- Why this happens. A snapshot captures the entropy pool's internal state at the moment it was
taken. When several instances restore from that same snapshot, each kernel CSPRNG (for example
Linux's/dev/urandomand itsgetrandom()system call) can start in the exact same seeded state, if
a key-generation event fires before the operating system has mixed in enough new, instance-unique
entropy (hardware interrupt timing, boot-time jitter), two "different" restored instances can produce
identical or related output. For RSA or Digital Signature Algorithm (DSA) key generation specifically,
this can leak the private key entirely via shared-prime attacks (an RSA public key's modulus is the product of two secret prime numbers, so if two keys accidentally reuse one of those primes, taking the greatest common divisor of their two public moduli recovers that shared prime instantly, and from it both private keys can be fully reconstructed): comparing keys across a fleet with a
batch greatest-common-divisor (GCD) computation reveals any pair that shares a prime factor, and
sharing even one prime factor breaks both keys completely. - Immediate detection. Identify every instance restored from the affected snapshot or golden-image
lineage, and every key or credential generated at or shortly after its first boot: Transport Layer
Security (TLS) host keys, Secure Shell (SSH) host keys, and any application-level "generate on first
boot" logic. A fast, practical triage technique is running a batch GCD comparison across all the
fleet's RSA public keys: if any two share a prime factor, both are broken, and this is proof by
itself, it does not require separately proving that entropy was actually low on either device. - Immediate mitigation. Force regeneration of every affected key or credential from a properly
seeded instance, never from the same snapshot lineage, and revoke or replace any certificates issued
for the old, potentially weak keys. Treat this exactly like any other confirmed key-compromise
incident, a low-entropy key is a compromised key whether or not anyone has exploited it yet. - Longer-term architectural changes.
- Ensure golden images and first-boot or cloud-init logic never perform long-term key generation
until sufficient entropy is confirmed available. Modern kernels already blockgetrandom()until
the CSPRNG is properly initialized specifically to prevent this class of bug, so the real fix is
often simply not bypassing that protection with an older or non-blocking random-source call, and
making full use of the platform's hardware entropy source, most major cloud providers inject
genuine hardware entropy into VMs specifically to address this problem. - Never bake unique, long-term secrets (host keys, node identity keys) into a golden image or
snapshot at all; generate them fresh on genuine first boot only, and make snapshot or clone
operations explicitly clear or regenerate anything that must be unique per instance. Standard
cloud-init mechanisms for regenerating SSH host keys on clone are the off-the-shelf version of this
fix, and should be treated as mandatory, not optional, for any image used to spawn multiple
instances. - Add a provisioning gate that verifies genuine entropy availability, or explicitly waits for the
kernel's CSPRNG-ready signal, before allowing any key-generation step to run, rather than trusting
that "the VM finished booting" implies "the VM is ready to generate secrets."
- Ensure golden images and first-boot or cloud-init logic never perform long-term key generation
Worked example
Large-scale internet scans of public RSA and DSA keys have found that a meaningful, non-trivial
fraction of examined keys share a prime factor with at least one other key on the internet, almost all
traced back to exactly this kind of boot-time entropy problem on embedded and virtualized systems. This
is precisely why batch GCD across a fleet is a real, practical detection technique used in production
incident response, not a theoretical concern: it is far cheaper computationally than trying to factor
each key individually, since it only needs pairwise GCD comparisons, but it does require you to
actually have collected every public key in the fleet first, which is itself a strong argument for
centrally inventorying every generated key as a prerequisite for ever being able to run this check.
Trade-offs & pitfalls
- Rotating the affected credentials without also fixing the root cause, the image or provisioning
logic's entropy handling, simply recreates the same vulnerability at the next scale-out event. - This is not a "slightly weaker" key; a successful shared-factor attack recovers the FULL private key.
Urgency and response should match a confirmed full compromise, not a minor weakness. - Batch GCD detection is cheap to run but only as good as your key inventory; if you cannot enumerate
every key your fleet has ever generated, you cannot run this check against all of them, which argues
for building that inventory before you need it, not during the incident.
You are designing a TLS-like record protocol that uses CBC-mode for record encryption. Describe an IV generation strategy for each record that prevents chosen-IV/IV-reuse attacks, minimizes predictability issues, and integrates with record sequence numbers and re-keying. Explain how your strategy defends against the classic CBC-oriented attacks that affected early TLS versions.
Sample Answer
Direct answer
For a TLS-like record protocol using Cipher Block Chaining (CBC) mode, derive each record's IV (initialization vector) as a pseudorandom function of a per-session secret and that record's own sequence number, IV_seq = PRF(IV_key, "iv" || seq), rather than any predictable or attacker-influenced value. This guarantees every IV is both UNIQUE (the sequence number never repeats within a session) and UNPREDICTABLE (an attacker cannot compute or influence a future IV in advance), which is the exact property that early TLS versions lacked and that the BEAST (Browser Exploit Against SSL/TLS) attack exploited.
Structured elaboration
Why "unique" alone is not enough for CBC. CBC's security proof requires the IV to be UNPREDICTABLE to an adversary at the time a record is encrypted, not merely non-repeating. Early TLS (1.0) used the previous record's LAST ciphertext block as the next record's IV, an idea that IS unique per record but is fully predictable: an attacker observing the ciphertext stream (a scenario realistic for a network attacker on a shared connection) knows the exact IV the next record will use before it is even encrypted, which enabled the BEAST attack's ability to verify byte-by-byte guesses about unknown plaintext.
The PRF-based derivation. Deriving each IV from a per-session secret via a pseudorandom function (PRF, a keyed function whose output is indistinguishable from random to anyone without the key) breaks that predictability: an attacker who can observe the ciphertext stream still cannot predict IV_seq+1 without knowing IV_key, which never appears on the wire.
Integration with sequence numbers and re-keying. Using the record's own sequence number as the PRF's input ties IV uniqueness directly to a value the protocol ALREADY tracks for anti-replay purposes, no separate counter needed. On re-key (a fresh session key negotiated mid-connection, or a brand-new session), derive a FRESH IV_key alongside the new encryption key and reset the sequence number; this prevents a subtle failure mode where an old IV sequence, if somehow reused under a NEW key, could reintroduce risk depending on how directly key and IV derivation are related.
Why this defends against the classic CBC attacks. BEAST specifically relied on IV predictability, not merely CBC's chaining structure; a PRF-derived per-record IV removes that predictability entirely, independent of any other TLS-record-format changes. It does not, by itself, address padding-oracle-style attacks (Lucky 13 and similar), which are a separate vulnerability class in how padding and MAC verification are handled, requiring their own mitigation (constant-time padding checks, or moving to an AEAD, Authenticated Encryption with Associated Data, construction entirely, which TLS 1.2+ effectively did by favoring AES-GCM and ChaCha20-Poly1305 over CBC).
Worked example
Deriving 5 successive per-record IVs via HMAC-SHA256, confirming uniqueness across sequence numbers and independence across a re-key, then a real CBC encrypt/decrypt round trip using one derived IV:
from cryptography.hazmat.primitives import hmac, hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def derive_iv(iv_key, seq):
h = hmac.HMAC(iv_key, hashes.SHA256())
h.update(b"iv" + seq.to_bytes(8, "big"))
return h.finalize()[:16]
iv_key = bytes.fromhex("6f6e6520706572202d2073657373696f"[:32])
ivs = [derive_iv(iv_key, seq) for seq in range(5)]
print("all 5 IVs distinct:", len(set(ivs)) == 5)
iv_key_2 = bytes.fromhex("74776f207065722d726b6579202d2073"[:32]) # fresh key after re-key
print("seq=0 differs after re-key:", ivs[0] != derive_iv(iv_key_2, 0))
key = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
record_iv = ivs[3]
base = b"GET /balance HTTP/1.1"
plaintext = base + b"P" * (-len(base) % 16 or 16)
enc = Cipher(algorithms.AES(key), modes.CBC(record_iv)).encryptor()
ciphertext = enc.update(plaintext) + enc.finalize()
dec = Cipher(algorithms.AES(key), modes.CBC(record_iv)).decryptor()
recovered = dec.update(ciphertext) + dec.finalize()
print("record round-trip matches original:", recovered == plaintext)
Output:
all 5 IVs distinct: True
seq=0 differs after re-key: True
record round-trip matches original: True
The derived IVs are unique across sequence numbers within a session, unrelated across a re-key (so no cross-key IV relationship exists to exploit), and function correctly as real CBC IVs in an encrypt/decrypt round trip.
Trade-offs and pitfalls
- This design specifically fixes IV PREDICTABILITY (the BEAST class of attack); it does NOT fix padding-oracle vulnerabilities (Lucky 13 and similar), which come from how padding validation and MAC verification are sequenced and timed, a separate design decision layered on top.
- The PRF call adds one HMAC computation per record; this is cheap relative to the AES operations already required, but it is still real, measurable overhead compared to using an explicit random IV transmitted in the record itself.
- Given that TLS 1.2+ largely moved away from CBC in favor of AEAD (Authenticated Encryption with Associated Data) ciphersuites specifically to eliminate this entire category of design pitfall, a from-scratch protocol today should default to an AEAD construction (AES-GCM, ChaCha20-Poly1305) rather than reproduce CBC's IV-management burden at all, unless legacy interoperability specifically requires it.
Explain the differences between collision resistance, preimage resistance, and second-preimage resistance in hash functions. Provide practical attack complexity estimates for MD5, SHA-1, and SHA-256, and state at what point you would mandate deprecation of a hashing algorithm within an organization.
Sample Answer
Direct answer
A cryptographic hash function is supposed to make three different attacks infeasible, and they are genuinely different properties, not three names for the same thing: collision resistance (hard to find any two distinct inputs that hash to the same output), preimage resistance (given a hash output, hard to find any input that produces it), and second-preimage resistance (given one specific input, hard to find a different input that hashes to the same value). MD5 and SHA-1 (Secure Hash Algorithm 1) have both had their collision resistance broken in practice; SHA-256 has not, at either a theoretical or practical level, and remains the safe current default.
Telling the three properties apart
| Property | Attacker is given | Attacker must find |
|---|---|---|
| Collision resistance | nothing specific | any two inputs x1 != x2 with hash(x1) == hash(x2) |
| Preimage resistance | a hash value h | any input x with hash(x) == h |
| Second-preimage resistance | a specific input x1 | a different input x2 with hash(x2) == hash(x1) |
Collision resistance is the weakest guarantee to break, because the attacker has complete freedom to choose both inputs, which is exactly what makes it vulnerable to the birthday paradox: you don't need to search anywhere near the full output space, only until any two outputs among many attempts happen to match.
Worked example: why collisions are so much cheaper than preimages
For a hash with an n-bit output, the generic (structure-agnostic) cost to find a collision by brute force is about 2n/2 attempts, while a generic preimage or second-preimage attack costs about 2n:
2128/2=264≈1.8×1019 (MD5’s generic collision bound) 2160/2=280≈1.2×1024 (SHA-1’s generic collision bound) 2256/2=2128≈3.4×1038 (SHA-256’s generic collision bound)Real cryptanalysis has done far better than these generic bounds for the two broken algorithms:
- MD5: differential collision attacks (Wang et al., refined repeatedly since 2004) make finding a collision practical in well under a second on an ordinary laptop, vastly cheaper than the generic 264 bound. MD5's preimage resistance is comparatively less damaged; no practical preimage attack is known, but its collision break alone rules it out for anything security-relevant.
- SHA-1: a real, published collision (the "SHAttered" attack, 2017) was demonstrated at a complexity of roughly 263.1 operations, about 100,000 times cheaper than the generic 280 birthday bound for its 160-bit output, and a cheaper chosen-prefix variant followed in 2020 at a small fraction of the original attack's cost. SHA-1's preimage resistance is not known to be practically broken, but its collision break is sufficient reason to treat it as unsafe for anything that depends on collision resistance, most notably digital signatures.
- SHA-256: no attack better than the generic birthday and brute-force bounds is known against either its collision or preimage resistance; it remains the safe, current default for new systems.
When to mandate deprecation
Don't wait for a public, practical break to start migrating, cryptographers had already been flagging MD5's collision weaknesses years before 2004's practical attacks, and SHA-1's theoretical weaknesses were known well before the 2017 demonstration. A defensible organizational policy is to mandate deprecation once a hash's best known attack (not just the generic bound) drops to a complexity a well-resourced attacker could plausibly reach within your data's required confidentiality or integrity lifetime, treating "cryptographers have found a meaningfully-better-than-generic attack at all" as the trigger to start planning migration, and "a practical, demonstrated break exists" as the trigger to have already finished it.
Trade-offs and pitfalls
A common mistake is judging a hash function purely by its output length rather than by known attacks against it: SHA-1's 160-bit output sounds larger and safer than MD5's 128-bit output, but its real collision resistance today is a demonstrated practical break, not a theoretical margin. Always check the current state of published cryptanalysis for the specific algorithm and use case (collision resistance failing matters enormously for signatures, much less for some non-adversarial checksums), rather than trusting the nominal output size alone.
Construct a comprehensive threat model for cryptographic primitives executed in cloud VMs and SGX enclaves using AES-NI. Define attacker capabilities (e.g., co-resident tenant, privileged host), enumerate likely attack vectors (cache attacks, speculative execution, fault injection), and propose mitigations at hardware, hypervisor, enclave, and application levels. Discuss residual risks.
Sample Answer
Direct answer
Model two attacker capability tiers, and be explicit that SGX's (Software Guard Extensions, an Intel technology for hardware-enforced isolated "enclaves") own design goal only closes one of them: an unprivileged co-resident tenant, who can mount purely software cache- and speculative-execution-based attacks, and a PRIVILEGED host or hypervisor operator, who has everything the co-resident tenant has plus full control over scheduling, paging, and interrupt delivery. SGX exists specifically to protect enclave confidentiality and integrity against that second, privileged tier, a compromised or malicious OS or hypervisor should not be able to read or tamper with enclave memory directly. What SGX does NOT do by design is hide the enclave's externally observable behavior (which memory pages it touches, when it page-faults, how it is scheduled) from that same privileged adversary, and several real, high-impact disclosed attacks (Foreshadow, also called L1TF, a 2018 speculative-execution flaw; the MDS family, a related 2019 flaw class; and Plundervolt, a 2019 fault-injection attack) exploited exactly that gap or a closely related one.
Structured elaboration
Attacker capabilities by tier
- Unprivileged co-resident tenant: cache-timing (Prime+Probe, Flush+Reload) and other purely software microarchitectural measurements, no special privilege required, same threat class as any other multi-tenant cloud scenario.
- Privileged host or hypervisor operator: everything above, plus full legitimate control over scheduling, memory paging, and interrupt delivery, capabilities SGX's threat model explicitly assumes could be malicious, and additionally, on affected hardware generations, the ability to exploit undocumented firmware-level interfaces (as Plundervolt did) that were never meant to be attacker-controllable at all.
Attack vectors specific to this setting
- Cache attacks against AES-NI (Advanced Encryption Standard New Instructions): AES-NI itself performs the AES round function as a dedicated, fixed-latency hardware instruction with no data-dependent memory lookup, which removes the classic T-table cache-timing risk for the round function itself. Surrounding software, key-schedule handling, other application data processed alongside it, or a software fallback path on hardware lacking AES-NI, can still carry the ordinary cache-timing risk that applies to any table-driven implementation processing secret data.
- Speculative execution: L1TF (L1 Terminal Fault, also called Foreshadow) specifically targeted SGX by exploiting the fact that speculative reads could pull data recently cached in the shared L1 data cache, including SGX enclave secrets, before the CPU's normal access-control check had completed. The related MDS (Microarchitectural Data Sampling) family leaks stale data from other internal CPU buffers (the store buffer, fill buffer, and load ports) during speculative execution, and depending on the specific variant and patch state, this can include data that passed through an enclave.
- Fault injection: Plundervolt (CVE-2019-11157, a Common Vulnerabilities and Exposures identifier, disclosed December 2019) demonstrated that a privileged software attacker abusing an undocumented, software-controllable voltage-scaling interface could induce precise computational faults INSIDE an enclave's own execution, and the researchers used it to inject faults into RSA-CRT (Rivest-Shamir-Adleman with Chinese Remainder Theorem) and AES-NI computations running inside an enclave and reconstruct full cryptographic keys. This defeats SGX's INTEGRITY guarantee directly, memory encryption and access-control isolation do nothing to stop a fault injected into the computation itself.
- Beyond these three, the same privileged operator who legitimately controls scheduling and page-fault handling can, without needing any of the above, infer an enclave's fine-grained memory-access pattern purely by observing which pages fault and when, since that control is a normal part of running a hypervisor, not a bug being exploited.
Mitigations by layer
- Hardware: microcode updates that close the specific information-flow paths L1TF and the MDS family relied on, and, following Plundervolt, firmware/BIOS-level options to disable the vulnerable software-controlled voltage-scaling interface entirely, removing the attacker's fault-injection lever rather than trying to detect faults after the fact.
- Hypervisor: avoid scheduling an untrusted co-tenant onto a sibling hyperthread of a core currently running an enclave (the same simultaneous-multithreading isolation discipline used against microarchitectural leakage in any multi-tenant host), and harden Enclave Page Cache (EPC) and page-table management specifically against the abuse pattern Foreshadow exploited.
- Enclave: write the enclave's OWN internal code to be constant-time, using the same techniques covered throughout this domain. This is a genuinely separate requirement from the enclave boundary itself, SGX protects against a privileged adversary reading enclave memory directly, not against that same adversary inferring secrets from the enclave's externally observable memory-access PATTERN, which its legitimate scheduling and paging control already gives it visibility into.
- Application: never trust a single enclave-internal computation of a high-value result (a private-key operation, a signature) without redundant verification where feasible, since Plundervolt is a direct demonstration that a privileged adversary can corrupt an enclave's INTERNAL computation without ever breaking its confidentiality boundary, so a result that was never independently checked can be silently wrong rather than silently leaked.
Residual risks
- Even after applying every mitigation above, SGX's threat model has an acknowledged structural limit: it was designed to protect the CONFIDENTIALITY and INTEGRITY of enclave data and code against a malicious privileged software stack, not to make the enclave's externally observable behavior invisible to that same stack. A hypervisor that legitimately controls scheduling and page-fault delivery retains that visibility no matter how the hardware mitigations above are configured, so residual side-channel risk has to be managed at the enclave-code level (constant-time discipline) and the deployment level (co-tenancy and scheduling policy), not assumed to be closed by the presence of the hardware feature itself.
Worked example
A concrete way these threads connect: a cloud provider running a shared SGX-capable host applies microcode updates addressing L1TF and the MDS family, and disables the vulnerable voltage-scaling interface at the firmware level in response to Plundervolt (CVE-2019-11157), closing the most consequential publicly disclosed SGX-specific attack classes. This is necessary but not sufficient: the provider's hypervisor still legitimately controls scheduling and page-fault delivery for every enclave on that host, and unless the ENCLAVE'S OWN code was written to be access-pattern-independent, a privileged operator (or an attacker who compromises the hypervisor) can still infer secrets from how the enclave's memory access pattern varies with its input, entirely without needing any of the three named hardware-level attacks.
Trade-offs and pitfalls
The single most consequential and common mistake is treating "our cryptographic code runs inside an SGX enclave" as a complete side-channel defense on its own. SGX's actual guarantee, confidentiality and integrity of enclave memory against a privileged adversary, is meaningfully narrower than "immune to side channels," and the enclave boundary does nothing about a malicious or compromised hypervisor's ability to legitimately observe scheduling and paging behavior and infer secrets from it; constant-time enclave code therefore remains mandatory, not optional, on top of the hardware isolation, exactly as in any other multi-tenant scenario covered in this domain. The mitigation costs are also real: disabling simultaneous multithreading and applying microcode-level buffer-clearing on certain transitions both carry a measurable, workload-dependent performance cost for enclave-heavy deployments, which creates the same pressure to skip them under load that recurs across every multi-tenant mitigation discussed here.
Build a toy hash function with a small internal state (say, 24-bit output), then implement a birthday attack that finds a collision in it. What constraints (time budget, allowed libraries) would make this tractable, and how would you verify your attack is actually exploiting the birthday bound rather than brute-forcing preimages?
Sample Answer
Direct answer
Truncate a strong hash (SHA-256) to 24 bits as the toy target, since building a genuinely weak-but-realistic compression function from scratch is its own research project and truncation gives an honest, small, uniformly-distributed output space to attack. The tractable constraint is memory, not time: a 24-bit space needs only about 1.25224≈5,134 hash calls to find a collision with even odds, small enough to hold every seen output in an in-memory dictionary with the standard library alone, no distinguished-point or disk-backed techniques required. Verifying the attack is exploiting the birthday bound, not brute-forcing a preimage, comes down to one check: is the search comparing every new draw against every PRIOR draw (birthday), or against one FIXED target chosen in advance (preimage)?
Structured elaboration
Building the toy hash. toy_hash(x) = SHA-256(x)[:3] (three bytes, 24 bits) is a defensible toy: it inherits SHA-256's strong statistical behavior over its truncated range (no artificial bias to accidentally exploit instead of the birthday bound) while making the birthday-bound work small enough to run in memory. Building a genuinely custom compression function from scratch risks accidentally introducing a REAL weakness that makes the "attack" trivial for the wrong reason (a structural bug, not the birthday bound), which would defeat the exercise's purpose.
Constraints that make it tractable. Space, not time, is the binding constraint: storing roughly 5,000 to 10,000 (input, output) pairs in a Python dict is trivial memory-wise, and no special hardware or parallelism is needed. Allowed libraries: nothing beyond the standard library (hashlib for the underlying strong hash, random for input generation); this is deliberately NOT representative of a real attack against a full-size hash, where a birthday-bound search over 264 or more candidates needs the van Oorschot-Wiener parallel rho method with disk-backed "distinguished points" specifically because you cannot hold that many entries in memory. Naming that gap explicitly is part of a complete answer: this toy proves the MECHANISM, not the SCALE, of a real attack.
Verifying you're exploiting the birthday bound, not brute-forcing a preimage. Two checks distinguish them. First, structurally: the attack must be an UNSTRUCTURED search, generate fresh random inputs, hash each, and check the new output against EVERY prior output seen so far, stopping at the first match, never fixing a target output in advance. Second, empirically: run the attack many times with different seeds and compare the AVERAGE number of tries against the birthday-bound prediction (≈5,134 for a 24-bit space) rather than against the full-space size (224=16,777,216); a search that only needs a few thousand tries on average is exploiting the birthday bound, one that needs tens of millions is doing something closer to brute-force preimage search, which is what a separate, deliberately-contrasted preimage experiment (search for an input matching ONE fixed target) should show taking dramatically longer, bounded by 224, not 212.
Worked example
Pinned seeds, 10 independent trials:
import hashlib, random, math
def toy_hash(x: bytes) -> int:
return int.from_bytes(hashlib.sha256(x).digest()[:3], "big")
def birthday_attack(rng):
seen, tries = {}, 0
while True:
tries += 1
x = rng.randbytes(8)
y = toy_hash(x)
if y in seen and seen[y] != x:
return tries, seen[y], x, y
seen[y] = x
def preimage_search(rng, target, cap):
for tries in range(1, cap + 1):
x = rng.randbytes(8)
if toy_hash(x) == target:
return tries, x
return None, None
theoretical = 1.2533 * math.sqrt(2 ** 24)
trial_counts = []
for seed in range(10):
rng = random.Random(seed)
tries, x1, x2, y = birthday_attack(rng)
trial_counts.append(tries)
print(f"seed={seed}: collision after {tries} tries")
avg = sum(trial_counts) / len(trial_counts)
print(f"average: {avg:.1f} theory: {theoretical:.1f}")
rng2 = random.Random(999)
fixed_target = toy_hash(b"whatever")
tries, x = preimage_search(rng2, fixed_target, 200_000)
print("preimage found within 200000 tries:", tries is not None)
Running this for seeds 0 through 9 gives collision counts of 6249, 2791, 5131, 5021, 9967, 9393, 1593, 4063, 7779, and 8746 tries, averaging 6073.3 tries, against the theoretical birthday-bound prediction of 5133.5 (1.2533×224), the same order of magnitude and well within the natural run-to-run variance of a randomized search. Contrast: a fixed-target preimage search against one pre-chosen output, capped at 200,000 tries (well under the roughly 16.7 million expected for a full brute-force preimage search), prints preimage found within 200000 tries: False, consistent with preimage search needing 224-scale work rather than 212-scale.
Trade-offs and pitfalls
A common mistake is truncating too aggressively (an 8-bit or 12-bit toy hash) where collisions happen so fast the birthday-versus-preimage distinction becomes hard to observe cleanly against run-to-run noise; 24 bits keeps the expected try-count in the thousands, large enough to average over many trials meaningfully while staying instantly tractable in memory. Another is forgetting to check seen[y] != x, without that guard, re-drawing the SAME input twice (a false collision, not a real one) can terminate the search early and corrupt the result; it's rare at 24 bits but worth guarding against on principle, since the guard costs nothing and the failure mode is silent. Finally, this toy result says nothing directly about the security margin of a real 128-bit or 256-bit hash beyond illustrating the mechanism; the actual attacker cost at real hash sizes comes from applying the same birthday-bound-versus-attacker-rate calculation at the real bit width, not from extrapolating this toy experiment's numbers directly.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths