Keamanan bukan tahap yang dikerjakan sekali di ujung proyek, melainkan rangkaian keputusan kecil setiap hari: memilih algoritma yang tepat, memvalidasi masukan, dan memantau dependensi. Ekosistem Java memiliki sumber rujukan yang matang — Oracle merawat panduan keamanan resmi tiap rilis JDK, OWASP mengkurasi pengetahuan keamanan aplikasi lintas bahasa. Chapter ini memetakan sumber-sumber tersebut, lalu memperagakan cara memakainya lewat satu pola yang paling sering ditanyakan: menyimpan dan memverifikasi kata sandi dengan aman.
Peta Sumber Keamanan Resmi #
Tabel berikut merangkum sumber yang paling sering kita buka saat bekerja dengan Java:
| Sumber | Alamat | Kapan dibuka |
|---|---|---|
| Security Developer’s Guide (JDK 25) | docs.oracle.com/en/java/javase/25/security/ | arsitektur kriptografi dan provider |
| Secure Coding Guidelines | oracle.com/java/technologies/javase/seccodeguide.html | aturan menulis kode tahan salah |
| Standard Algorithm Names | docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html | daftar nama algoritma sah |
| dev.java: Keamanan | dev.java/learn/security/ | pengantar bertahap kriptografi |
| OWASP Top 10 | owasp.org/www-project-top-ten/ | taksonomi risiko aplikasi web |
| OWASP Dependency-Check | owasp.org/www-project-dependency-check/ | pindai kerentanan dependensi |
| OWASP Cheat Sheet Series | cheatsheetseries.owasp.org/ | resep teknis, termasuk penyimpanan sandi |
Kenali dua pola alamatnya. Dokumen modern berada di bawah docs.oracle.com/en/java/javase/25/ dan berganti nomor versi tiap rilis; dokumen lawas era Java 8 tetap dapat diakses untuk sistem lama. Untuk pertanyaan “algoritma apa saja yang diakui JDK ini”, jawaban otoritatifnya adalah dokumen Standard Algorithm Names.
Membaca Secure Coding Guidelines #
Dokumen Secure Coding Guidelines for Java SE paling efektif dibaca seperti daftar periksa audit: tiap aturan menyajikan contoh kode yang rentan diikuti versi yang diperbaiki. Cara memakainya sederhana — ambil satu aturan, cari pola pelanggarannya di codebase Anda, perbaiki, lalu lanjut ke aturan berikutnya. Mulailah dari aturan validasi masukan dan penanganan exception yang menulis data sensitif.
Pola Aman Penyimpanan Kata Sandi #
Mari terapkan satu resep dari sumber di atas. Masalahnya klasik: aplikasi perlu memverifikasi kata sandi, tetapi menyimpan sandi sebagai teks polos — atau sebagai hash cepat tanpa garam — membuat seluruh akun jatuh ketika basis data bocor. Mekanismenya: PBKDF2 mengulang HMAC-SHA256 ratusan ribu kali sehingga tiap percobaan sandi menjadi mahal, garam membuat dua pengguna bersandi sama tetap menghasilkan hash berbeda, dan perbandingan hasil dilakukan konstan-waktu.
Program berikut memperagakan pola lengkapnya dengan JDK murni:
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.HexFormat;
public class ContohSandiKata {
private static final int ITERASI = 600_000;
private static byte[] hash(String sandi, byte[] garam)
throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spesifikasi = new PBEKeySpec(
sandi.toCharArray(), garam, ITERASI, 256);
SecretKeyFactory pabrik =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return pabrik.generateSecret(spesifikasi).getEncoded();
}
private static boolean verifikasi(String sandi, byte[] garam, byte[] hashTersimpan)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] hashBaru = hash(sandi, garam);
// Bandingkan konstan-waktu agar durasi verifikasi tidak membocorkan
// berapa byte awal yang sudah cocok.
return MessageDigest.isEqual(hashBaru, hashTersimpan);
}
public static void main(String[] args)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// Garam contoh 16 byte. Pada sistem nyata garam acak dibuat sekali
// saat pendaftaran lalu disimpan berdampingan dengan hash.
byte[] garam = HexFormat.of().parseHex("1f2e3d4c5b6a79880112233445566778");
byte[] hashTersimpan = hash("rahasiaBudi", garam);
System.out.println("Garam (hex) : " + HexFormat.of().formatHex(garam));
System.out.println("Iterasi : " + ITERASI);
System.out.println("Hash (hex) : " + HexFormat.of().formatHex(hashTersimpan));
System.out.println("Panjang hash : " + hashTersimpan.length + " byte");
System.out.println("Sandi cocok : " + verifikasi("rahasiaBudi", garam, hashTersimpan));
System.out.println("Sandi salah : " + verifikasi("rahasiaSiti", garam, hashTersimpan));
}
}Output yang Dihasilkan:
Garam (hex) : 1f2e3d4c5b6a79880112233445566778
Iterasi : 600000
Hash (hex) : a0e6fa8f385d725a3de32587d7a3e727bcaae1ef046bb79c87bbac9d2ece8b7b
Panjang hash : 32 byte
Sandi cocok : true
Sandi salah : falseMari kita telaah baris-baris kuncinya:
- Baris 14-15 (
new PBEKeySpec(...)): membungkus sandi sebagaichar[], garam, 600.000 iterasi, dan panjang kunci 256 bit — angka iterasi mengikuti rekomendasi Cheat Sheet penyimpanan sandi OWASP. - Baris 16-17 (
SecretKeyFactory.getInstance(...)): meminta provider keamanan JDK untuk algoritma tersebut. Salah ketik nama menghasilkanNoSuchAlgorithmException, bukan hasil salah diam-diam. - Baris 23 dan 26 (
MessageDigest.isEqual): verifikasi menghitung ulang hash dengan garam yang sama lalu membandingkan kedua hash dalam waktu konstan.Arrays.equalsyang berhenti di byte berbeda pertama membocorkan panjang prefiks yang cocok lewat durasi eksekusi. - Baris 33-35 (
garamdanhashTersimpan): mensimulasikan basis data sederhana — garam dan hash disimpan, kata sandi aslinya tidak pernah.
OWASP untuk Ekosistem Java #
Sumber Oracle menjelaskan platform dari dalam; OWASP menjelaskan risiko yang datang dari luar. OWASP Top 10 memberi taksonomi risiko yang sama-sama dipakai tim keamanan dan pengembang, sehingga laporan audit dan daftar tugas tim pengembang memakai bahasa yang sama. Untuk sisi rantai pasok, Dependency-Check membandingkan daftar dependensi Maven/Gradle dengan basis data kerentanan publik tiap kali build berjalan. Konfigurasi berikut adalah konsep tampilan untuk proyek Maven — jalankan pada proyek Anda sendiri:
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>ISI_VERSI_TERBARU_DARI_HALAMAN_PROYEK</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
</configuration>
</plugin>Dengan peta ini, kita tahu dokumen mana yang dibuka lebih dulu saat tim membahas keamanan. Berikutnya kita beralih dari aman ke cepat: memetakan sumber performa JVM, dari dokumen garbage collector hingga kerangka benchmark JMH.
Referensi #
- Oracle: Security Developer’s Guide (JDK 25)
- Oracle: Secure Coding Guidelines for Java SE
- OWASP Dependency-Check
- OWASP Top 10
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ContohSandiKata.java
Unduh berkasimport javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.HexFormat;
public class ContohSandiKata {
private static final int ITERASI = 600_000;
private static byte[] hash(String sandi, byte[] garam)
throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spesifikasi = new PBEKeySpec(
sandi.toCharArray(), garam, ITERASI, 256);
SecretKeyFactory pabrik =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return pabrik.generateSecret(spesifikasi).getEncoded();
}
private static boolean verifikasi(String sandi, byte[] garam, byte[] hashTersimpan)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] hashBaru = hash(sandi, garam);
// Bandingkan konstan-waktu agar durasi verifikasi tidak membocorkan
// berapa byte awal yang sudah cocok.
return MessageDigest.isEqual(hashBaru, hashTersimpan);
}
public static void main(String[] args)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// Garam contoh 16 byte. Pada sistem nyata garam acak dibuat sekali
// saat pendaftaran lalu disimpan berdampingan dengan hash.
byte[] garam = HexFormat.of().parseHex("1f2e3d4c5b6a79880112233445566778");
byte[] hashTersimpan = hash("rahasiaBudi", garam);
System.out.println("Garam (hex) : " + HexFormat.of().formatHex(garam));
System.out.println("Iterasi : " + ITERASI);
System.out.println("Hash (hex) : " + HexFormat.of().formatHex(hashTersimpan));
System.out.println("Panjang hash : " + hashTersimpan.length + " byte");
System.out.println("Sandi cocok : " + verifikasi("rahasiaBudi", garam, hashTersimpan));
System.out.println("Sandi salah : " + verifikasi("rahasiaSiti", garam, hashTersimpan));
}
}