Keamanan adalah tanggung jawab setiap developer, bukan hanya tim security. Ekosistem Kotlin mewarisi tooling keamanan dari JVM (java.security.*, javax.crypto.*) serta praktik khusus Android (ProGuard, R8). Pada chapter ini kita memetakan OWASP Top 10, mendemokan hashing password, AES-GCM encryption, validasi input, dan HTML escape — semua dengan nilai deterministik untuk pengujian.
OWASP Top 10 (2021) #
OWASP mempublikasikan daftar 10 risiko keamanan paling umum pada aplikasi web. Berikut ringkasannya:
| Kode | Risiko | Mitigasi umum |
|---|---|---|
| A01 | Broken Access Control | Cek otorisasi di setiap endpoint; jangan percaya request klien |
| A02 | Cryptographic Failures | Pakai algoritma modern (AES-GCM, SHA-256+); jangan simpan secret di kode |
| A03 | Injection (SQL, XSS, Command) | Parameterized query; escape output HTML |
| A04 | Insecure Design | Threat modeling sejak desain |
| A05 | Security Misconfiguration | Hardening default; matikan debug di produksi |
| A06 | Vulnerable Components | CVE scan; update dependency berkala |
| A07 | Identification & Auth Failures | Hash password (bcrypt/argon2); MFA |
| A08 | Software & Data Integrity | Signed update; verifikasi integritas file |
| A09 | Security Logging Failures | Log event keamanan; alerting |
| A10 | SSRF (Server-Side Request Forgery) | Whitelist URL; jangan izinkan IP internal |
Demo Hashing, Encryption, dan Validasi #
Cuplikan berikut mendemokan empat praktik keamanan: hashing password dengan SHA-256 + salt, AES-GCM encryption simetris, validasi username lewat regex, dan HTML escape untuk mencegah XSS. Salt dan IV dibekukan agar output deterministik untuk pengujian:
// panduan_keamanan.kt
// Tour singkat Panduan Keamanan untuk Kotlin:
// - OWASP Top 10: https://owasp.org/www-project-top-ten/
// - ProGuard / R8: code shrinking & obfuscation untuk Android
// - Cryptography: java.security.*, javax.crypto.*
//
// File ini mendemokan:
// - Hashing password (SHA-256 + salt)
// - AES symmetric encryption
// - Validasi input (SQL injection, XSS)
@file:OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
import java.security.MessageDigest
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64
// === Password hashing (SHA-256 + salt) ===
class PasswordHasher {
private val random = SecureRandom()
fun hash(password: String, salt: ByteArray = ByteArray(16).also(random::nextBytes)): String {
val md = MessageDigest.getInstance("SHA-256")
md.update(salt)
val hashed = md.digest(password.toByteArray(Charsets.UTF_8))
val saltB64 = Base64.encode(salt)
val hashB64 = Base64.encode(hashed)
return "$saltB64:$hashB64"
}
fun verify(password: String, stored: String): Boolean {
val parts = stored.split(":")
if (parts.size != 2) return false
val salt = Base64.decode(parts[0])
val expected = parts[1]
val actual = hash(password, salt).split(":")[1]
return actual == expected
}
}
// === AES-GCM symmetric encryption ===
class AesGcmCipher(keyBytes: ByteArray) {
private val secretKey: SecretKey = SecretKeySpec(keyBytes, "AES")
fun encrypt(plaintext: String): Pair<String, ByteArray> {
val iv = ByteArray(12).also(SecureRandom()::nextBytes)
return encryptWithIv(plaintext, iv) to iv
}
fun encryptWithIv(plaintext: String, iv: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
val encrypted = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
return Base64.encode(encrypted)
}
fun decrypt(ciphertextB64: String, iv: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
val decoded = Base64.decode(ciphertextB64)
return String(cipher.doFinal(decoded), Charsets.UTF_8)
}
}
// === Input validation ===
object Validator {
fun sanitizeUsername(input: String): String? {
val pattern = Regex("^[a-zA-Z0-9_]{3,32}$")
return if (pattern.matches(input)) input else null
}
fun sanitizeEmail(input: String): String? {
val pattern = Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
return if (pattern.matches(input)) input else null
}
fun escapeHtml(input: String): String = input
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
}
@OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
fun main() {
println("=== Panduan Keamanan Kotlin ===")
println("OWASP Top 10 | ProGuard/R8 | Java Cryptography (JCA)")
println()
println("--- 1. Password hashing (SHA-256 + salt) ---")
val hasher = PasswordHasher()
val password = "rahasiaKu123"
// Salt beku untuk determinisme demo. Produksi: pakai SecureRandom.
val fixedSalt = ByteArray(16) { (it + 1).toByte() }
val stored = hasher.hash(password, fixedSalt)
println(" password: $password")
println(" stored : $stored (salt:hash)")
println(" verify ('rahasiaKu123') : ${hasher.verify("rahasiaKu123", stored)}")
println(" verify ('salah') : ${hasher.verify("salah", stored)}")
println()
println("--- 2. AES-GCM encryption ---")
val keyBytes = ByteArray(32).also { it.indices.forEach { i -> it[i] = (i + 1).toByte() } }
val cipher = AesGcmCipher(keyBytes)
val plaintext = "Halo, ini pesan rahasia"
// IV beku untuk determinisme demo. Produksi: pakai SecureRandom per encrypt.
val iv = ByteArray(12) { (it + 1).toByte() }
val encrypted = cipher.encryptWithIv(plaintext, iv)
println(" plaintext : $plaintext")
println(" encrypted : $encrypted")
println(" iv (12 B) : ${Base64.encode(iv)}")
val decrypted = cipher.decrypt(encrypted, iv)
println(" decrypted : $decrypted")
println(" verifikasi: ${plaintext == decrypted}")
println()
println("--- 3. Input validation ---")
val usernames = listOf("budi", "siti_123", "admin'; DROP TABLE--", "a", "nama_sangat_panjang_tidak_valid_xxxxx")
usernames.forEach { u ->
val sanitized = Validator.sanitizeUsername(u)
println(" \"$u\" -> ${sanitized ?: "(ditolak)"}")
}
println()
println("--- 4. HTML escape (XSS prevention) ---")
val userInput = "<script>alert('xss')</script>"
println(" input : $userInput")
println(" escaped: ${Validator.escapeHtml(userInput)}")
println()
println("--- 5. OWASP Top 10 (2021) ---")
val owasp = listOf(
"A01 Broken Access Control",
"A02 Cryptographic Failures",
"A03 Injection (SQL, XSS, Command)",
"A04 Insecure Design",
"A05 Security Misconfiguration",
"A06 Vulnerable Components",
"A07 Identification & Auth Failures",
"A08 Software & Data Integrity",
"A09 Security Logging Failures",
"A10 SSRF (Server-Side Request Forgery)"
)
owasp.forEach { println(" $it") }
println()
println("--- 6. ProGuard / R8 (Android) ---")
val proguard = listOf(
"Code shrinking -> hapus class/method tak terpakai",
"Resource shrinking -> hapus gambar/string tak terpakai",
"Obfuscation -> rename class/field jadi a, b, c",
"Optimization -> inline, dead code elimination"
)
proguard.forEach { println(" $it") }
println()
println("--- 7. Aturan praktis keamanan ---")
val aturan = listOf(
"1. Hash password dengan bcrypt/argon2 (bukan SHA-256 plain)",
"2. Pakai HTTPS (TLS 1.2+) untuk semua komunikasi",
"3. Validasi SEMUA input (client + server)",
"4. Parameterized query (jangan konkatenasi SQL)",
"5. Jangan log sensitive (password, token, PII)",
"6. Rotate secret dan key secara berkala",
"7. Pakai secret manager (Vault, AWS Secrets Manager)",
"8. Update dependency secara berkala (CVE scan)"
)
aturan.forEach { println(" $it") }
}Output yang Dihasilkan:
=== Panduan Keamanan Kotlin ===
OWASP Top 10 | ProGuard/R8 | Java Cryptography (JCA)
--- 1. Password hashing (SHA-256 + salt) ---
password: rahasiaKu123
stored : AQIDBAUGBwgJCgsMDQ4PEA==:rXkSbvpxTX2j77MhlO9gjE3OI/sfTjh2h+YGgPUv5SM= (salt:hash)
verify ('rahasiaKu123') : true
verify ('salah') : false
--- 2. AES-GCM encryption ---
plaintext : Halo, ini pesan rahasia
encrypted : 5k/A8hxLSoMGnYhAkJtTBXs/mkYlHLByX57kYHF62D9jmx0SUr64
iv (12 B) : AQIDBAUGBwgJCgsM
decrypted : Halo, ini pesan rahasia
verifikasi: true
--- 3. Input validation ---
"budi" -> budi
"siti_123" -> siti_123
"admin'; DROP TABLE--" -> (ditolak)
"a" -> (ditolak)
"nama_sangat_panjang_tidak_valid_xxxxx" -> (ditolak)
--- 4. HTML escape (XSS prevention) ---
input : <script>alert('xss')</script>
escaped: <script>alert('xss')</script>
--- 5. OWASP Top 10 (2021) ---
A01 Broken Access Control
A02 Cryptographic Failures
A03 Injection (SQL, XSS, Command)
A04 Insecure Design
A05 Security Misconfiguration
A06 Vulnerable Components
A07 Identification & Auth Failures
A08 Software & Data Integrity
A09 Security Logging Failures
A10 SSRF (Server-Side Request Forgery)
--- 6. ProGuard / R8 (Android) ---
Code shrinking -> hapus class/method tak terpakai
Resource shrinking -> hapus gambar/string tak terpakai
Obfuscation -> rename class/field jadi a, b, c
Optimization -> inline, dead code elimination
--- 7. Aturan praktis keamanan ---
1. Hash password dengan bcrypt/argon2 (bukan SHA-256 plain)
2. Pakai HTTPS (TLS 1.2+) untuk semua komunikasi
3. Validasi SEMUA input (client + server)
4. Parameterized query (jangan konkatenasi SQL)
5. Jangan log sensitive (password, token, PII)
6. Rotate secret dan key secara berkala
7. Pakai secret manager (Vault, AWS Secrets Manager)
8. Update dependency secara berkala (CVE scan)Perhatikan empat kelompok yang dicetak: password hash yang diverifikasi benar untuk password asli dan salah untuk password keliru, AES-GCM encrypt-decrypt yang menghasilkan plaintext kembali, validasi username yang menolak SQL injection dan nama terlalu panjang, dan HTML escape yang mengubah <script> menjadi <script>.
ProGuard dan R8 untuk Android #
ProGuard dan R8 adalah tool shrinker dan obfuscator untuk Android. R8 adalah pengganti modern ProGuard yang dikirim bersama Android Gradle Plugin. Keduanya melakukan empat transformasi: code shrinking (menghapus class dan method yang tidak terpakai), resource shrinking (menghapus file di res/ yang tidak terpakai), obfuscation (men-rename class dan field menjadi a, b, c), serta optimization (inline dan dead code elimination). Hasilnya APK lebih kecil dan lebih sulit dibalik-rekayasa.
Referensi Lanjutan #
- Kotlin documentation home — halaman utama dokumentasi Kotlin sebagai pintu masuk topik keamanan tipe, null-safety, dan kontrak fungsi
Selanjutnya #
Aplikasi yang aman belum tentu cepat. Chapter berikutnya membahas panduan performance — JMH untuk microbenchmark, async-profiler untuk profil CPU, dan tuning JVM yang umum dipakai produksi.