Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Hashing vs Encryption vs Encoding

By Kokil Thapa | Last reviewed: September 2026

Hashing vs encryption vs encoding trips up even experienced teams because all three transform data into something that looks unreadable. They solve different problems. Mix them up and you store passwords you can decrypt, or you encrypt API payloads you can never verify. On production custom software projects, I treat this distinction as architecture—not a trivia question. This guide maps each technique to real use cases with PHP 8.3+ and Laravel 13 examples you can ship today.

What is the difference between hashing, encryption, and encoding?

All three change how bytes look on disk or on the wire. Only two protect confidentiality. Only one is designed to be irreversible.

Hashing runs input through a one-way function. The same input yields the same digest. You cannot derive the original string from the hash. Password verification works by hashing the login attempt and comparing digests.

Encryption uses a key to scramble data. Anyone with the correct key can decrypt. Use it when the application must read the original value later—payment tokens, national ID numbers, or document contents in a client portal.

Encoding represents binary data as text. Base64 is the common example. No key is involved. Decoding is trivial. Encoding is not security. It is a format change.

Three Data TransformationsHashingOne-way digestNo key neededPasswordsEncryptionTwo-way with keyConfidentialityPII, tokensEncodingReversible formatNo key at allBase64, UTF-8Plaintext: user@example.comHash = verify only | Encrypt = read later | Encode = transport
Hashing vs encryption vs encoding: three transformations with different goals and reversibility
PropertyHashingEncryptionEncoding
Reversible?No (one-way)Yes (with key)Yes (no key)
Requires secret key?No (uses salt + algorithm)YesNo
Primary goalIntegrity / verificationConfidentialityData representation
Typical output sizeFixed digest lengthSimilar to input + overhead~33% larger for Base64
Example usePassword storageEncrypting PAN at restEmail attachment in JSON
Security if exposed alone?Resists reversal (if strong)Safe until key leaksNone—trivial to decode

The Argon2id vs bcrypt password hashing article goes deeper on digest algorithms. For transport-layer secrecy, read symmetric vs asymmetric encryption. Those posts assume you already know which category you need.

When should you use hashing instead of encryption?

Hash when you never need the original value back. You only need to confirm a future input matches what was stored.

Passwords are the canonical case. A login form sends a plaintext password over TLS. Your server hashes it and compares the result to the stored digest. Storing an encrypted password means your app holds the decryption key. One database leak exposes every account.

File integrity checks use hashing too. You hash a deployment artefact and compare it to a published checksum. You are not hiding the file—you are proving it was not tampered with.

API request signing sometimes uses HMAC—a keyed hash. That is still a hash family member, not encryption. The shared secret proves authenticity without encrypting the entire payload.

Password Hashing FlowRegistration and login — no decryption step existsPlain passwordArgon2id / bcrypt+ unique saltDigest stored in DBLogin attemptHash againConstant-time comparesame saltOriginal password is never recoveredMatch = login success | Mismatch = reject
One-way password hashing: store digests, verify at login, never decrypt

Do not hash credit card numbers when you must charge them later. That needs encryption or a vault. Do not hash email addresses if you send mail to them daily—unless you accept lookup-by-hash trade-offs.

On legal-tech portals like Mijar Law Associates, client document metadata may be hashed for deduplication. The files themselves stay encrypted at rest. Different layers, different tools.

Algorithms you should and should not use for passwords

  • Use: Argon2id, bcrypt, or scrypt via PHP's password_hash()
  • Never use for passwords: MD5, SHA-1, plain SHA-256 without a proper KDF
  • Context matters: SHA-256 is fine for checksums, wrong for password storage

The OWASP Password Storage Cheat Sheet aligns with this guidance. Treat it as the baseline audit checklist.

How do you implement encryption and encoding correctly in PHP and Laravel?

Encryption protects data you must retrieve. Laravel ships with AES-256-CBC and AES-256-GCM via the Encrypter class. Your APP_KEY drives it. Rotate keys carefully—old ciphertext becomes unreadable unless you re-encrypt.

For authenticated encryption details, see AEAD explained. GCM adds integrity alongside confidentiality. Prefer it when your Laravel version supports it for new data.

Symmetric Encryption CyclePlaintext PIIe.g. passport no.EncryptAES-256-GCM+ APP_KEYCiphertext in DBDecryptsame APP_KEYPlaintext restoredKey leak = all ciphertext readableUse envelope encryption for high-value secrets
Encryption is reversible: protect the key as fiercely as the database itself

Password hashing in PHP

<?php
declare(strict_types=1);

$password = 'Correct-Horse-Battery-Staple-2026!';

$hash = password_hash($password, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,
    'time_cost'   => 4,
    'threads'     => 2,
]);

if (password_verify($password, $hash)) {
    echo "Login OK\n";
}

if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
    $hash = password_hash($password, PASSWORD_ARGON2ID);
}

PHP 8.3 or higher on Laravel 13 supports Argon2id natively when compiled with libsodium. Fall back to PASSWORD_BCRYPT on hosts that lack it. Check with password_get_info() after deploy—not only on your laptop.

Field-level encryption in Laravel

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;

class ClientDocument extends Model
{
    protected $casts = [
        'national_id' => 'encrypted',
    ];

    public function setNotesAttribute(?string $value): void
    {
        $this->attributes['notes'] = $value === null
            ? null
            : Crypt::encryptString($value);
    }

    public function getNotesAttribute(?string $value): ?string
    {
        return $value === null ? null : Crypt::decryptString($value);
    }
}

The encrypted cast handles serialisation automatically. For large blobs, consider database encryption at rest and in transit plus application-level field encryption. Defence in depth beats a single layer.

Encoding with Base64

Base64 turns binary into ASCII-safe text. Email systems, JWT segments, and JSON payloads use it constantly. It is not encryption. Anyone can decode it in seconds.

<?php
$binary = random_bytes(32);
$encoded = base64_encode($binary);
$decoded = base64_decode($encoded, true);

if ($decoded === false) {
    throw new RuntimeException('Invalid Base64 input');
}

Try the on-site Base64 encoder and decoder when debugging API payloads. Pair it with the password generator for test credentials—not production secrets pulled from a browser tab.

Unicode text needs UTF-8 before Base64 if you expect correct round-trips. The Nepali Unicode converter handles a different encoding problem—character sets—not cryptographic encoding. Same word, different meaning.

What are common mistakes developers make with hashing vs encryption vs encoding?

These errors appear in code review repeatedly. They are preventable once you label each field by its lifecycle requirement.

  1. Encrypting passwords. Reversible storage invites mass decryption after a key leak. Hash with Argon2id instead.
  2. Hashing data you must read later. SHA-256 of a phone number cannot be reversed for SMS delivery. Encrypt or store plaintext in a secured column.
  3. Calling Base64 "encryption". Compliance audits fail on this wording. Encoding is not a control.
  4. Using MD5 or SHA-1 for passwords. GPU cracking makes these obsolete for credentials.
  5. Hard-coding keys in source control. APP_KEY, API secrets, and KMS credentials belong in environment variables or a vault.
  6. Skipping TLS because the body is encrypted. Transport encryption and storage encryption solve different threats. Use HTTPS always.
Which Technique Do You Need?New data transformMust read later?Never read again?EncryptNeed secrecy?Yes = AES-GCMHashPasswords, HMACArgon2id / bcryptBinary in JSON? → Base64Not a security control
Decision flow for hashing vs encryption vs encoding based on whether you must recover the original value

I have seen teams on enterprise Laravel builds encrypt session IDs unnecessarily. That adds latency and key-management risk for no gain. Sessions already rely on server-side storage or signed cookies.

Another pattern: searchable encrypted fields. Standard AES makes WHERE email = ? impossible. Options include deterministic encryption with strict access controls, blind indexes (HMAC of normalised values), or dedicated search engines with tokenisation. Each trade-off deserves a design note, not an improvised helper function.

Different assets need different controls. Map each column and cookie to a row in your data-classification spreadsheet before writing code.

API bearer tokens: Store a hash of the token, display the plaintext once at creation. Same pattern as passwords. Laravel Sanctum and Passport follow this model internally.

Payment gateway references: Encrypt if you must retry charges. Better yet, store only the gateway's tokenised reference. Never touch raw card numbers if the provider offers tokenisation.

Legal document uploads: Files at rest belong on encrypted storage—S3 SSE or disk encryption. Metadata like case numbers may stay plaintext for search. Sensitive notes get field-level encryption. On portals such as Notary Nepal, access control and audit logs matter as much as the algorithm choice.

Webhook signatures: Use HMAC-SHA256 over the raw body. Compare with hash_equals() to block timing attacks. This is hashing for authenticity, not encryption of the payload.

<?php
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $signatureHeader)) {
    abort(401, 'Invalid webhook signature');
}

For multi-key setups and cloud KMS, read AWS KMS envelope encryption explained and hybrid encryption explained. They extend the symmetric model for scale.

Rate limiting and abuse prevention—covered in the API rate limiting guide—pair with these crypto choices. A perfectly hashed password still fails if login endpoints allow unlimited guesses.

Operational checklist for production

  • Document which tables use hashing, encryption, or encoding
  • Store APP_KEY outside git; back it up securely
  • Enable TLS 1.2+ on all public endpoints via your web server
  • Run php artisan key:generate only once per environment
  • Schedule password hash upgrades via password_needs_rehash() on login
  • Log decryption failures; they often signal tampering or key rotation errors

Server hardening complements application crypto. See Linux system administration for PHP-FPM, permission, and firewall work that keeps keys and data paths safe on Ubuntu hosts.

The official PHP password_hash documentation lists supported algorithms per build. Verify production binaries match CI—not only your local Docker image.

Key Takeaways

  • Hash passwords and API token digests; never encrypt credentials you do not need to read.
  • Encrypt reversible PII and secrets at rest; protect APP_KEY like a root password.
  • Treat Base64 and UTF-8 as encoding—visible to anyone, not a security boundary.
  • Use Argon2id or bcrypt via password_hash(); retire MD5 and SHA-1 for credentials.
  • Compare HMAC signatures with hash_equals() to prevent timing side channels.
  • Layer TLS in transit, encryption at rest, and access control—one technique never covers every threat.

People Also Ask

Is Base64 encryption?

No. Base64 is encoding. It converts binary to text without a secret key. Decoding takes one line of code. Use it for transport formats, not confidentiality. Real encryption requires a key and an algorithm like AES.

Can you decrypt a hash?

No. Hashes are one-way by design. Attackers guess inputs and hash them until a match appears—that is brute force, not decryption. Strong password hashes with salts make that impractical at scale.

Should you hash or encrypt email addresses?

Encrypt if you send email to the address and query by exact match. Hash only if you need verification without retrieval—uncommon for email. Many apps store emails in plaintext with strict access control and TLS. Choose based on your threat model and search requirements.

What is the best algorithm for password hashing in 2026?

Argon2id is the first choice on PHP 8.3+ with libsodium support. Bcrypt remains acceptable on older hosts. Both beat MD5, SHA-1, and unsalted SHA-256. Rehash on login when password_needs_rehash() returns true after you raise work factors.

Build security into your application from day one

Hashing vs encryption vs encoding is not academic. Pick wrong and you either lose data you needed or expose data you thought was protected. Label every sensitive field, apply the right transform, and test on production PHP builds before launch.

If you are planning a portal, API, or eCommerce platform that handles user credentials or confidential documents, review the API development service or browse the full project portfolio for shipped examples. Need help auditing an existing Laravel app? Contact us for a practical security review—not a generic checklist.

Frequently Asked Questions

All three change how bytes look on disk or on the wire, but they solve different problems. Hashing is one-way: the same input yields a fixed digest you cannot reverse, used for password verification and integrity checks. Encryption is two-way with a secret key: anyone holding the key can recover the original, used when your app must read values like national IDs or payment tokens later. Encoding such as Base64 is reversible without any key—it only changes data representation for safe transport, not confidentiality.

No. Base64 is encoding. It converts binary to ASCII-safe text without a secret key. Anyone can decode it in one line of code.

No. Hashes are one-way by design. Attackers guess inputs and hash them until a match appears—that is brute force, not decryption.

Hash when you never need the original value back and only need to confirm a future input matches what was stored. Passwords are the canonical case: your server hashes the login attempt and compares digests. File integrity checks and API request signing with HMAC follow the same logic. Do not hash credit card numbers you must charge later, or email addresses you query and mail to daily unless you accept lookup-by-hash trade-offs. On legal-tech portals, metadata may be hashed for deduplication while the actual files stay encrypted at rest.

Argon2id is the first choice on PHP 8.3+ when compiled with libsodium. Bcrypt remains acceptable on hosts that lack Argon2id support.

Encrypt if you send email to the address and need to query by exact match. Hash only if you need verification without ever retrieving the original—a uncommon pattern for email. Many production apps store emails in plaintext behind strict access control and TLS instead. The right choice depends on your threat model and whether searchable encrypted fields matter. Standard AES field encryption makes a simple WHERE email equals query impossible, so plan deterministic encryption, blind indexes, or tokenisation before you pick hashing by default.

Laravel ships with AES-256-CBC and AES-256-GCM through its Encrypter class, driven by your APP_KEY. GCM adds authenticated encryption—integrity alongside confidentiality—so prefer it for new data when your Laravel version supports it. Field-level encryption via the encrypted cast or Crypt::encryptString handles serialisation automatically for columns like national IDs or sensitive notes. Rotate APP_KEY carefully: old ciphertext becomes unreadable unless you re-encrypt. Treat the key like a root password and store it outside source control.

Use PHP's password_hash() with PASSWORD_ARGON2ID on PHP 8.3+ hosts that support libsodium, configuring memory_cost, time_cost, and threads appropriately. Fall back to PASSWORD_BCRYPT where Argon2id is unavailable. Verify logins with password_verify(), not manual string comparison. On each successful login, call password_needs_rehash() and upgrade digests when you raise work factors. Never use MD5, SHA-1, or plain SHA-256 for credentials. Verify production PHP builds with password_get_info() after deploy—not only on your local machine.

Encrypting passwords instead of hashing them invites mass decryption after a key leak. Hashing data you must read later, like phone numbers for SMS, leaves you stuck. Calling Base64 encryption fails compliance audits because encoding is not a security control. Using MD5 or SHA-1 for passwords ignores GPU cracking realities. Hard-coding APP_KEY or API secrets in git exposes every ciphertext. Skipping TLS because the request body is encrypted misses transport-layer threats. I have also seen teams encrypt session IDs unnecessarily, adding latency and key-management risk when signed cookies already suffice.

Store a hash of the token and display the plaintext only once at creation—the same pattern as passwords. Laravel Sanctum and Passport follow this model internally. Hashing means a database leak exposes digests, not usable bearer strings. Pair this with rate limiting on login and token endpoints so a perfectly hashed credential still cannot be brute-forced at unlimited speed. Document the approach in your data-classification spreadsheet alongside payment references and PII columns so every field gets the correct transform.

No. Passwords should be hashed with Argon2id or bcrypt, never encrypted. Reversible storage means your application holds a decryption key, and one database plus key leak exposes every account. Login verification works by hashing the submitted password and comparing digests with password_verify(). Encryption belongs on data you must retrieve later—payment tokens, national ID numbers, or confidential document notes—not credentials you only ever need to confirm, not read back in plaintext.

Never use MD5 or SHA-1 for passwords—GPU cracking has made them obsolete for credentials. Plain SHA-256 without a proper key derivation function is also wrong for password storage. SHA-256 remains fine for checksums and file integrity verification where reversal is not the threat model. For passwords, stick to Argon2id, bcrypt, or scrypt via password_hash(), aligned with the OWASP Password Storage Cheat Sheet. Context matters: the algorithm that proves a deployment artefact was not tampered with is not the algorithm that protects user logins.

HMAC is a keyed hash that proves authenticity without encrypting the entire payload. API request signing and webhook verification use it: you compute hash_hmac with SHA-256 over the raw body using a shared secret, then compare the result to the signature header using hash_equals() to block timing attacks. This is still the hash family, not encryption. On production Laravel apps, webhook endpoints should reject requests when the HMAC comparison fails. Use encryption when you need confidentiality; use HMAC when you need to verify the sender and detect tampering.

Map each column in a data-classification spreadsheet before writing code. Files at rest belong on encrypted storage such as S3 SSE or disk encryption. Metadata like case numbers may stay plaintext for search, while sensitive notes get field-level encryption via Laravel's encrypted cast. Access control and audit logs matter as much as algorithm choice on portals handling confidential documents. Layer TLS in transit, encryption at rest, and role-based access—one technique never covers every threat. Payment references should use gateway tokenisation rather than storing raw card numbers when the provider supports it.

Document which tables use hashing, encryption, or encoding. Store APP_KEY outside git and back it up securely—run php artisan key:generate only once per environment. Enable TLS 1.2 or higher on all public endpoints. Schedule password hash upgrades via password_needs_rehash() on login. Log decryption failures because they often signal tampering or key rotation errors. Verify production PHP binaries match CI using password_get_info(), not only your local Docker image. Server hardening on Ubuntu—PHP-FPM permissions, firewall rules, and secure key paths—complements application-level crypto and keeps APP_KEY out of reach even if the web root is compromised.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: