Tantangan Keamanan Data Sensitif pada Oracle Database
Penyimpanan data sensitif seperti nomor kartu pembayaran (PAN), data identitas kependudukan, atau kredensial sistem secara plaintext di database melanggar standar kepatuhan regulasi seperti PCI-DSS dan ISO 27001. Meskipun Oracle menyediakan fitur Transparent Data Encryption (TDE), implementasi TDE memerlukan lisensi Oracle Advanced Security pada edisi Enterprise dan mengenkripsi seluruh tablespace atau kolom secara transparan bagi semua sesi database yang memiliki hak SELECT.
Ketika aplikasi membutuhkan kontrol akses granular tingkat baris atau ketika berjalan pada edisi database yang tidak mendukung TDE, enkripsi terprogram berbasis PL/SQL melalui package bawaan DBMS_CRYPTO menjadi alternatif utama. Namun, implementasi manual sering kali rentan terhadap kesalahan arsitektur kriptografi dasar seperti pemilihan mode cipher yang salah dan penyimpanan kunci yang terekspos langsung dalam database.
Risiko Keamanan Umum pada Implementasi DBMS_CRYPTO
1. Penggunaan Electronic Codebook (ECB) Mode
Mode ECB membagi plaintext menjadi blok-blok tetap dan mengenkripsi setiap blok dengan kunci yang sama secara independen. Jika dua blok plaintext bernilai identik, ciphertext yang dihasilkan juga identik. Pola data tetap terbaca oleh penyerang meskipun isinya terenkripsi. Hindari penggunaan cipher suite seperti DBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_ECB untuk data tabular.
2. Hardcoded Key pada Package Body
Menyimpan raw key di dalam konstanta atau variabel package PL/SQL (misalnya c_key RAW(32) := HEXTORAW('...');) merupakan pelanggaran isolasi hak akses. Siapa pun dengan hak akses SELECT_CATALOG_ROLE, DBA, atau pengguna yang dapat membaca view ALL_SOURCE / DBA_SOURCE dapat mengekstrak kunci enkripsi secara langsung.
Arsitektur Pemisahan Kunci (Key Separation)
Prinsip least privilege mengharuskan data terenkripsi dan kunci pembukanya tidak berada pada domain wewenang yang sama:
- Skema Terisolasi: Buat skema terpisah (misalnya
CRYPTO_CORE) khusus untuk mengeksekusi package enkripsi, tanpa memberikan hak akses tabel bisnis ke skema tersebut. Skema aplikasi hanya diberikan hakEXECUTEpada fungsi wrapper. - Dynamic Key Injection: Kunci enkripsi tidak disimpan permanen di dalam tabel database. Aplikasi backend mengambil master key dari Key Management Service eksternal (seperti HashiCorp Vault, AWS KMS, atau Azure Key Vault) saat runtime, kemudian menyuntikkannya ke sesi database via Application Context (
DBMS_SESSION.SET_CONTEXT) atau melewatkannya sebagai parameter fungsi pada saat transaksi berlangsung.
Implementasi Enkripsi Kolom AES-256-CBC dengan Unique IV
Standar keamanan simetris yang direkomendasikan adalah AES-256 dengan mode Cipher Block Chaining (CBC) dan skema padding PKCS#5. Mode CBC membutuhkan Initialization Vector (IV) acak sepanjang 16 byte untuk setiap operasi enkripsi guna memastikan ciphertext selalu unik meskipun plaintext bernilai sama.
IV bukan merupakan data rahasia, namun harus unik per enkripsi. Praktik terbaik adalah menggabungkan (concatenate) nilai IV pada awal payload ciphertext dalam format RAW, lalu menyimpannya dalam satu kolom bertipe data RAW.
1. Package Specification
CREATE OR REPLACE PACKAGE crypto_engine_pkg AUTHID DEFINER AS
FUNCTION encrypt_data (
p_plain_text IN VARCHAR2,
p_key IN RAW
) RETURN RAW;
FUNCTION decrypt_data (
p_crypto_payload IN RAW,
p_key IN RAW
) RETURN VARCHAR2;
END crypto_engine_pkg;
/2. Package Body
CREATE OR REPLACE PACKAGE BODY crypto_engine_pkg AS
-- Suite: AES-256 + CBC Mode + PKCS#5 Padding
c_cipher_suite CONSTANT PLS_INTEGER :=
DBMS_CRYPTO.ENCRYPT_AES256
+ DBMS_CRYPTO.CHAIN_CBC
+ DBMS_CRYPTO.PAD_PKCS5;
c_iv_length CONSTANT PLS_INTEGER := 16; -- 128-bit block size untuk AES
FUNCTION encrypt_data (
p_plain_text IN VARCHAR2,
p_key IN RAW
) RETURN RAW IS
v_raw_data RAW(32767);
v_iv RAW(16);
v_encrypted RAW(32767);
BEGIN
IF p_plain_text IS NULL THEN
RETURN NULL;
END IF;
-- Konversi VARCHAR2 ke RAW dengan character set eksplisit
v_raw_data := UTL_I18N.STRING_TO_RAW(p_plain_text, 'AL32UTF8');
-- Hasilkan IV acak kriptografis
v_iv := DBMS_CRYPTO.RANDOMBYTES(c_iv_length);
-- Enkripsi data
v_encrypted := DBMS_CRYPTO.ENCRYPT(
src => v_raw_data,
typ => c_cipher_suite,
key => p_key,
iv => v_iv
);
-- Gabungkan IV (16 byte) + Ciphertext
RETURN UTL_RAW.CONCAT(v_iv, v_encrypted);
END encrypt_data;
FUNCTION decrypt_data (
p_crypto_payload IN RAW,
p_key IN RAW
) RETURN VARCHAR2 IS
v_iv RAW(16);
v_encrypted RAW(32767);
v_decrypted RAW(32767);
BEGIN
IF p_crypto_payload IS NULL THEN
RETURN NULL;
END IF;
-- Ekstrak 16 byte pertama sebagai IV
v_iv := UTL_RAW.SUBSTR(p_crypto_payload, 1, c_iv_length);
-- Ekstrak sisa payload sebagai ciphertext murni
v_encrypted := UTL_RAW.SUBSTR(p_crypto_payload, c_iv_length + 1);
-- Dekripsi payload
v_decrypted := DBMS_CRYPTO.DECRYPT(
src => v_encrypted,
typ => c_cipher_suite,
key => p_key,
iv => v_iv
);
-- Konversi RAW kembali ke VARCHAR2
RETURN UTL_I18N.RAW_TO_CHAR(v_decrypted, 'AL32UTF8');
END decrypt_data;
END crypto_engine_pkg;
/Pengujian dan Verifikasi
Contoh eksekusi menggunakan kunci AES-256 (32 byte / 256 bit):
DECLARE
-- Kunci 256-bit contoh (harus di-supply aman via parameter/KMS saat produksi)
v_master_key RAW(32) := UTL_I18N.STRING_TO_RAW('InitSecretKeyForAES256Test32Byte', 'AL32UTF8');
v_source_text VARCHAR2(100) := 'NomorRekeningRahasia-99881122';
v_encrypted_1 RAW(2000);
v_encrypted_2 RAW(2000);
v_decrypted VARCHAR2(100);
BEGIN
-- Enkripsi dua kali untuk teks yang sama
v_encrypted_1 := crypto_engine_pkg.encrypt_data(v_source_text, v_master_key);
v_encrypted_2 := crypto_engine_pkg.encrypt_data(v_source_text, v_master_key);
-- Output ciphertext berbeda karena IV acak
DBMS_OUTPUT.PUT_LINE('Cipher 1: ' || RAWTOHEX(v_encrypted_1));
DBMS_OUTPUT.PUT_LINE('Cipher 2: ' || RAWTOHEX(v_encrypted_2));
-- Dekripsi Cipher 1
v_decrypted := crypto_engine_pkg.decrypt_data(v_encrypted_1, v_master_key);
DBMS_OUTPUT.PUT_LINE('Decrypted: ' || v_decrypted);
END;
/Konversi Tipe Data: RAW vs VARCHAR2
Kesalahan fatal yang sering terjadi adalah memperlakukan output enkripsi sebagai string karakter biasa. Ciphertext biner dapat memuat byte kontrol atau null byte (0x00) yang menyebabkan pemotongan string secara tidak terduga pada VARCHAR2.
- Simpan kolom terenkripsi pada database dengan tipe
RAW(misalRAW(2000)) atauBLOBjika ukuran data melebihi limit inline. - Hindari konversi ke
VARCHAR2berbasis Hexadecimal atau Base64 di layer database storage jika tidak diperlukan, karena encoding HEX meningkatkan konsumsi ruang penyimpanan sebesar 100% (2x lipat), sementara Base64 meningkatkan ukuran sekitar 33%. Gunakan formatRAWlangsung untuk efisiensi penyimpanan block I/O. - Gunakan fungsi eksplisit
UTL_I18N.STRING_TO_RAW(str, 'AL32UTF8')untuk mencegah dependensi terhadap variable lingkunganNLS_LANGsesi database.
Dampak Performa I/O dan CPU
1. Lonjakan Utilisasi CPU
Operasi komputasi AES dieksekusi oleh CPU database core. Operasi bulk DML (ribuan baris per detik) yang memanggil fungsi enkripsi baris-per-baris melalui trigger atau inline SQL query akan memicu overhead CPU signifikan. Batasi enkripsi hanya pada kolom dengan klasifikasi data konfidensial tinggi.
2. Redundansi Storage & I/O Overhead
Penambahan IV acak (16 byte) dan PKCS#5 block padding (1-16 byte) menyebabkan ukuran data bertambah minimal 17 hingga 32 byte per nilai kolom. Hal ini menurunkan kepadatan baris per blok database, meningkatkan frekuensi I/O read/write, serta memperbesar konsumsi buffer cache SGA.
3. Keterbatasan Pengindeksan (Indexing Constraints)
Penggunaan CBC dengan IV acak menghasilkan ciphertext non-deterministik. Anda tidak dapat melakukan pencarian tepat langsung (exact match) menggunakan index B-Tree standar terhadap kolom ciphertext (misal: WHERE encrypted_col = :val akan selalu gagal). Jika fitur pencarian nilai eksak tetap diperlukan, gunakan pola blind indexing (menyimpan kolom terpisah berisi hash HMAC ber-salt dari data asli khusus untuk kebutuhan indeks pencarian).
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!