Kriptografi adalah bidang yang tampak sederhana di permukaan—enkripsi, dekripsi, hash—tetapi penuh dengan jebakan halus yang hanya terlihat setelah ditinjau oleh ahli. Aturan paling dasar dalam industri keamanan: jangan pernah menulis algoritma kriptografi sendiri. Bahkan implementasi AES yang tampak benar dapat bocor informasi melalui side channel timing attack, cache timing, atau padding oracle. Pada chapter ini kita mendemonstrasikan konsep dasar dengan XOR cipher—sebuah algoritma mainan yang hanya untuk pembelajaran—dan kemudian memetakan library produksi yang seharusnya dipakai: OpenSSL, libsodium, mbedTLS.

Aturan Dasar: Pakai Library yang Sudah Diaudit #

Tiga pustaka kriptografi standar yang dipakai industri:

  • OpenSSL (libcrypto): Pustaka paling tersebar luas. Mendukung TLS, AES, RSA, ECDSA, SHA-2/3, HKDF, PBKDF2. Sering terlalu kompleks untuk pemula—API memiliki banyak footgun.
  • libsodium: Pustaka modern dengan API sederhana yang sulit dipakai salah. Direkomendasikan untuk aplikasi baru. Menyediakan crypto_secretbox (simetri), crypto_box (asimetri), crypto_pwhash (Argon2), randombytes_buf.
  • mbedTLS: Ringan dan cocok untuk embedded. Memiliki API modular: cukup link modul yang dipakai.

XOR Cipher: Demo Konseptual (BUKAN untuk Produksi) #

XOR cipher adalah operasi enkripsi paling sederhana: setiap byte plaintext di-XOR dengan byte kunci yang berulang. Karena A ^ K ^ K == A, operasi yang sama dapat dipakai untuk enkripsi dan dekripsi. Demonstrasinya membantu kita memahami konsep abstrak tetapi tidak aman untuk produksi.

c
/* cryptography_basics.c */
/* Demonstrasi konseptual XOR cipher — JANGAN dipakai untuk produksi.
   Untuk produksi: gunakan OpenSSL, libsodium, atau mbedTLS. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* XOR sederhana: stream plaintext XOR dengan kunci berulang. */
static void xor_cipher(const char *input, const char *key,
                       char *out, size_t out_size) {
    size_t in_len = strlen(input);
    size_t key_len = strlen(key);
    if (key_len == 0) return;

    size_t copy = in_len < out_size - 1 ? in_len : out_size - 1;
    for (size_t i = 0; i < copy; i++) {
        out[i] = input[i] ^ key[i % key_len];
    }
    out[copy] = '\0';
}

/* Cetak byte dalam bentuk heksadesimal supaya tidak terlihat aneh. */
static void print_hex(const char *label, const char *data, size_t len) {
    printf("%s", label);
    for (size_t i = 0; i < len; i++) {
        printf("%02x", (unsigned char)data[i]);
    }
    printf("\n");
}

int main(void) {
    const char *plaintext = "Halo Budi, ini pesan rahasia.";
    const char *key = "KUNCIRAHASIA"; /* kunci pendek — lemah! */

    size_t len = strlen(plaintext);
    char *ciphertext = malloc(len + 1);
    char *decrypted  = malloc(len + 1);
    if (!ciphertext || !decrypted) {
        fprintf(stderr, "malloc gagal\n");
        free(ciphertext); free(decrypted);
        return 1;
    }

    printf("=== Enkripsi XOR (DEMO saja, bukan produksi) ===\n");
    printf("Plaintext : '%s'\n", plaintext);
    printf("Kunci     : '%s'\n", key);

    xor_cipher(plaintext, key, ciphertext, len + 1);
    print_hex("Ciphertext: ", ciphertext, len);

    /* Dekripsi: XOR dengan kunci yang sama membatalkan enkripsi. */
    xor_cipher(ciphertext, key, decrypted, len + 1);
    printf("Dekripsi  : '%s'\n", decrypted);

    printf("\n=== Verifikasi: plaintext == dekripsi? ===\n");
    if (strcmp(plaintext, decrypted) == 0) {
        printf("YA — round-trip XOR sukses (sifat simetri XOR).\n");
    } else {
        printf("TIDAK — terjadi kesalahan.\n");
    }

    printf("\n=== Kelemahan XOR cipher ini ===\n");
    printf("- Kunci pendek berulang -> mudah dipecahkan dengan analisis frekuensi.\n");
    printf("- Tidak ada integritas: perubahan 1 bit ciphertext mengubah 1 bit plaintext.\n");
    printf("- Tidak ada autentikasi: attacker bisa memodifikasi tanpa terdeteksi.\n");
    printf("- TIDAK ada IV/nonce: plaintext sama -> ciphertext sama (deterministik).\n");

    free(ciphertext);
    free(decrypted);
    return 0;
}

Output yang Dihasilkan:

text
=== Enkripsi XOR (DEMO saja, bukan produksi) ===
Plaintext : 'Halo Budi, ini pesan rahasia.'
Kunci     : 'KUNCIRAHASIA'
Ciphertext: 0334222c6910342c287f6928253c6e332c212026612128292a26272267
Dekripsi  : 'Halo Budi, ini pesan rahasia.'

=== Verifikasi: plaintext == dekripsi? ===
YA — round-trip XOR sukses (sifat simetri XOR).

=== Kelemahan XOR cipher ini ===
- Kunci pendek berulang -> mudah dipecahkan dengan analisis frekuensi.
- Tidak ada integritas: perubahan 1 bit ciphertext mengubah 1 bit plaintext.
- Tidak ada autentikasi: attacker bisa memodifikasi tanpa terdeteksi.
- TIDAK ada IV/nonce: plaintext sama -> ciphertext sama (deterministik).

Mari kita telaah baris-baris kode di atas:

  1. Baris 11-19 (xor_cipher): Fungsi inti. Loop meng-XOR setiap byte plaintext dengan byte kunci pada posisi i % key_len. Karena key_len = 13 (panjang "KUNCIRAHASIA"), kunci berulang setiap 13 byte—pola yang dapat dideteksi dengan analisis frekuensi pada ciphertext yang panjang.
  2. Baris 22-28 (print_hex): Mencetak byte dalam heksadesimal karena ciphertext berisi byte non-printable. Output 0334222c... adalah representasi heksa dari byte-byte hasil XOR.
  3. Baris 41 (enkripsi): xor_cipher(plaintext, key, ciphertext, len + 1) menghasilkan ciphertext. Plaintext Halo Budi, ini pesan rahasia. (28 byte) di-XOR dengan kunci KUNCIRAHASIA (13 byte) yang berulang.
  4. Baris 47 (dekripsi): Memanggil xor_cipher yang sama dengan ciphertext sebagai input. Karena (P ^ K) ^ K == P, hasil dekripsi identik dengan plaintext asli—properti simetri XOR.
  5. Output (verifikasi): Round-trip sukses. Tetapi kesuksesan round-trip tidak berarti algoritma aman. Keamanan kriptografi diukur dari ketahanan terhadap serangan, bukan dari keberhasilan fungsi normal.
  6. Output (kelemahan): Empat kelemahan utama. Yang paling krusial: tidak ada IV/nonce, sehingga plaintext yang sama selalu menghasilkan ciphertext yang sama. Penyerang yang melihat ciphertext identik tahu bahwa plaintext juga identik—membocorkan pola komunikasi.

Pembandingan Konstan-Waktu #

Bahkan bila kita memakai pustaka kriptografi yang kuat, ada satu detail halus yang sering luput: cara membandingkan dua nilai (token, MAC, hash). Pembandingan string biasa memcmp kembali lebih cepat bila byte pertama berbeda—penyerang dapat mengukur waktu respons untuk menebak byte demi byte (timing attack). Berikut implementasi pembandingan konstan-waktu:

c
/* extra_cryptography_basics.c — pembandingan konstan-waktu untuk token. */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

/* Pembandingan konstan-waktu: hindari bocor info melalui timing. */
static int constant_time_equal(const unsigned char *a,
                                const unsigned char *b, size_t n) {
    volatile uint8_t diff = 0;
    for (size_t i = 0; i < n; i++) {
        diff |= a[i] ^ b[i];
    }
    return diff == 0;
}

int main(void) {
    const unsigned char token_a[] = "abcdef0123456789";
    const unsigned char token_b[] = "abcdef0123456789"; /* identik */
    const unsigned char token_c[] = "abcdef012345678X"; /* beda 1 char */
    size_t len = 16;

    printf("a vs b (identik) : %s\n",
           constant_time_equal(token_a, token_b, len) ? "COCOK" : "BEDA");
    printf("a vs c (1 beda)  : %s\n",
           constant_time_equal(token_a, token_c, len) ? "COCOK" : "BEDA");
    printf("(Kedua pembandingan membutuhkan waktu yang sama — anti timing attack)\n");
    return 0;
}

Output yang Dihasilkan:

text
a vs b (identik) : COCOK
a vs c (1 beda)  : BEDA
(Kedua pembandingan membutuhkan waktu yang sama — anti timing attack)

Kunci implementasi: variabel diff ditandai volatile agar compiler tidak mengoptimalkan loop menjadi short-circuit. XOR setiap byte dan OR ke diff—setelah loop selesai, diff == 0 hanya bila semua byte identik. Waktu eksekusi tetap sama terlepas dari posisi byte yang berbeda. Produksi sebaiknya memakai crypto_verify_* dari libsodium atau CRYPTO_memcmp dari OpenSSL yang telah diaudit.

Konsep Inti yang Perlu Diketahui #

Sebelum memakai pustaka kriptografi, pahami konsep berikut:

  • Symmetric vs asymmetric: Simetri (satu kunci untuk enkripsi dan dekripsi: AES, ChaCha20) cepat tetapi memerlukan pertukaran kunci aman. Asimetri (sepasang kunci: publik dan privat: RSA, ECDSA) menyelesaikan pertukaran kunci tetapi lambat dan ukuran kunci besar.
  • Hash vs encryption: Hash satu arah (SHA-256, BLAKE2b) menghasilkan digest tetap tanpa kunci. Enkripsi (AES, ChaCha20) dua arah dengan kunci. Hash bukan untuk kerahasiaan—hanya untuk integritas dan identifikasi.
  • MAC vs signature: MAC (Message Authentication Code) memakai kunci simetri untuk autentikasi. Tanda tangan digital memakai kunci privat untuk menandatangani, kunci publik untuk verifikasi.
  • Authenticated encryption: AES-GCM dan ChaCha20-Poly1305 menggabungkan enkripsi dan autentikasi dalam satu operasi. Selalu pilih mode authenticated encryption—mode ECB dan CBC tanpa MAC rentan padding oracle.
  • KDF: Key Derivation Function (PBKDF2, scrypt, Argon2) mengubah password manusia menjadi kunci kriptografi dengan menambahkan work factor untuk melawan brute force.

Contoh Produksi: libsodium #

Implementasi enkripsi simetri dengan libsodium jauh lebih ringkas dan aman:

c
/* Contoh konseptual libsodium — bukan demo runnable (butuh libsodium). */
#include <sodium.h>

#define MSG_LEN 32
#define CIPHERTEXT_LEN (MSG_LEN + crypto_secretbox_MACBYTES)

int encrypt_example(const unsigned char *key,
                    const unsigned char *nonce,
                    const unsigned char *plaintext,
                    unsigned char *ciphertext) {
    /* crypto_secretbox_easy: enkripsi + autentikasi dalam satu langkah. */
    return crypto_secretbox_easy(ciphertext, plaintext, MSG_LEN,
                                 nonce, key);
}

int decrypt_example(const unsigned char *key,
                    const unsigned char *nonce,
                    const unsigned char *ciphertext,
                    unsigned char *plaintext) {
    /* Mengembalikan 0 bila sukses, -1 bila autentikasi gagal. */
    return crypto_secretbox_open_easy(plaintext, ciphertext, CIPHERTEXT_LEN,
                                      nonce, key);
}

Perhatikan tiga elemen yang hilang dari XOR cipher kita:

  1. Nonce: Setiap enkripsi memerlukan nonce unik (24 byte untuk crypto_secretbox). Nonce yang sama dengan kunci yang sama menghasilkan ciphertext yang dapat dieksploitasi.
  2. MAC: crypto_secretbox_MACBYTES (16 byte) ditambahkan ke ciphertext untuk autentikasi. crypto_secretbox_open_easy memverifikasi MAC sebelum dekripsi—penyerang yang memodifikasi ciphertext akan ditolak.
  3. Kunci panjang: crypto_secretbox_KEYBYTES adalah 32 byte (256 bit). Tidak ada masalah kunci pendek berulang.

Daftar Cek Sebelum Memakai Library Crypto #

  • Versi terbaru: Pastikan library di atas versi yang diperbaiki untuk CVE terbaru.
  • Algoritma modern: Hindari DES, 3DES, MD5, SHA-1, RC4. Pakai AES-256, ChaCha20, SHA-256/384/512, BLAKE2/3.
  • Mode aman: AES-GCM atau AES-CCM (bukan ECB atau CBC tanpa MAC).
  • Random aman: Pakai randombytes_buf dari libsodium atau /dev/urandom—tidak pernah rand().
  • Kunci dari KDF: Jangan pernah menurunkan kunci langsung dari password manusia. Pakai Argon2id, scrypt, atau PBKDF2 dengan salt acak.
  • Konstant-time compare: Untuk membandingkan MAC atau token, pakai crypto_verify_* atau sodium_memcmp—bukan memcmp yang dapat bocor waktu eksekusi.