Skip to content
AgileLogics
6 min read

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.