Data Protection and Encryption in Practice Questions
Protecting data at rest and in transit across real systems from an engineering rather than pure-cryptography standpoint. Covers encryption strategy and key management for stored and transmitted data, secrets and sensitive-data handling, tokenization and secure elements for payment and sensitive data, and secure data handling in application code. Applied data-protection controls, distinct from cryptographic primitive design and from privacy-regulation compliance.
Design a field-level encryption approach for a microservices architecture where specific PII fields, for example a social security number or email address, must be encrypted at the service boundary while some services still need to index or search on those fields. Cover deterministic versus randomized encryption, key-per-field versus key-per-tenant, and how you would handle schema versioning as encrypted fields change type or size.
Sample Answer
Direct answer
Encrypt PII (personally identifiable information) fields like a social security number or email address at the service boundary, wrapping a per-field data key with a key from a central KMS (Key Management Service), and choose deterministic encryption only for the specific fields that must remain exactly searchable, randomized encryption for everything else, since randomized ciphertext reveals nothing about whether two values match.
Structured elaboration
Deterministic versus randomized: Deterministic encryption produces identical ciphertext for identical plaintext every time, which enables equality lookups and database joins, but leaks pattern information: anyone who can see the ciphertext column can tell which rows share a value, and frequent values become visible through simple frequency analysis. Randomized encryption (for example AES-GCM with a fresh random nonce each time) produces different ciphertext every time for the same plaintext, so no lookup is possible without decrypting, and no pattern leaks. Default to randomized; use deterministic only where a genuine business need for exact-match search exists on that specific field.
Key-per-field versus key-per-tenant: Key-per-field uses a separate data key for each field type (one for SSNs, another for emails) across all tenants, limiting the blast radius of a key compromise to one field type. Key-per-tenant uses one key hierarchy per tenant covering all of that tenant's fields, limiting blast radius to a single tenant, and it enables crypto-shredding: deleting a tenant's key instantly and irreversibly makes all of that tenant's encrypted data unreadable, which is a fast, reliable way to satisfy a tenant-offboarding data-deletion requirement without a slow row-by-row delete job.
Where decryption happens: Decrypting inside the calling application, using a shared internal library, keeps the database and any database proxy from ever seeing plaintext, and centralizes the crypto logic so individual teams don't reimplement it insecurely. Decrypting at a database-proxy layer (a sidecar sitting between the application and the database) centralizes crypto operations without requiring every service to integrate the library, but turns that proxy into a single high-value target that needs its own hardening. For most PII, application-layer decryption is the safer default; a proxy is a reasonable compromise for a large number of legacy services that can't easily be touched.
Library selection for a polyglot stack: The same field must decrypt correctly whether it was written by a Java service or read by a Python one, so pick a single, well-vetted, cross-language cryptographic library or specification, such as Google's Tink, rather than letting each team choose its own primitives independently, and verify that test vectors produce byte-identical ciphertext behavior across every language in use before relying on it.
Schema versioning: Attach a small header to every ciphertext recording the key ID and algorithm version used. When a field's encryption scheme changes, for example moving email from randomized to deterministic because a new search requirement appeared, run a background job that decrypts with the old key and version, re-encrypts with the new one, updates the version tag, and only then removes the old ciphertext. This allows field-by-field migration without a risky, all-at-once cutover.
Concrete field examples. Email is a strong candidate for deterministic encryption, since "does this email already exist" is a common exact-match check during signup. A credit card number generally should not be handled by this kind of general field-level encryption at all; it belongs in a dedicated tokenization flow with its own PCI-scoped vault, not the shared field-encryption path used for identifiers like email or SSN.
Worked example
A signup service receives { email, ssn }. The email field is encrypted deterministically with the tenant's per-tenant data key, so a later WHERE email = ? lookup at signup time works without decrypting every row. The SSN field is encrypted with a randomized scheme using the same tenant key, since nothing in the product needs to search on it, and its ciphertext changes every time even for the same value. Both data keys are themselves wrapped by a tenant-scoped key held in the central KMS, so revoking that tenant's access is a single key operation, not a per-field cleanup.
Trade-offs and pitfalls
Deterministic encryption on a low-cardinality field, a boolean flag or a two-digit country code, leaks almost the entire value through frequency analysis, since there are only a handful of possible ciphertexts to distinguish; never apply it there. Combining key-per-tenant encryption with a shared, cross-tenant search index also breaks isolation unless the index itself is scoped per tenant.
What secret-scanning approaches would you recommend to catch secrets before they ever reach source control, covering source code, container images, and CI logs? Compare static, regex-based, and machine-learning-based scanners, and explain how you would keep false positives and false negatives manageable in a production scanning pipeline.
Sample Answer
Direct answer
Static, regex-based scanners match known secret formats (an AWS access key's AKIA prefix, a private key's PEM header, a JWT (JSON Web Token)'s three-part structure) and are fast and deterministic, but blind to secrets that don't match a known pattern. Entropy-based checks catch unknown formats by flagging any high-randomness string, at the cost of more false positives on things that merely look random (hashes, UUIDs, test fixtures). Newer machine-learning and live-verification approaches go a step further by actually testing a candidate against the real provider API to confirm it's a working credential, which sharply cuts false positives without needing a hand-written pattern for every secret type.
Structured elaboration
Coverage needs to span three surfaces, not just source code:
- Source code: the most common target, scanned via regex/entropy/verification tooling at multiple points in the lifecycle.
- Container images: a secret can be baked into an image layer even when the source repository is clean (a build argument or a copied config file), so image-layer scanning is a separate, necessary check.
- CI logs: a job can echo a secret at run time even when nothing sensitive was ever committed to source, so scanning the captured logs themselves closes a gap the other two miss.
Managing false positives and false negatives in production: maintain an allowlist or baseline file for known false positives (test fixtures, example keys in documentation) so the same finding doesn't get re-triaged every run; prefer scanners that support live verification over purely pattern-based detection, since a verified hit ("this AWS key authenticates right now") is close to zero false positives; and accept that entropy-only detection needs a tuned threshold specific to the codebase, since a threshold copied from another project either misses real secrets or drowns the team in noise.
Worked example
Where scanning fits in the SDLC (software development lifecycle), across three stages: a pre-commit hook is the earliest and cheapest point to catch a mistake, before it's even recorded in history; a required CI check on every pull request is the broad safety net that catches what a bypassed or missing pre-commit hook let through (a developer running git commit --no-verify, for instance); and a periodic full-history scheduled scan catches anything that slipped past both of the first two checks, including secrets committed before scanning was ever adopted.
That CI check should be wired as a required status check that fails the build on a high-confidence match, blocking the merge until the finding is resolved or explicitly allowlisted with a documented reason, rather than merely reporting a warning that's easy to ignore.
Trade-offs and pitfalls
Scanning source control and CI is necessary but not sufficient: a credential that was valid, got scanned and found clean, and is still sitting unrotated in a running environment months later is a real and separate risk. Continuous validation of already-deployed secrets, tracking each credential's last-used timestamp and periodically confirming it's still needed, catches the stale-but-technically-not-leaked case that point-in-time source scanning cannot.
Compare symmetric and asymmetric encryption from a practical, decision-making perspective: performance characteristics, key distribution and management implications, and typical use cases such as bulk data encryption versus key exchange. Explain where hybrid approaches combining both are used and why.
Sample Answer
Direct answer
Symmetric encryption uses one shared secret key for both encrypting and decrypting, and is fast enough for bulk data. Asymmetric encryption uses a mathematically linked key pair, a public key anyone can use to encrypt and a private key only the owner holds to decrypt, which solves the problem of distributing a secret in advance, but at a real computational cost that makes it impractical for large volumes of data.
Structured elaboration
| Symmetric | Asymmetric | |
|---|---|---|
| Keys | One shared secret key | A public/private key pair |
| Speed | Fast; suited to bulk data | Much slower per byte |
| Key distribution | Hard: both parties need the same secret beforehand, without an intercepted channel | Easy: the public key can be shared openly |
| Typical use | Encrypting the actual payload (files, database fields, network traffic) | Exchanging a symmetric key, or signing/verifying identity |
| Examples | AES (Advanced Encryption Standard) | RSA, elliptic curve cryptography (ECC) |
The performance gap is structural, not just an implementation detail: symmetric ciphers work through relatively simple, hardware-accelerated substitution and permutation operations on fixed-size blocks, while asymmetric algorithms rely on expensive number-theory operations, large modular exponentiation for RSA or elliptic curve point multiplication for ECC, which cost far more computation per operation. That is why no practical system encrypts bulk data directly with an asymmetric algorithm.
Key distribution is the other deciding factor, and it scales badly for symmetric-only designs. If five people each need to communicate securely with every other one using only symmetric keys, they need a separate shared key for every pair: 5 * 4 / 2 = 10 distinct keys. Add a sixth person and it jumps to 15. With asymmetric cryptography, each person only needs one key pair (5 pairs total for 5 people, or 6 for 6) and publishes the public half; nobody has to pre-share a secret with anyone else.
Worked example
This is exactly the problem hybrid encryption solves, and it's why TLS (the protocol behind HTTPS) uses both: when a browser connects to a server, the two sides use asymmetric cryptography only briefly, to agree on a random, one-time symmetric session key (via an asymmetric key exchange or by encrypting it with the server's public key). Every subsequent byte of the actual session, the page content, the API responses, is then encrypted with that symmetric key using a fast cipher like AES. This gets the key-distribution benefit of asymmetric crypto and the speed of symmetric crypto without paying either one's downside for the whole session.
Trade-offs and pitfalls
Never design a custom hybrid scheme from first principles; use an established protocol like TLS, which has been through extensive cryptographic review. A common mistake is assuming asymmetric encryption is simply "more secure" and therefore always preferable: it solves a different problem (key distribution) and using it for large payloads wastes CPU and adds latency for no real security benefit once a symmetric key is already safely shared.
Describe practical techniques to prevent secrets from leaking into application logs and monitoring telemetry. Cover how you would configure logging libraries, structured logging, and redaction so that a startup-time configuration dump or an error trace never captures a live credential.
Sample Answer
Direct answer: Structural prevention beats catching secrets after the fact: keep secret values out of anything that gets turned into a log line in the first place, using typed wrapper objects, redaction filters attached to the logging pipeline, and a rule against dumping whole config or request objects, then back that up with automated scanning of what actually reaches the log store, since a hand-reviewed rule will eventually miss a new field name.
Structured elaboration:
Keep secrets out of the value that gets logged. Wrap secret values in a type that refuses to render its contents in string conversion or JSON serialization; many configuration and validation libraries ship a "secret string" type for exactly this, and a two-line wrapper class works if yours doesn't. This stops the single most common leak: someone logs "here's my config" and the config object happens to contain a secret field. Relatedly, never log a whole request, response, or config object by dumping it wholesale; log only the specific fields a debugging session needs. A broad startup-time dump of an entire config object is exactly the pattern that leaks credentials.
Redact at the logging layer, as a second line of defense. Attach a filter or processor to your logging library that pattern-matches likely secret shapes, known key names like password or api_key, or known credential formats such as cloud access-key prefixes, and masks the value before the line is formatted, so redaction happens even if application code slips up. Apply the same discipline to exception traces and APM (application performance monitoring, the tooling that captures traces and errors for debugging production issues) tooling, since an unhandled exception that captures a secret in a local variable will otherwise ship it straight into your error tracker.
Verify with automated scanning, not just review. Run the same class of secret-detection scanner you'd use on source code, pattern and entropy based, against sample log output in continuous integration, and periodically against the live log store, since new fields get added over time and a manual checklist won't catch them all.
Worked example:
import logging, re, io
# Matches key=value pairs where the key NAME contains a secret-ish word
# (password, token, api_key, ...), even as part of a longer identifier
# like db_password, and captures the whole value (quoted or bare) so the
# entire assignment can be replaced.
SECRET_KV = re.compile(
r'(?i)([\w]*(?:password|passwd|secret|api[_-]?key|token)[\w]*)\s*=\s*'
r'(\'[^\']*\'|"[^"]*"|\S+)'
)
AWS_KEY_ID = re.compile(r'\bAKIA[0-9A-Z]{16}\b')
class RedactFilter(logging.Filter):
"""Attach to any handler; rewrites the rendered message before it is
emitted so secrets never reach disk, stdout, or a log shipper."""
def filter(self, record):
msg = record.getMessage()
msg = SECRET_KV.sub(lambda m: f"{m.group(1)}=***REDACTED***", msg)
msg = AWS_KEY_ID.sub("***REDACTED***", msg)
record.msg = msg
record.args = ()
return True
stream = io.StringIO()
logger = logging.getLogger("startup")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(stream)
handler.addFilter(RedactFilter())
logger.addHandler(handler)
logger.info("Booting service with config: db_password='hunter2-prod-db', api_key=sk_live_abc123xyz")
logger.info("Loaded AWS key AKIAABCDEFGHIJKLMNOP for backup job")
print(stream.getvalue(), end="")
Output:
Booting service with config: db_password=***REDACTED***, api_key=***REDACTED***
Loaded AWS key ***REDACTED*** for backup job
Two angles worth folding in from real incidents of this shape. First, if a pentest finds that a service is already writing secrets to your log aggregator at startup, the fix isn't only "add redaction and redeploy": treat every secret that appeared in logs as compromised and rotate it, because logs are frequently replicated (to a SIEM, a backup, a third-party log-shipping vendor) beyond your primary retention window, so deleting the original log line doesn't guarantee the value is gone everywhere. Second, to confirm whether a secret was actually exfiltrated via logs rather than merely present in them, search the log aggregator and every downstream copy, exports, backups, any third-party shipping destination, for the exposure window, then check access logs on the log store itself for who queried that data in the same window; treat "we can't prove it wasn't accessed" as equivalent to "assume it was" when deciding whether to rotate.
Trade-offs and pitfalls: Overly aggressive redaction, masking anything that looks vaguely secret-shaped, can hide the exact field an on-call engineer needs to debug a real incident; tune patterns narrowly and give engineers an audited break-glass path to an unredacted view when they truly need one. A redaction filter also only sees what's already been rendered into a message, so a secret embedded in a stack trace's local-variable dump can bypass a filter that only checks the top-level message string.
Explain the practical differences between encryption at rest, encryption in transit, and encryption in use. For each category, give two concrete examples from a typical cloud and on-premise stack, and describe the primary threats each one defends against and the residual risk that remains even when it is correctly implemented.
Sample Answer
Direct answer
Data protection has to cover three different moments in a value's life: while it sits on a disk (at rest), while it moves across a network (in transit), and while a program is actively working with it in memory (in use). Each state has a different attacker in mind, and being strong in one gives you no protection in the others.
Structured elaboration
| State | What it protects | Typical mechanism | Defends against | Residual risk |
|---|---|---|---|---|
| At rest | Data stored on disk, in a database, or in an object store | Full-disk or volume encryption, database TDE (Transparent Data Encryption), object-store server-side encryption (SSE) | Theft of a physical drive, exfiltration of a raw backup or storage snapshot | An attacker with valid application credentials, or a bug that lets them query the app normally, still sees decrypted data |
| In transit | Data moving over a network | TLS (Transport Layer Security) between a browser and a server, mTLS (mutual TLS, where both sides present a certificate) between internal services | Eavesdropping or a man-in-the-middle on the network path | Nothing once the data lands: whatever sits unencrypted on either endpoint before send or after receive is fully exposed |
| In use | Data actively being processed by the CPU | Confidential computing: hardware-isolated memory regions (Trusted Execution Environments) that keep even the host operating system or hypervisor from reading process memory | A compromised host OS, hypervisor, or cloud operator trying to read a running process's memory | A bug in the code running inside the protected region, or a side-channel attack against the hardware itself, both bypass it |
The same logic scales across an enterprise's whole storage surface, not just one database: a relational database's TDE, an object store's SSE, a message queue's on-disk encryption (for example Kafka's disk-level encryption), and encrypted backups are all just different instances of "at rest," judged by the same threat model. Who actually holds the key matters as much as whether encryption exists at all: a secrets manager might use a fully provider-managed key inside a cloud KMS (Key Management Service, the service that generates and guards encryption keys), or you might bring your own key (BYOK), which changes whether the provider itself could ever access your data even under compulsion.
Worked example
A payment record moves through three states in one request: it is written to a database with TDE enabled (at rest), read back by an API service over mTLS (in transit), then held in that service's memory while an interest calculation runs (in use). If the at-rest and in-transit controls are both configured correctly, a SQL injection vulnerability in the application layer can still read the row in plaintext, because the app is trusted to decrypt it as part of normal operation. Encryption at rest defends against someone bypassing the app to read raw storage, not against someone abusing the app itself.
Trade-offs and pitfalls
Encryption at rest and in transit are inexpensive, mature, and should be the default everywhere. Encryption in use is a much heavier tool: it requires specialized hardware, has real performance and compatibility costs, and should be reserved for cases where you specifically distrust the infrastructure operator (your own cloud provider, or a shared host) rather than applied by default. None of the three states protect against an authorization bug, an insider with legitimate key access, or a compromised credential; they are complementary controls, not substitutes for access control.
Unlock Full Question Bank
Get access to all 22 Data Protection and Encryption in Practice interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.