Kata “enkripsi” sering dipakai untuk semua hal kriptografi, padahal di baliknya ada tiga keluarga alat yang berbeda tugas, berbeda masalah yang diselesaikan, dan berbeda konsekuensi bila salah pakai. Hash bukan enkripsi; menyimpan kata sandi dengan AES-GCM alih-alih hash lambat adalah kesalahan desain; memakai RSA untuk mengenkripsi berkas video besar adalah pemborosan. Bab ini merapikan peta besar itu: tiga keluarga kriptografi beserta kegunaan masing-masing, kosakata dasarnya (plaintext, ciphertext, kunci, IV), dan arsitektur Java Cryptography Architecture (JCA) yang menjadi pintu masuk seluruh praktik kripto kita di Java. Seluruh contoh memakai algoritme standar dari java.security dan javax.crypto — sesuai prinsip Kerckhoffs, kita tidak pernah menciptakan sendiri.

Tiga Keluarga dan Kegunaannya #

HashSimetrisAsimetris
KunciTanpa kunciSatu kunci (rahasia)Sepasang: public + private
ArahSatu arah, tak bisa dibalikDua arah (enkripsi ↔ dekripsi)Dua arah (enkripsi publik ↔ dekripsi privat; sign privat ↔ verify publik)
ContohSHA-256, SHA-3AES-GCM, ChaCha20-Poly1305RSA-OAEP, Ed25519
Kegunaan utamaSidik jari data, integritasKerahasiaan data dalam jumlah besarPertukaran kunci, tanda tangan digital
BatasTidak menyembunyikan dataKunci harus dibagikan secara aman duluLambat; hanya untuk data kecil

Tiga keluarga ini jarang berdiri sendiri. Sesi HTTPS modern memakai asimetris untuk bertukar kunci sesi, lalu simetris untuk seluruh data, dan hash di dalam MAC untuk integritas — komposisi yang memakai kekuatan masing-masing. Memahami kegunaan lebih penting daripada menghafal matematikanya: pertanyaan pertama saat merancang selalu “kebutuhan saya kerahasiaan, integritas, autentikasi pengirim, atau kombinasi?”

Kosakata: Plaintext Ciphertext Kunci dan IV #

Empat istilah ini akan muncul di seluruh chapter berikutnya, jadi mari kita tetapkan artinya. Plaintext adalah data asli yang ingin dilindungi — bisa teks maupun byte biner. Ciphertext adalah wujud terenkripsi yang tampak seperti derau acak. Kunci adalah data rahasia yang menentukan bagaimana transformasi dilakukan: enkripsi dengan kunci berbeda menghasilkan ciphertext berbeda, dan tanpa kunci yang tepat, ciphertext tidak bisa dikembalikan. IV (initialization vector) adalah angka pemersatu untuk mode operasi seperti GCM: dua pesan sama yang dienkripsi dengan kunci sama tetapi IV berbeda menghasilkan ciphertext berbeda — IV menjaga pola tidak bocor.

Satu catatan penting tentang AES-GCM sebelum kita menyentuh kode: mode ini tergolong AEAD (authenticated encryption with associated data) — sekali jalan, mode ini memberikan kerahasiaan sekaligus tag autentikasi yang mendeteksi manipulasi ciphertext. Itulah alasan AES-GCM menjadi bawaan modern dan alasan contoh kita memilih mode ini, bukan mode lawas seperti ECB yang membocorkan pola.

Praktik di JCA dan Bukti Eksekusinya #

JCA memisahkan apa dari bagaimana: kode kita meminta "SHA-256" atau "AES/GCM/NoPadding" lewat getInstance(), sementara provider — implementasi terdaftar di JVM, seperti SUN atau SunJCE — yang menyediakan mesinnya. Pemisahan ini memungkinkan JVM enterprise menyuntikkan provider tersertifikasi FIPS tanpa mengubah satu baris kode aplikasi. Program berikut menjalankan dua keluarga sekaligus pada data tetap — hash SHA-256 untuk string "Budi", lalu putaran enkripsi-dekripsi AES-GCM dengan kunci dan IV tetap demi output yang deterministik:

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HexFormat;

public class TigaKeluarga {

    public static void main(String[] args) throws Exception {
        // ============ Keluarga 1: hash (satu arah, tanpa kunci) ============
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        byte[] sidik = sha256.digest("Budi".getBytes(StandardCharsets.UTF_8));
        System.out.println("1) Hash SHA-256(\"Budi\")  : " + HexFormat.of().formatHex(sidik));

        // ============ Keluarga 2: enkripsi simetris AES-GCM ============
        // Kunci dan IV tetap HANYA untuk demo deterministik.
        // Di produksi: kunci dari secret manager, IV acak per pesan.
        byte[] kunci = "kunci-demo-aes16".getBytes(StandardCharsets.UTF_8); // 128 bit
        byte[] ivTetap = new byte[12]; // 96 bit, semua nol - jangan ditiru
        String plaintext = "Transfer Rp1.000.000 ke rekening Budi";

        SecretKeySpec spesifikasiKunci = new SecretKeySpec(kunci, "AES");
        GCMParameterSpec parameterGcm = new GCMParameterSpec(128, ivTetap);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, spesifikasiKunci, parameterGcm);
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        System.out.println();
        System.out.println("2) AES-GCM ciphertext    : "
                + Base64.getEncoder().encodeToString(ciphertext));

        cipher.init(Cipher.DECRYPT_MODE, spesifikasiKunci, parameterGcm);
        byte[] dipulihkan = cipher.doFinal(ciphertext);
        System.out.println("   Dekripsi berhasil     : "
                + new String(dipulihkan, StandardCharsets.UTF_8));

        // ============ Keluarga 2 lanjutan: integritas GCM ============
        byte[] dipindahtangan = ciphertext.clone();
        dipindahtangan[0] ^= 1; // balik satu bit pada ciphertext
        try {
            cipher.init(Cipher.DECRYPT_MODE, spesifikasiKunci, parameterGcm);
            cipher.doFinal(dipindahtangan);
            System.out.println("3) Dekripsi data ubahan  : berhasil (seharusnya tidak)");
        } catch (Exception e) {
            System.out.println();
            System.out.println("3) Satu bit ciphertext dibalik -> dekripsi DITOLAK");
            System.out.println("   Tag autentikasi GCM tidak cocok: data telah berubah");
        }
    }
}

Output yang Dihasilkan:

text
1) Hash SHA-256("Budi")  : c78aa6f52b1f8cac26e4c59f0ffcddfd53884ae8e7fb2522f2176faaa3d3fedd

2) AES-GCM ciphertext    : DMQp38LFZJ/k2LWFjY9NGYJPQRw2aAx6XbKZlDXMUtfcNo8rbud84LIOPpT4vVcQUKvqBZQ=
   Dekripsi berhasil     : Transfer Rp1.000.000 ke rekening Budi

3) Satu bit ciphertext dibalik -> dekripsi DITOLAK
   Tag autentikasi GCM tidak cocok: data telah berubah

Output membuktikan tiga hal sekaligus. Hash "Budi" selalu menghasilkan sidik 64 karakter heksadesimal yang sama — sifat deterministik inilah yang membuat hash cocok untuk membandingkan data. Enkripsi-dekripsi AES-GCM memulihkan plaintext utuh. Dan bagian ketiga adalah kejutan yang menyenangkan: satu bit ciphertext dibalik, dekripsi ditolak habis — tag autentikasi GCM menolak data yang telah diubah, tanpa kita menulis satu baris kode integritas tambahan.

Bedah baris-per-baris untuk bagian simetrisnya:

  1. Baris 17-19 ("kunci-demo-aes16" dan new byte[12]): AES-128 membutuhkan kunci tepat 16 byte, dan GCM standar memakai IV 12 byte. Kedua nilai sengaja tertulis tetap agar output pembelajaran ini identik setiap eksekusi — komentar di dalam kode menegaskan bahwa produksi wajib memakai IV acak per pesan.
  2. Baris 22-23 (new SecretKeySpec(...) dan new GCMParameterSpec(128, ...)): kunci mentah byte dibungkus SecretKeySpec agar dikenali sebagai kunci AES; parameter GCM menetapkan panjang tag autentikasi 128 bit. Kelas spec adalah cara JCA menerima parameter tanpa mengikat kode pada provider tertentu.
  3. Baris 25-27 (Cipher.getInstance("AES/GCM/NoPadding")): string transformasi dibaca “algoritme/mode/padding”. NoPadding wajib untuk GCM karena mode ini mengelola panjangnya sendiri — padding tambahan justru melanggar spesifikasi.
  4. Baris 28-29 (cipher.init(Cipher.ENCRYPT_MODE, ...)): instance Cipher diinisialisasi per operasi — enkripsi dan dekripsi memakai objek sama tapi mode beda. Setelah doFinal, hasil ciphertext berisi data terenkripsi plus 16 byte tag di ekornya; itulah alasan ciphertext Base64 lebih panjang dari plaintext.
  5. Baris 38-39 (dipindahtangan[0] ^= 1): operator XOR membalik satu bit terendah byte pertama. Perubahan sekecil itu pun mengubah tag yang dihitung ulang saat dekripsi, sehingga GCM melempar AEADBadTagException — tertangkap di catch dan dilaporkan sebagai penolakan, bukan sebagai kegagalan sistem.

Keluarga ketiga — asimetris — tidak muncul sebagai contoh runnable di bab ini karena perannya berbeda: RSA dan kawan-kawannya dipakai untuk bertukar kunci sesi dan menandatangani data, bukan mengenkripsi muatan besar. Kita akan bertemu kembali dengan pasangan public-private key pada bab TLS dan Certificate Validation serta Message Digests dan Signatures. Sebelum itu, satu bahan baku kriptografi yang sering diremehkan harus kita kuasai lebih dulu: keacakan. Bab berikutnya membahas SecureRandom — dan mengapa Random ber-seed yang begitu nyaman untuk pengujian justru berbahaya untuk token keamanan.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

TigaKeluarga.java

Unduh berkas
java
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.HexFormat;

public class TigaKeluarga {

    public static void main(String[] args) throws Exception {
        // ============ Keluarga 1: hash (satu arah, tanpa kunci) ============
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        byte[] sidik = sha256.digest("Budi".getBytes(StandardCharsets.UTF_8));
        System.out.println("1) Hash SHA-256(\"Budi\")  : " + HexFormat.of().formatHex(sidik));

        // ============ Keluarga 2: enkripsi simetris AES-GCM ============
        // Kunci dan IV tetap HANYA untuk demo deterministik.
        // Di produksi: kunci dari secret manager, IV acak per pesan.
        byte[] kunci = "kunci-demo-aes16".getBytes(StandardCharsets.UTF_8); // 128 bit
        byte[] ivTetap = new byte[12]; // 96 bit, semua nol - jangan ditiru
        String plaintext = "Transfer Rp1.000.000 ke rekening Budi";

        SecretKeySpec spesifikasiKunci = new SecretKeySpec(kunci, "AES");
        GCMParameterSpec parameterGcm = new GCMParameterSpec(128, ivTetap);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, spesifikasiKunci, parameterGcm);
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        System.out.println();
        System.out.println("2) AES-GCM ciphertext    : "
                + Base64.getEncoder().encodeToString(ciphertext));

        cipher.init(Cipher.DECRYPT_MODE, spesifikasiKunci, parameterGcm);
        byte[] dipulihkan = cipher.doFinal(ciphertext);
        System.out.println("   Dekripsi berhasil     : "
                + new String(dipulihkan, StandardCharsets.UTF_8));

        // ============ Keluarga 2 lanjutan: integritas GCM ============
        byte[] dipindahtangan = ciphertext.clone();
        dipindahtangan[0] ^= 1; // balik satu bit pada ciphertext
        try {
            cipher.init(Cipher.DECRYPT_MODE, spesifikasiKunci, parameterGcm);
            cipher.doFinal(dipindahtangan);
            System.out.println("3) Dekripsi data ubahan  : berhasil (seharusnya tidak)");
        } catch (Exception e) {
            System.out.println();
            System.out.println("3) Satu bit ciphertext dibalik -> dekripsi DITOLAK");
            System.out.println("   Tag autentikasi GCM tidak cocok: data telah berubah");
        }
    }
}