You are currently viewing Hashing, Encryption, and Signatures: Choosing the Right Cryptographic Tool

Hashing, Encryption, and Signatures: Choosing the Right Cryptographic Tool

You cannot decrypt a SHA-256 password digest. You can, however, guess a password, hash the guess, and check whether the digests match. That is why plain SHA-256 is a poor way to store passwords. Hashing, encryption, and password hashing solve different problems, even though they are often lumped together as “cryptography.”

Start with the property you need

Before choosing an algorithm, identify the data and what could go wrong. Confidentiality keeps data unreadable to unauthorized parties. Integrity lets a recipient detect changes. Authenticity helps establish who created a message or who holds a particular key. One property does not imply the others: encryption alone may not detect tampering, and a hash alone does not prove who produced a file.

Cryptography cannot decide whether a user is allowed to read a record; that is an authorization decision. Encrypting a database also will not protect plaintext that an application has legitimately decrypted and then exposed through a faulty endpoint. First, identify the trust boundary: is the data at rest, moving across a network, or held briefly in a running process?

Need Common primitive Critical condition
Keep recoverable data secret Authenticated encryption Protect the key and use nonces correctly
Detect accidental file changes Cryptographic hash Compare against a trusted digest
Store a password verifier Password-hashing function Use a unique salt and suitable cost settings
Verify a message from a shared-key peer Message authentication code Keep the shared secret private
Verify a signer without sharing its private key Digital signature Trust the correct public key

Hashing: a fingerprint is not a secret

A cryptographic hash maps an input of almost any length to a fixed-length digest. The same input always produces the same digest, while a tiny change should produce an unpredictable-looking result. SHA-256, for example, produces 256 bits of output. Strong hash functions are designed to make it computationally difficult to find an input matching a given digest or two distinct inputs with the same digest. A digest is not a compressed copy you can decode.

Hashes help identify file contents, index content-addressed objects, and support other cryptographic constructions. A downloaded file’s digest can reveal a mismatch—but only if the expected digest came from a source the attacker could not also alter. A file and its checksum on the same compromised server do not establish authenticity.

Password hashing needs deliberate slowness

General-purpose hashes are fast. That is useful for files and harmful for password verification: a stolen database of unsalted SHA-256 password digests allows rapid guessing and reveals which accounts share a password. Use a dedicated password-hashing function such as Argon2id, scrypt, or bcrypt, based on your platform’s maintained support and current guidance. These functions add computational cost; Argon2id and scrypt also offer memory-hard settings that make large-scale guessing more expensive.

Generate a fresh random salt for each password and store it alongside the verifier. The salt need not be secret. It stops identical passwords from producing identical stored values and prevents reuse of precomputed guesses. Measure work settings on your production hardware to balance verification time against guessing resistance. Store the algorithm and parameters with each verifier so you can upgrade an account after a future successful login. Rate limiting and breached-password screening address online and common-password risks that password hashing cannot solve on its own.

Encryption: secrecy depends on keys and modes

Symmetric encryption uses the same secret key to encrypt and decrypt. It works efficiently for files, database fields, and network traffic. Modern applications commonly use an authenticated encryption with associated data (AEAD) scheme, such as AES-GCM or ChaCha20-Poly1305. AEAD produces ciphertext and an authentication tag. If tag verification fails, decryption must reject the entire message; never pass unauthenticated plaintext to application logic.

Associated data is authenticated but not encrypted. An application might, for instance, bind ciphertext to a record identifier or format version. Decryption must supply exactly the same associated data. If the application consistently checks that binding, copying a valid encrypted value into the wrong context will fail verification.

A key protects records while metadata stays visible

Nonces are part of correctness

An AEAD operation usually requires a nonce, also called an initialization value. It is normally stored with the ciphertext and need not be secret, but the rules for generating it matter. Reusing a nonce with the same key can seriously compromise schemes such as AES-GCM. Size and uniqueness requirements vary by scheme, so follow the library’s API rather than casually inventing a counter format. If you cannot reliably maintain unique nonces across restarts and multiple writers, use an established library and design that explicitly handles that problem.

Encryption does not hide everything. Message length, send time, destination, and unencrypted associated data may remain visible. Compression, error messages, and application behavior can leak more, even when the encryption itself is sound. Ciphertext formatting and parsing belong in the security design, not in an afterthought about serialization.

MACs and signatures answer different trust questions

A message authentication code (MAC), such as HMAC-SHA-256, lets parties with the same secret key verify that a message has not changed and came from someone holding that key. HMAC does not encrypt the message. And because every verifier holds the secret, a MAC cannot prove to an independent observer which holder created a particular message.

A digital signature uses a private key to sign and a corresponding public key to verify. Many recipients can check a software release or document without receiving the signing secret. A valid signature proves the relevant private key signed the specified bytes. It does not, on its own, prove that the key belongs to the person or organization named in a user interface; that depends on how the public key was obtained and trusted.

Both MACs and signatures depend on an agreed representation of the data. If one component signs one byte sequence and another reconstructs the message differently, verification may fail or cover different data than intended. Prefer standard formats and library routines. For an API request, specify exactly which method, path, body bytes, timestamp, and other fields are authenticated. Where the protocol requires freshness, reject unacceptable replayed or expired requests.

Public-key cryptography and the key-exchange problem

Asymmetric cryptography gives public and private keys different roles. A sender can use a recipient’s public key as part of an encryption protocol; only the matching private-key holder should be able to recover the protected secret. In practice, public-key operations often establish or wrap a short-lived symmetric key, which then protects the bulk data. Signatures use the pair another way: the private key signs, and the public key verifies.

Public keys help with distribution only if you can check who owns them. If an attacker substitutes their key for a colleague’s, encryption to that key keeps the data secret from everyone except the attacker. Certificates, trusted key directories, pinned keys, and out-of-band fingerprint checks can establish the binding, each with operational trade-offs. Simply accepting a key delivered over the same untrusted channel as the message does not establish ownership.

Transport Layer Security (TLS) combines several of these ideas. A client checks a server certificate against its trust rules, the parties establish session keys, and authenticated encryption protects the connection. A valid certificate confirms an identity under the certificate system’s rules; it does not guarantee that the application behind it is safe. Skipping certificate validation removes a central defense against interception. Use the platform’s maintained TLS stack rather than building your own handshake.

Certificate checks help establish the server's identity

Key management is where deployments succeed or fail

A strong algorithm offers little protection when its key is committed to a repository, printed in logs, or shared across unrelated environments. Generate keys with the operating system’s cryptographically secure random-number generator, not timestamps, predictable identifiers, or ordinary pseudorandom generators. A human-memorable password needs a suitable password-based key derivation function before it can serve as encryption-key material; it is not equivalent to a randomly generated key.

Map each key’s lifecycle before deploying it:

  • Creation: identify the source of randomness, intended algorithm, and permitted uses.
  • Storage: restrict access through an appropriate secrets manager, key-management service, hardware-backed facility, or platform keystore.
  • Use: give access only to processes that need the key, and avoid logging secrets or decrypted values.
  • Rotation: record a key identifier with encrypted data so older records remain decryptable while new writes use a replacement key.
  • Recovery and retirement: plan backups, revocation, deletion, and what happens if a key is permanently lost.

Rotation need not mean re-encrypting every stored value immediately. You can keep old keys available for reads while writing with a new one, then migrate records and retire keys you no longer need. A suspected compromise calls for different priorities: determine what the exposed key could access, stop using it for new data, and assess whether affected data needs re-encryption or credentials need replacement. The answer depends on where the key was exposed and whether copies of the protected data could have been obtained.

Use established libraries, then test the boundaries

Writing an encryption algorithm to learn how it works is different from deploying one to protect users. Production code should use maintained cryptographic libraries and documented, high-level APIs. Look for safe defaults, support for the formats you need, and security updates. Avoid obscure modes, custom padding, and home-built combinations of primitives unless a reviewed protocol requires them.

An encrypt-decrypt round trip is only the start of testing. Use known test vectors where available. Check that altered ciphertext, tags, nonces, or associated data are rejected, and test empty input, maximum supported lengths, malformed encodings, and key-version changes. Authentication failures must not return partial plaintext or reveal sensitive details in responses or logs. Faulty failure behavior can remain invisible in ordinary success-path tests.

Check where protection begins and ends, too. A field encrypted in storage may still appear in search indexes, backups, debug traces, or analytics exports. TLS may protect browser-to-gateway traffic while the gateway-to-service connection follows a different path. Draw a simple data-flow diagram and mark every place plaintext exists and every component allowed to hold a key.

A small design exercise

Suppose an application stores a user’s recovery note and later returns it to that same authorized user. The note must be recoverable, so password hashing will not work. Choose a supported AEAD scheme, generate an encryption key through the approved key service, and store the scheme version, key identifier, nonce, ciphertext, and tag in a documented format. Authenticate a stable record identifier as associated data so ciphertext moved to another record fails verification. Authorization must still check the requesting user’s access before decryption.

Test the boundary directly: encrypt a sample note under record ID 42, then confirm it decrypts with ID 42. Try the unchanged ciphertext with record ID 43 as associated data. That attempt must fail without returning any part of the note.