Security Fundamentals
Prerequisites: Back-end development with Ktor, Android app architecture
Most security problems come from a short list of mistakes. Knowing them covers most of the risk, on both Android and the server.
1. Injection — never mix data into code
Build SQL with parameters, never by inserting user input into the string:
// DANGEROUS: SQL injection
// connection.createStatement().executeQuery("SELECT * FROM users WHERE email = '$email'")
// Safe: the value is sent separately from the SQL
connection.prepareStatement("SELECT id, name FROM users WHERE email = ?").use { stmt ->
stmt.setString(1, email)
stmt.executeQuery().use { rows -> /* ... */ }
}
Room, Exposed, and other libraries use parameters for you — as long as
you don't build raw query strings yourself. The same rule applies to
shell commands: pass arguments as a list (ProcessBuilder("convert", input, output)), never as one interpolated string.
2. Secrets
- Never hard-code API keys or passwords in source code. On the server, read them from environment variables or a secrets manager.
- Anything inside an Android APK can be extracted by anyone who downloads it. Keep truly secret keys on your server, and have the app authenticate to the server with per-user tokens.
- On Android, protect tokens with the Android Keystore, which keeps cryptographic keys in secure hardware where available.
3. Randomness and hashing
Use SecureRandom for tokens — never Random or Math.random():
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.Base64
fun main() {
val bytes = ByteArray(24).also { SecureRandom().nextBytes(it) }
val token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
println(token.length)
val digest = MessageDigest.getInstance("SHA-256").digest("hello".toByteArray())
println(digest.joinToString("") { "%02x".format(it) })
}
32
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256 is fine for checksums, but not for passwords — it's far too fast, so attackers can try billions of guesses per second. Store passwords with a dedicated algorithm such as bcrypt or Argon2.
4. Validate on the server
Anything in a request can be changed by the sender, even if it came from your own app. Validate input and check permissions on the server for every request — client-side checks are only for user convenience.
5. Android-specific
- Use HTTPS only; Android blocks cleartext HTTP by default for modern apps — keep it that way.
- Don't export activities, services, or content providers unless other
apps genuinely need them (
android:exported="false"). - Request permissions only when the feature needs them.
- Enable R8 for release builds, which also makes reverse engineering harder (but never rely on that to protect secrets).
6. Dependencies
Libraries run with your app's full permissions. Keep them updated, and use GitHub's Dependabot or the OWASP Dependency-Check Gradle plugin to be alerted to known vulnerabilities.
Common mistakes
- Logging tokens, passwords, or personal data. Logs are often stored for a long time and read by many people.
- Returning full exception details and stack traces to API clients in production.