Hashing, enkripsi, dan encoding sering dianggap sama karena ketiganya mengubah data menjadi teks yang terlihat acak. Padahal tujuannya berbeda total: hashing bersifat satu arah dan dipakai untuk memverifikasi (misalnya password), enkripsi bisa dibalik hanya oleh pemilik kunci dan dipakai untuk menjaga kerahasiaan, sedangkan encoding seperti Base64 bisa dibalik oleh siapa saja dan sama sekali bukan pengamanan. Salah memilih salah satunya adalah sumber banyak kebocoran data.
Artikel ini fokus pada perbandingan konsep: apa bedanya, kapan memakai yang mana, kenapa MD5 dan SHA-1 tidak boleh dipakai untuk password, apa fungsi salt, dan bagaimana semuanya diterapkan di PHP serta Laravel.
Ringkasan cepat: tabel perbandingan
| Aspek | Hashing | Enkripsi | Encoding |
|---|---|---|---|
| Tujuan | Verifikasi integritas / password | Kerahasiaan data | Representasi data agar aman dikirim |
| Bisa dibalik? | Tidak (satu arah) | Ya, dengan kunci yang benar | Ya, oleh siapa pun |
| Butuh kunci? | Tidak (password hashing memakai salt) | Ya | Tidak |
| Panjang output | Tetap, apa pun inputnya | Mengikuti panjang input | Mengikuti panjang input (Base64 ยฑ33% lebih besar) |
| Contoh | SHA-256, bcrypt, Argon2id | AES-256-GCM, RSA, ChaCha20 | Base64, URL encoding, hex |
| Dipakai untuk | Menyimpan password, checksum file | Data sensitif yang perlu dibaca lagi | Lampiran email, data URI, JWT |
Encoding: mengubah format, bukan mengamankan
Encoding adalah cara merepresentasikan data dalam format lain supaya bisa dipindahkan melalui media tertentu tanpa rusak. Base64, misalnya, mengubah data biner menjadi karakter teks aman (AโZ, aโz, 0โ9, +, /) agar bisa dimasukkan ke email atau JSON. URL encoding mengubah spasi menjadi %20 agar URL tetap valid.
Kunci pentingnya: aturan encoding bersifat publik dan tidak memakai rahasia apa pun. Siapa saja bisa men-decode-nya dalam hitungan detik. Anda bisa mencobanya sendiri di Base64 Encoder/Decoder.
<?php
$encoded = base64_encode('rahasia123');
echo $encoded; // cmFoYXNpYTEyMw==
echo base64_decode($encoded); // rahasia123
Kesalahan klasik yang masih sering ditemui: menyimpan password di database dalam bentuk Base64 dan menganggapnya "sudah terenkripsi". Ini sama saja dengan menyimpan teks asli. Hal yang sama berlaku untuk payload JWT; bagian header dan payload JWT hanya di-encode Base64URL, sehingga isinya bisa dibaca siapa pun lewat JWT Decoder. Yang melindungi JWT adalah tanda tangannya, bukan encoding-nya.
Enkripsi: rahasia yang bisa dibuka kembali
Enkripsi mengubah data asli (plaintext) menjadi ciphertext memakai algoritma dan kunci. Tanpa kunci yang benar, ciphertext tidak bisa dibaca. Dengan kunci, data bisa dikembalikan utuh. Inilah perbedaan mendasar dengan hashing: enkripsi dirancang untuk bisa dibalik.
Ada dua keluarga besar:
- Simetris โ satu kunci untuk enkripsi dan dekripsi. Contohnya AES dan ChaCha20. Cepat, cocok untuk data dalam jumlah besar.
- Asimetris โ pasangan kunci publik dan privat, contohnya RSA dan kurva eliptik. Dipakai pada HTTPS untuk pertukaran kunci dan tanda tangan digital, juga pada DKIM email.
Gunakan enkripsi ketika aplikasi Anda perlu membaca data aslinya lagi: nomor KTP, token API pihak ketiga, kredensial SMTP milik pelanggan, atau catatan medis. Password pengguna tidak termasuk kategori ini, karena aplikasi tidak pernah perlu tahu password aslinya; aplikasi cukup memastikan password yang diketik cocok.
Di Laravel, enkripsi simetris tersedia lewat facade Crypt. Laravel memakai AES-256 (default AES-256-CBC) dengan MAC untuk mendeteksi manipulasi, dan kuncinya adalah APP_KEY di file .env.
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
$cipher = Crypt::encryptString('1234-5678-9012');
try {
$plain = Crypt::decryptString($cipher);
} catch (DecryptException $e) {
// data rusak atau APP_KEY berbeda
}
Laravel juga punya cast encrypted pada model, sehingga kolom otomatis dienkripsi saat disimpan dan didekripsi saat dibaca:
protected function casts(): array
{
return [
'npwp' => 'encrypted',
];
}
Konsekuensinya, APP_KEY menjadi sangat berharga. Jika kunci hilang, data terenkripsi tidak bisa dibuka lagi. Jika kunci bocor, enkripsi tidak ada artinya. Jangan pernah meng-commit file .env ke repository, dan jangan menjalankan php artisan key:generate di server produksi yang sudah berisi data terenkripsi.
Hashing: sidik jari satu arah
Fungsi hash menerima input sepanjang apa pun dan menghasilkan output dengan panjang tetap. SHA-256 selalu menghasilkan 256 bit (64 karakter heksadesimal), baik inputnya satu huruf maupun file 2 GB. Sifat penting fungsi hash kriptografis:
- Deterministik: input sama selalu menghasilkan hash yang sama.
- Satu arah: tidak ada cara matematis untuk mendapatkan input dari hash-nya.
- Efek longsor: perubahan satu karakter mengubah hash secara total.
- Tahan tabrakan: sangat sulit menemukan dua input berbeda dengan hash sama.
<?php
echo hash('sha256', 'halo');
// 2c3b6b0d... (64 karakter hex)
echo hash('sha256', 'Halo');
// hasil yang sama sekali berbeda
Anda bisa membandingkan hasil MD5, SHA-1, SHA-256, dan SHA-512 untuk teks yang sama di Hash Generator.
Hash cepat vs hash password
Di sinilah banyak developer keliru. Fungsi hash dibagi menjadi dua kelompok dengan tujuan berlawanan:
- Hash cepat (MD5, SHA-1, SHA-256, SHA-512): dirancang secepat mungkin. Cocok untuk checksum file, deduplikasi, ETag, atau HMAC.
- Hash password (bcrypt, scrypt, Argon2id): sengaja dibuat lambat dan bisa diatur biayanya (cost/work factor). Argon2id dan scrypt juga memakan memori agar sulit diparalelkan di GPU.
Kecepatan adalah keunggulan untuk checksum, tetapi bencana untuk password. Jika database bocor, penyerang akan mencoba jutaan tebakan password per detik terhadap hash yang dicuri (serangan offline). Kartu grafis modern mampu menghitung miliaran hash MD5 per detik, sehingga password pendek atau umum bisa ditemukan dengan cepat. Dengan bcrypt cost 12, setiap tebakan memakan waktu ratusan milidetik pada CPU biasa, membuat serangan yang sama jauh lebih mahal.
Kenapa MD5 dan SHA-1 buruk untuk password
Ada tiga alasan yang saling menguatkan:
- Terlalu cepat. Seperti dijelaskan di atas, kecepatan memudahkan brute force dan serangan kamus.
- Tanpa salt bawaan.
md5('123456')selalu menghasilkan hash yang sama di seluruh dunia. Penyerang cukup mencocokkan dengan tabel hash yang sudah dihitung sebelumnya (rainbow table) atau sekadar mencarinya di mesin pencari. - Sudah rusak secara kriptografis. Tabrakan MD5 bisa dibuat secara praktis sejak pertengahan 2000-an, dan tabrakan SHA-1 berhasil didemonstrasikan secara publik pada 2017 (SHAttered). Untuk password, masalah utamanya tetap kecepatan, tetapi status "rusak" ini juga berarti keduanya tidak layak untuk tanda tangan digital atau sertifikat.
Perlu dicatat: SHA-256 memang belum rusak, tetapi tetap bukan pilihan yang tepat untuk password karena sama cepatnya. Menambahkan salt ke SHA-256 menutup masalah rainbow table, namun tidak menutup masalah kecepatan.
Salt dan pepper
Salt adalah nilai acak unik per pengguna yang digabungkan dengan password sebelum di-hash. Dengan salt, dua pengguna yang sama-sama memakai password rahasia123 akan memiliki hash berbeda, dan rainbow table menjadi tidak berguna karena penyerang harus menyerang setiap hash satu per satu.
Salt tidak perlu dirahasiakan. Pada bcrypt dan Argon2, salt disimpan langsung di dalam string hash itu sendiri, bersama algoritma dan parameter biayanya:
Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.
$2y$12$Wq5mC0n0e1Jd8y7mWqJ6UuQ7v3tq0m2zq1y5XkR8yQ9bH3zP4aB6C
| | | |
| | salt (22 karakter) hash
| cost = 12
algoritma = bcrypt
Pepper adalah rahasia tambahan yang sama untuk semua pengguna dan disimpan di luar database (misalnya di environment variable). Pepper bersifat opsional; yang wajib adalah algoritma hash password yang benar dengan salt unik. Kabar baiknya, Anda tidak perlu membuat salt sendiri, karena fungsi bawaan PHP sudah menanganinya.
Praktik yang benar di PHP: password_hash dan password_verify
PHP menyediakan API password yang aman sejak versi 5.5. Gunakan ini, jangan meracik sendiri dengan md5() atau hash().
<?php
// Saat registrasi
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// simpan $hash ke kolom VARCHAR(255)
// Saat login
if (password_verify($_POST['password'], $user['password'])) {
// cek apakah hash perlu diperbarui (misal cost naik)
if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
$newHash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// update kolom password dengan $newHash
}
// login sukses
}
Beberapa hal yang perlu diperhatikan:
PASSWORD_DEFAULTsaat ini adalah bcrypt. Konstanta ini bisa berubah di versi PHP mendatang, karena itu kolom database sebaiknyaVARCHAR(255), bukanCHAR(60).- Untuk Argon2id, gunakan
PASSWORD_ARGON2ID. Opsi ini hanya tersedia jika PHP dikompilasi dengan dukungan Argon2; cek dengandefined('PASSWORD_ARGON2ID'). - bcrypt hanya memproses 72 byte pertama dari password. Untuk password yang sangat panjang, sisanya diabaikan. Ini jarang jadi masalah praktis, tetapi perlu diketahui.
password_verify()sudah memakai perbandingan yang aman terhadap timing attack, jadi jangan membandingkan hash secara manual dengan==.
Di Laravel: facade Hash
Laravel membungkus API tersebut dalam facade Hash. Driver default adalah bcrypt, dan konfigurasinya ada di config/hashing.php serta variabel BCRYPT_ROUNDS di .env.
use Illuminate\Support\Facades\Hash;
$user->password = Hash::make($request->password);
if (Hash::check($request->password, $user->password)) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($request->password);
$user->save();
}
}
Di Laravel 10 ke atas, model User bawaan memakai cast 'password' => 'hashed', sehingga nilai yang di-assign otomatis di-hash. Jangan memanggil Hash::make() lagi pada model dengan cast ini, karena Laravel akan mendeteksi nilai yang sudah di-hash, tetapi kode Anda menjadi membingungkan. Pilih salah satu cara secara konsisten.
Untuk mengganti driver ke Argon2id, ubah 'driver' => 'argon2id' di config/hashing.php (publikasikan dulu filenya bila belum ada). Perhatikan bahwa Laravel 11 secara default memverifikasi bahwa hash memakai algoritma yang sesuai driver (opsi verify, dikendalikan HASH_VERIFY). Selama masa transisi, hash bcrypt lama baru bisa dicek oleh driver Argon2id jika verifikasi ini dimatikan; setelah itu hash diperbarui bertahap saat pengguna login.
Kapan memakai yang mana?
| Kebutuhan | Pilihan |
|---|---|
| Menyimpan password pengguna | bcrypt atau Argon2id (password_hash, Hash::make) |
| Menyimpan token API pelanggan yang harus dipakai lagi | Enkripsi (Crypt, cast encrypted) |
| Menyimpan token reset password / API token milik aplikasi sendiri | Hash cepat seperti SHA-256 sudah cukup karena token acak dan panjang |
| Memverifikasi file hasil unduhan | SHA-256 checksum |
| Menandatangani webhook | HMAC-SHA256 (hash_hmac) |
| Mengirim data biner lewat JSON atau email | Base64 encoding |
Poin di baris ketiga sering membingungkan. Token acak 40 karakter memiliki entropi sangat tinggi, sehingga brute force tidak realistis meskipun hash-nya cepat. Password buatan manusia berbeda: entropinya rendah dan mudah ditebak, karena itu wajib memakai hash yang lambat. Laravel Sanctum, misalnya, menyimpan token dalam bentuk SHA-256.
Kesalahan umum
- Menyebut Base64 sebagai "enkripsi" dan memakainya untuk menyembunyikan data sensitif.
- Mengenkripsi password (bisa dibalik) alih-alih meng-hash-nya. Jika kunci bocor, semua password terbuka.
- Memakai
md5($password)atausha1($password . 'garam')dengan salt statis. - Membuat kolom password
VARCHAR(32)atauCHAR(60)sehingga hash terpotong atau tidak muat ketika algoritma diganti. - Membandingkan hash dengan
==alih-alihpassword_verify()/hash_equals(). - Menurunkan cost bcrypt di produksi demi kecepatan login. Turunkan hanya di environment testing.
Checklist
- Password:
password_hash()/Hash::make(), kolomVARCHAR(255). - Rehash otomatis saat login bila parameter berubah.
- Data sensitif yang perlu dibaca lagi: enkripsi dengan kunci yang dijaga.
APP_KEYdicadangkan dengan aman dan tidak pernah di-commit.- Base64 hanya untuk transport data, bukan untuk keamanan.
- MD5/SHA-1 hanya untuk keperluan non-keamanan seperti cache key.
FAQ
Apakah hash MD5 bisa "di-decrypt"?
Secara teknis tidak, karena hash tidak dienkripsi. Situs "MD5 decrypt" sebenarnya mencocokkan hash Anda dengan database besar berisi pasangan teks dan hash yang sudah dihitung sebelumnya. Jika password Anda umum, kemungkinan besar sudah ada di database tersebut.
Lebih baik bcrypt atau Argon2id?
Keduanya layak. Argon2id adalah rekomendasi utama OWASP karena juga memakan memori sehingga lebih tahan terhadap serangan GPU. bcrypt tetap aman dengan cost yang memadai dan didukung di semua instalasi PHP. Untuk kebanyakan aplikasi Laravel, default bcrypt sudah baik.
Apakah HTTPS menggantikan kebutuhan hashing password?
Tidak. HTTPS mengenkripsi data selama perjalanan dari browser ke server. Hashing melindungi password yang tersimpan di database jika suatu saat database bocor. Keduanya melindungi tahap yang berbeda dan sama-sama wajib.
Bagaimana memigrasikan password lama yang masih MD5?
Cara paling umum: saat pengguna login, cek apakah hash masih format lama; jika cocok dengan md5(), langsung hash ulang password tersebut dengan password_hash() dan simpan. Alternatif yang lebih cepat adalah membungkus hash lama, yaitu password_hash($md5Lama, ...), lalu memverifikasi dengan password_verify(md5($input), $hash) sampai semua pengguna dimigrasikan.
Apakah encoding punya manfaat keamanan sama sekali?
Encoding membantu mencegah kerusakan data dan, dalam konteks seperti HTML escaping, mencegah input ditafsirkan sebagai kode. Namun encoding tidak pernah menyembunyikan isi data dari pihak yang tidak berwenang.