Hashing & enkripsi
Hash itu satu arah — dipakai untuk sesuatu yang cukup dicocokkan. Enkripsi dua arah — dipakai untuk sesuatu yang harus bisa dibaca lagi. Menukar keduanya adalah kesalahan desain yang mahal diperbaiki.
Intisari
- Password selalu di-hash, tidak pernah dienkripsi. Bcrypt (default) atau Argon2id.
- Cast
'hashed'pada model membuat password otomatis di-hash saat disimpan. - Enkripsi untuk data yang perlu dibaca kembali — nomor identitas, token pihak ketiga.
- Enkripsi memakai
APP_KEY. Kunci hilang berarti datanya hilang selamanya. - Kolom terenkripsi tidak bisa dicari — kalau perlu dicari, simpan kolom hash pendamping.
Password
// Cara yang benar: cast, sekali tulis di model
protected function casts(): array
{
return ['password' => 'hashed'];
}
$user->password = $request->password; // otomatis di-hash saat disimpan
// Manual, kalau memang perlu
$hash = Hash::make($request->password);
if (Hash::check($request->password, $user->password)) { /* cocok */ }
// Naikkan biaya hash tanpa memaksa semua orang ganti password
if (Hash::needsRehash($user->password)) {
$user->update(['password' => $request->password]);
}
Jangan pernah md5() atau sha1() untuk password. Keduanya dirancang untuk
cepat, dan kecepatan adalah musuh di sini: GPU modern bisa menghitung miliaran hash per detik. Bcrypt
dan Argon2 sengaja dibuat lambat dan bisa diatur biayanya. Ini bukan preferensi, ini persyaratan.
Kebijakan password
// AppServiceProvider::boot()
Password::defaults(function () {
return app()->isProduction()
? Password::min(12)->letters()->numbers()->uncompromised()
: Password::min(8);
});
uncompromised() memeriksa password ke basis data kebocoran publik memakai k-anonymity —
hanya lima karakter pertama hash yang dikirim, jadi password pengguna tidak pernah keluar dari servermu.
Ini satu method yang mencegah pemakaian password yang sudah bocor di tempat lain.
Enkripsi
$rahasia = Crypt::encryptString($nomorKtp);
$asli = Crypt::decryptString($rahasia);
// Lebih rapi: cast, supaya seluruh aplikasi tidak perlu tahu
protected function casts(): array
{
return [
'nomor_ktp' => 'encrypted',
'metadata' => 'encrypted:array',
];
}
| Hash | Enkripsi | |
|---|---|---|
| Arah | Satu arah | Dua arah |
| Untuk | Password, token API | Nomor identitas, kredensial pihak ketiga |
| Bisa dibaca lagi | Tidak pernah | Ya, dengan kunci |
| Bisa dicari | Ya, kalau hash-nya deterministik | Tidak |
Bergantung APP_KEY | Tidak | Ya |
Data terenkripsi yang tetap perlu dicari
// Dua kolom: satu untuk ditampilkan, satu untuk dicocokkan
Schema::table('pelanggan', function (Blueprint $t) {
$t->text('nomor_ktp'); // encrypted
$t->string('nomor_ktp_hash', 64)->index();
});
protected static function booted(): void
{
static::saving(function (Pelanggan $p) {
// Hash deterministik dengan kunci — bukan hash password, jadi cepat itu wajar
$p->nomor_ktp_hash = hash_hmac('sha256', $p->nomor_ktp, config('app.key'));
});
}
Pelanggan::where('nomor_ktp_hash', hash_hmac('sha256', $cari, config('app.key')))->first();
Mengelola APP_KEY
- Simpan di penyimpanan rahasia (AWS Secrets Manager di Fase 9), bukan di berkas
.envyang ikut ke image. - Kunci yang berbeda untuk setiap lingkungan — lokal, staging, produksi.
- Jangan pernah menggantinya tanpa rencana rotasi: seluruh data terenkripsi jadi tidak terbaca dan semua sesi berakhir.
- Untuk rotasi sungguhan, Laravel mendukung
APP_PREVIOUS_KEYSsupaya data lama masih bisa dibaca sementara data baru memakai kunci baru.
APP_KEY=base64:kunci-baru
APP_PREVIOUS_KEYS=base64:kunci-lama
Rencana rotasi itu tiga langkah, bukan satu. Pasang kunci baru sambil mempertahankan yang lama di
APP_PREVIOUS_KEYS; jalankan job yang membaca lalu menyimpan ulang setiap baris terenkripsi supaya
ter-enkripsi dengan kunci baru; setelah semuanya beres, hapus kunci lama. Melompati langkah kedua berarti data
lama akan mati begitu kunci lama dicabut.
Latihan: tambahkan cast 'hashed' pada password dan 'encrypted' pada satu
kolom baru. Simpan sebuah nilai, lihat isinya langsung di database, dan pastikan tidak terbaca. Lalu coba
where('kolom_terenkripsi', $nilai) — dan pahami kenapa ia tidak akan pernah menemukan apa pun.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.