← Semua pembelajaran / Laravel Nol → Enterprise
Fase 4 · Autentikasi & Keamanan

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.

Sumber asli laravel.com Resmi Rangkuman ~5 menit baca

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',
    ];
}
HashEnkripsi
ArahSatu arahDua arah
UntukPassword, token APINomor identitas, kredensial pihak ketiga
Bisa dibaca lagiTidak pernahYa, dengan kunci
Bisa dicariYa, kalau hash-nya deterministikTidak
Bergantung APP_KEYTidakYa

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

  1. Simpan di penyimpanan rahasia (AWS Secrets Manager di Fase 9), bukan di berkas .env yang ikut ke image.
  2. Kunci yang berbeda untuk setiap lingkungan — lokal, staging, produksi.
  3. Jangan pernah menggantinya tanpa rencana rotasi: seluruh data terenkripsi jadi tidak terbaca dan semua sesi berakhir.
  4. Untuk rotasi sungguhan, Laravel mendukung APP_PREVIOUS_KEYS supaya 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.