- Introduction
- Chapter 1 The Developer’s Crypto Mindset and Choosing the Right Primitive
- Chapter 2 Sources of Randomness: Entropy, RNGs, and Nonces
- Chapter 3 Symmetric Encryption: From Block Ciphers to AEAD
- Chapter 4 Message Integrity: MACs, HMAC, and AEAD Tags
- Chapter 5 Hashing and Key Derivation: SHA-2/3, HKDF, PBKDF2, scrypt, Argon2
- Chapter 6 Public-Key Basics: RSA, Diffie–Hellman, and Elliptic Curves
- Chapter 7 Signatures in Practice: RSA-PSS, ECDSA, Ed25519
- Chapter 8 Key Exchange and Forward Secrecy: ECDH and X25519
- Chapter 9 Managing Keys: Generation, Storage, Rotation, and Deletion
- Chapter 10 Hardware and Cloud Key Management: HSMs, TPMs, and KMS
- Chapter 11 Transport Security: TLS 1.3, mTLS, and Certificate Hygiene
- Chapter 12 Secure Data at Rest: File, Database, and Secrets Encryption
- Chapter 13 Authentication Tokens and Claims: JWTs, PASETO, and Alternatives
- Chapter 14 Secure Protocol Design: Boundaries, Context, and Associated Data
- Chapter 15 End-to-End Encryption Patterns: Messaging and File Sharing
- Chapter 16 Working with Modern Libraries: Libsodium, Tink, WebCrypto, OpenSSL
- Chapter 17 Cross-Language and Cross-Platform Pitfalls: Mobile, Web, Backend
- Chapter 18 Avoiding API Misuse: Nonce Reuse, Padding Oracles, and RNG Failures
- Chapter 19 Side-Channel Awareness: Timing, Caching, and Memory Safety
- Chapter 20 Performance and Scalability: Streaming, Batch, and Async Cryptography
- Chapter 21 Monitoring Encrypted Systems: Logging, Metrics, and Key Events
- Chapter 22 Regulations and Compliance: NIST SP 800-series and FIPS 140-3
- Chapter 23 Testing and Verification: Test Vectors, Fuzzing, and CI Checks
- Chapter 24 Migration Strategies: Deprecations, Algorithm Agility, and Key Rotation
- Chapter 25 Practical Recipes and Case Studies: Secure-by-Default Implementations
Practical Cryptography for Developers
Table of Contents
Introduction
Software teams reach for cryptography to protect data, authenticate users, and prove integrity—but the distance between “it compiles” and “it’s secure” can be vast. This book was written to close that gap for working developers. Rather than asking you to wade through heavy mathematics, we focus on what you need to ship secure systems: how to pick the right primitive, call modern libraries correctly, and avoid the pitfalls that turn good intentions into vulnerabilities.
We begin by building a mental model for where cryptography fits in real architectures: the threats you’re defending against, the assets you care about, and the failure modes that matter. With that framing, the choices among symmetric encryption, hashing, key derivation, signatures, and key agreement stop feeling abstract and start mapping to concrete requirements like confidentiality, integrity, authenticity, forward secrecy, and replay resistance. You’ll learn why “AES” is not a complete answer, why authenticated encryption beats ad‑hoc combos, and why nonce management is as important as key length.
Throughout, we lean on mature, well-vetted libraries and safe-by-default APIs. You’ll see how to accomplish common tasks—encrypting files, securing database fields, establishing TLS with mutual authentication, signing tokens—without reinventing primitives or protocols. Where trade-offs exist, we’ll call them out: usability versus security, performance versus battery, compliance versus practicality. You’ll also learn to recognize red flags in API designs and documentation, so you can avoid subtle hazards like nonce reuse, padding oracle exposure, weak randomness, and misapplied hash functions.
Effective cryptography depends on effective key management. We therefore devote significant attention to generating strong keys, storing them securely, rotating them safely, and retiring them responsibly. You’ll work with hardware security modules and cloud key management services, understand envelope encryption, and design systems that keep secrets out of logs, crash reports, and build artifacts. We’ll also cover operational realities—monitoring, incident response, and the guardrails that keep encryption usable and maintainable over time.
Because systems evolve, migration is a first-class concern. You’ll learn algorithm agility, how to phase out legacy choices like obsolete ciphers or hash functions, and how to introduce modern primitives without breaking existing clients or data at rest. We’ll test our implementations using reference vectors, fuzzers, and property-based tests, integrating those checks into CI so regressions are caught before release.
Finally, this is a practical book. Each chapter ends with implementation guidance and patterns you can adapt: secure storage of application secrets, AEAD usage for messages and files, safe token formats, TLS 1.3 setup with sane defaults, and end-to-end encryption building blocks. By the end, you won’t just know the names of modern cryptographic primitives—you’ll know when to use them, how to wire them together, and how to keep them from becoming liabilities in production.
CHAPTER ONE: The Developer’s Crypto Mindset and Choosing the Right Primitive
Welcome to the trenches of practical cryptography, where good intentions often pave the road to vulnerable systems. This isn't a theoretical exploration; it’s a field guide for developers navigating the intricate landscape of digital security. Our journey begins not with algorithms or code, but with something far more fundamental: the developer’s mindset. Without the right perspective, even the strongest cryptographic primitives can become liabilities.
The crypto mindset isn't about becoming a cryptographer. It's about cultivating a healthy paranoia, a deep skepticism, and an understanding that security is not a feature you bolt on at the end, but a continuous process woven into the fabric of your application from the very first line of code. It's realizing that convenience and security are often at odds, and that defaults are rarely secure-by-design in the cryptographic world. This mindset embraces the fact that attackers are clever, persistent, and always looking for the weakest link.
One of the most crucial elements of this mindset is understanding that "rolling your own crypto" is almost universally a terrible idea. Cryptographic primitives are the basic building blocks, like hash functions, block ciphers, or digital signatures, designed to perform one specific task reliably. Designing these primitives is a highly specialized, time-consuming, and error-prone endeavor, even for experts. They undergo years of scrutiny by the cryptographic community to gain confidence in their security. As developers, our role is to choose and use well-vetted, established primitives and libraries, not to invent new ones.
The allure of custom solutions is strong, particularly for developers who enjoy problem-solving and optimization. However, the subtle mathematical nuances and potential side-channel attacks that can compromise custom cryptographic implementations are far beyond the scope of typical software development. Even a seemingly minor deviation from established practices can introduce catastrophic vulnerabilities. Trust in widely accepted and extensively tested cryptographic libraries is paramount.
Another cornerstone of the crypto mindset is recognizing that cryptography alone isn't a magic bullet. It’s a powerful tool, but its effectiveness is entirely dependent on its correct application within a broader security architecture. You can encrypt data with the strongest algorithms, but if the keys are hardcoded in the source code or stored insecurely, the encryption is rendered useless. This holistic view of security, where cryptography is just one layer of defense, is vital.
We must also embrace the idea of "defense in depth." This means layering multiple security mechanisms so that if one fails, others are still in place to protect the system. For instance, combining robust encryption with strong access controls and secure key management creates a much more resilient system than relying on any single component. This layered approach acknowledges that no single solution is foolproof and that redundancy in security is a virtue.
The rapid evolution of threats and cryptographic research also demands a mindset of continuous learning and adaptation. Algorithms considered strong today might be vulnerable tomorrow due to new cryptanalytic breakthroughs or advancements in computing power. Therefore, staying informed about current best practices, deprecated algorithms, and emerging threats is not optional; it’s a professional responsibility.
Now, let's pivot to the practical side: choosing the right cryptographic primitive. This is where the abstract concepts of the crypto mindset translate into concrete technical decisions. The first step, and arguably the most important, is to define your security requirements clearly. What exactly are you trying to protect? From whom are you protecting it? What are the potential consequences if your defenses fail? These questions guide your choices.
For instance, if your goal is data confidentiality—ensuring that only authorized parties can read sensitive information—then you'll need an encryption algorithm. If you need data integrity—guaranteeing that data hasn't been tampered with—then hash functions or message authentication codes (MACs) are your tools. Authentication, proving the identity of a user or system, often involves digital signatures. Most real-world systems will require a combination of these.
Consider the nature of the data itself. Is it data at rest (stored on a disk, in a database)? Or is it data in transit (being sent over a network)? The threats and appropriate cryptographic solutions can differ significantly. Data at rest might require strong disk or file encryption, while data in transit typically relies on protocols like TLS (Transport Layer Security).
Once you've identified your security requirements, you can start to map them to specific cryptographic primitives. These primitives are essentially the mathematical algorithms that perform fundamental security tasks. They are categorized broadly into symmetric-key cryptography and asymmetric-key (or public-key) cryptography.
Symmetric-key cryptography uses a single, shared secret key for both encryption and decryption. It’s generally much faster and more efficient than asymmetric encryption, making it suitable for encrypting large volumes of data. Think of it like a shared secret handshake – both parties know the secret, and use it to secure their conversation. The Advanced Encryption Standard (AES) is the most widely used symmetric encryption algorithm today and is considered the standard for securing sensitive data.
Asymmetric-key cryptography, on the other hand, employs a pair of mathematically related keys: a public key for encryption and a private key for decryption. The public key can be freely shared, while the private key must remain secret. This ingenious design allows two parties to communicate securely without ever having to exchange a secret key beforehand, solving the "key exchange problem" inherent in symmetric systems. Algorithms like RSA and Elliptic Curve Cryptography (ECC) fall into this category. While less efficient for bulk data encryption, asymmetric methods are crucial for secure key exchange and digital signatures.
Hash functions are another fundamental primitive. They take an input of arbitrary length and produce a fixed-length output, often called a "hash" or "digest." Cryptographically secure hash functions have two main properties: they are non-reversible (it's practically impossible to reconstruct the original input from its hash) and collision-resistant (it's practically impossible to find two different inputs that produce the same hash value). These properties make them invaluable for verifying data integrity, storing passwords securely, and in blockchain protocols. SHA-256 and SHA-3 are examples of recommended hash functions.
Beyond these core primitives, you'll also encounter Key Derivation Functions (KDFs), which are specialized functions that transform passwords or other non-uniform inputs into strong cryptographic keys. Since human-chosen passwords are often weak and low-entropy, KDFs like PBKDF2, scrypt, and Argon2 are essential for strengthening them against brute-force attacks by introducing computational cost and memory usage.
Digital signatures are built using asymmetric cryptography and hash functions. They provide authenticity (proving the sender's identity) and integrity (ensuring the message hasn't been altered). When you digitally sign a document, you're essentially using your private key to "stamp" a hash of the document, which anyone can then verify with your public key.
Finally, Random Number Generators (RNGs) are indispensable. The security of cryptographic keys and other values often relies on true randomness. If cryptographic keys can be guessed or predicted due to weak randomness, even the strongest algorithms are compromised. Cryptographically Secure Pseudo-Random Number Generators (CSPRNGs) are used to generate high-quality random numbers for cryptographic purposes. This is so important that Chapter 2 is entirely dedicated to sources of randomness.
In summary, choosing the right primitive starts with a clear understanding of your security objectives. Do you need confidentiality, integrity, authenticity, or a combination? Once those are defined, you can select the appropriate cryptographic building blocks: symmetric encryption for bulk confidentiality, asymmetric encryption for key exchange and digital signatures, hash functions for integrity, and KDFs for password-based key generation. Always remember the developer’s crypto mindset: prioritize well-vetted libraries, embrace defense in depth, and stay vigilant against evolving threats.
This is a sample preview. The complete book contains 27 sections.