What Actually Matters In Cryptography
Most people think cryptography is about hiding things. It's not. It's about making the wrong kind of exposure computationally impossible even when everything else leaks. I've spent years implementing and breaking crypto systems, and the gap between textbook descriptions and what actually happens in production is enormous. The principles are simple. The applications are where things get messy. Kerckhoffs's principle is the first thing you need to internalize. The algorithm must be public. Only the key stays secret. Every secure system I've ever seen relied on this. The ones that didn't collapsed the moment someone reverse-engineered the implementation. Security through obscurity doesn't work because it fails exactly when you need it most.
Introduction To Cryptography Principles And Applications Information Security And Cryptography
The core division in any real system is between symmetric and asymmetric cryptography. Symmetric uses the same key for encryption and decryption. AES-256 in GCM mode is the workhorse. It encrypts data fast, provides authentication in the same pass, and has been vetted through decades of public analysis. Asymmetric cryptography solves a different problem entirely. RSA and ECDSA handle key exchange and signatures. You can't replace AES with RSA for bulk encryption. RSA-OAEP with a 2048-bit key can encrypt roughly 190 bytes. Try pushing a single database record through it and you'll see why hybrid schemes exist. Here's how a normal hybrid scheme works in practice. Alice generates a random AES key. She encrypts her message with AES-GCM using that key. She encrypts the AES key with Bob's RSA public key. She sends both. Bob decrypts the AES key with his RSA private key, then decrypts the message. Two algorithms, two key sizes, one coherent protocol. This is what TLS does. This is what PGP does. This is what almost every serious system does. I ran into a specific problem last year that illustrates why understanding the primitives matters more than following a library. We were migrating an internal service from SHA-1 HMAC to SHA-256 HMAC. The old keys were still valid. New signatures used the new algorithm. I wrote a migration script that checked the hash prefix to determine which algorithm a signature used, then verified accordingly. The bug was that the verification function accepted a signature computed with the wrong key if the algorithm tag happened to match. A developer on the team had written the verification logic without binding the key identity to the algorithm identifier. An attacker who controlled the signing key for algorithm A could craft a valid-looking signature for algorithm B if the key length happened to align. It took three days to find. The fix was a single line: include the algorithm identifier as part of the signed data, not just as a tag in the metadata. HMAC-SHA256(key, algorithm || message) instead of whatever the previous implementation was doing. It sounds obvious now. It wasn't obvious during the audit.
Randomness Is The Actual Bottleneck
Everyone focuses on the encryption algorithm. The randomness source is where systems actually fail. CSPRNG stands for Cryptographically Secure Pseudorandom Number Generator. It's not the same as the random number generator in your standard library. The Mersenne Twister is fantastic for simulations. It is completely unsuitable for key generation because its state can be reconstructed from enough output samples. Use /dev/urandom on Linux, BCryptGenRandom on Windows, or the secrets module in Python. These sources feed from hardware entropy pools and are designed to resist state compromise extensions. A counternounce is another detail that causes production incidents. GCM mode requires a unique nonce for every encryption operation under the same key. If you reuse a nonce, the authentication tag becomes trivially forgeable and partial plaintext recovery is possible. I once saw a system that generated nonces by incrementing a counter stored in a database. The counter wrapped after 2^32 operations. The application didn't rotate the key. After roughly four billion requests, the same key was being used with identical nonces. Data integrity was compromised. The fix wasn't to use a better counter. It was to switch to deterministic nonce generation using a block cipher in counter mode, or to use XChaCha20-Poly1305 which gives you a 192-bit nonce space that makes collision probability negligible even under heavy load.
Get the Full Details

Hash Functions Work Differently Than People Expect
Password hashing and data hashing are not interchangeable. SHA-256 is fast. That's the problem when you're hashing passwords. A GPU can compute billions of SHA-256 hashes per second. bcrypt, scrypt, and Argon2 are deliberately slow. They tie memory and computation together so that parallel brute-force attacks become expensive. Argon2id is the current recommendation. It combines the memory-hard properties of scrypt with resistance to side-channel attacks. The parameters matter. For a typical server, setting memory to 64 megabytes, iterations to 3, and parallelism to 4 gives you acceptable latency around 100 milliseconds per hash while making offline attacks cost-prohibitive. Here's a detail that catches people out. Merkle-Damgård hash functions like SHA-256 are vulnerable to length extension attacks. If you know H(key || message), you can compute H(key || message || padding || extra_data) without knowing the key. HMAC solves this with a nested construction. H(K_opad || H(K_ipad || message)). But not everyone uses HMAC correctly. I've seen implementations that compute H(key || message) directly for authentication. That's broken. Switch to HMAC or use a hash based on the sponge construction like SHA-3, which doesn't have this vulnerability at all.
The Implementation Layer Where Everything Breaks
Choosing the right algorithm is the easy part. The hard part is the implementation. Timing attacks are real and they're subtle. A naive string comparison function returns false on the first mismatched byte. An attacker who can measure response time to microsecond precision can determine your secret byte by byte. Use constant-time comparison functions. Most mature libraries provide these. In Go there's crypto/subtle.ConstantTimeCompare. In Python it's hmac.compare_digest. In C you need to be careful because the compiler can optimize away your attempts at constant-time code. -O2 and above will sometimes break constant-time code unless you use compiler barriers or volatile operations. Side-channel resistance isn't just about timing. Cache attacks, power analysis, and electromagnetic leakage have all been used to extract keys from smart cards and cloud instances. If you're running cryptographic operations on shared infrastructure, assume that the hardware is observable. This is why Amazon Web Services offers HSM instances and why hardware security modules exist in air-gapped racks. The cost is real. A Thales Luna HSM runs into the tens of thousands. But the alternative is a breach that runs into the millions.
Key Management Is The Actual Problem
Encryption without key management is just obfuscation. Keys need to be generated, distributed, stored, rotated, and destroyed. Each step has failure modes. Key generation must use a CSPRNG. Key distribution over an insecure channel requires asymmetric cryptography or a trusted key distribution center. Key storage should never be in plaintext in a configuration file. AWS KMS, Azure Key Vault, and HashiCorp Vault solve this problem by providing centralized key storage with access control policies and audit logging. They're not perfect. They add latency. They create single points of failure. But implementing your own key management system is almost always worse. I worked on a project where we needed to encrypt data at rest in a distributed database. The requirement was that no single team member should be able to access raw decryption keys. We implemented a key splitting scheme using Shamir's Secret Sharing. The master key was split into five shares. Any three shares could reconstruct the key. Two of the five shares were held by the security team, two by the infrastructure team, and one by the application service account. The application could reconstruct the key when needed but couldn't do it alone. The overhead was measurable. Key reconstruction added about 50 milliseconds to each database connection setup. The tradeoff was acceptable given the threat model. Without the split, a compromised infrastructure credential would have given an attacker direct access to all encrypted data.

What Cryptography Cannot Do
This is the part that most introductions skip. Cryptography does not fix bad architecture. If your application logs plaintext passwords, encrypting the database won't help. If your TLS certificate validation is disabled because "it causes problems in development," you're not using cryptography. You're using theater. The strongest encryption in the world is useless when the key is stored in a git repository. Cryptography also cannot guarantee availability. A well-placed DDoS attack doesn't care how strong your AES keys are. It doesn't need to break the encryption. It just needs to make the service unreachable. That's an infrastructure problem, not a cryptographic one. Similarly, cryptography cannot prevent social engineering. If an attacker calls your help desk and convinces them to reset a password, the password hash strength is irrelevant. The human accepting the request is the weak link, and no algorithm fixes that. There are also scenarios where encryption actively hurts you. Forensic investigations require access to encrypted evidence. End-to-end encryption means service providers cannot comply with lawful access requests. If you're building a system that handles sensitive user data, you need to decide whether you want to be able to decrypt it yourself or whether you want to be cryptographically prevented from doing so. Both choices have legal and operational consequences. There is no neutral position.
Practical Recommendations That Actually Help
Don't write your own crypto. This applies even if you think you're good at it. The people who write crypto libraries spend their careers finding each other's mistakes. When you write your own, you're doing it in your spare time. Use established libraries. libsodium for general purpose cryptography. BoringSSL or OpenSSL for TLS. age for file encryption. These libraries have been reviewed. They handle edge cases you won't think of. They mitigate known attack classes. When you must choose between algorithms, prefer the ones with the largest security margin. SHA-3 over SHA-2 if you're starting a new design. Ed25519 over RSA for signatures. X25519 over RSA for key exchange. ChaCha20-Poly1305 over AES-GCM when hardware acceleration isn't available. These aren't preferences. They're based on concrete analysis of known attacks and implementation complexity. Ed25519 signatures are deterministic, which eliminates a whole class of nonce-related bugs. AES-GCM requires careful nonce management. Ed25519 doesn't have that problem. Measure your implementation. Use tools like TLS scope for configuration auditing. Run cryptcheck against your TLS endpoints. Check that you're not accepting legacy protocols. TLS 1.0 and 1.1 are broken. RC4 is broken. MD5 is broken. Disable them explicitly. Default configurations sometimes enable these for compatibility. Compatibility with broken protocols is not a feature.
The field moves slowly in some ways and fast in others. Post-quantum cryptography is entering standardization. NIST selected CRYSTALS-Kyber for key encapsulation and CRYSTALS-Dilithium for signatures in 2022. These algorithms are resistant to known quantum attacks but have larger key sizes and slower performance than their classical counterparts. Hybrid schemes that combine classical and post-quantum algorithms are the recommended migration path. Don't wait for the standard to be final. Start planning now because the migration will take years across the entire internet infrastructure.
