Android Network Security Configuration
What Is network_security_config.xml?
Android 7.0 (API 24) introduced network_security_config.xml, a declarative way to configure TLS settings, cleartext traffic, and certificate trusts without writing code.
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<!-- Block cleartext (HTTP) traffic in production -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- Allow cleartext for local development -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
<!-- Certificate pinning for your API -->
<domain-config>
<domain includeSubdomains="true">api.yourapp.com</domain>
<pin-set expiration="2025-12-31">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>
Reference it in your manifest:
<application
android:networkSecurityConfig="@xml/network_security_config"
... >
Why Block Cleartext?
Cleartext HTTP traffic is vulnerable to eavesdropping and tampering. On Android 9+, cleartext is blocked by default. If your app still needs HTTP (for local dev or third-party services), you must explicitly permit it per domain.
// Checking network connectivity and security at runtime
fun isNetworkSecure(context: Context): Boolean {
val connectivityManager = context.getSystemService<ConnectivityManager>()
val network = connectivityManager?.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
Certificate Pinning and Trust
How TLS Certificate Validation Works
When your app connects to api.yourapp.com, the server presents a certificate. Your device validates it by checking:
- Is the certificate signed by a trusted Certificate Authority (CA)?
- Is the certificate for the correct domain?
- Has the certificate expired?
- Has the certificate been revoked?
Certificate pinning adds step 5: Does the certificate match a known fingerprint?
Why Pin Certificates?
A compromised CA can issue fraudulent certificates. Without pinning, an attacker with a rogue certificate can intercept all traffic via a MITM proxy. Pinning limits which certificates your app trusts, even if a CA is compromised.
Pinning with OkHttp
fun createPinnedClient(): OkHttpClient {
val certificatePinner = CertificatePinner.Builder()
.add(
"api.yourapp.com",
"sha256/7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y="
)
.add(
"api.yourapp.com",
"sha256/fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE="
)
.build()
return OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
}
Always provide at least two pins: your current certificate and a backup. If you lose access to your private key, the backup pin keeps your app working.
Custom Trust Managers
For enterprise environments where you need to trust a private CA:
fun createCustomTrustClient(context: Context): OkHttpClient {
val cf = CertificateFactory.getInstance("X.509")
val ca = context.resources.openRawResource(R.raw.private_ca).use {
cf.generateCertificate(it)
}
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", ca)
}
val trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
).apply {
init(keyStore)
}
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, trustManagerFactory.trustManagers, null)
}
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManagerFactory.trustManagers[0] as X509TrustManager)
.build()
}
Warning: Custom trust managers bypass the system trust store. Only use them for legitimate private CAs, never to disable certificate validation.
Production Security Checklist
Network Security Checklist
- Disable cleartext in production via
network_security_config.xml - Pin certificates for your primary API domain with at least two pins
- Use HTTPS everywhere - never send tokens or user data over HTTP
- Validate hostnames - ensure OkHttp does not follow redirects to different domains
- Rotate pins before expiration using the
expirationattribute in pin-set
Hostname Verification
OkHttp verifies hostnames by default. Disable this only for testing:
// NEVER do this in production
val unsafeClient = OkHttpClient.Builder()
.hostnameVerifier { _, _ -> true } // Disables hostname verification!
.build()
Token Security on Android
Store tokens in EncryptedSharedPreferences, never in plain text:
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Store token
securePrefs.edit().putString("auth_token", token).apply()
// Read token
val token = securePrefs.getString("auth_token", null)
Network Safety with Certificate Transparency
Certificate Transparency (CT) logs all issued certificates publicly. Android 15+ enforces CT for new apps. You can add CT checks to OkHttp with the certificatetransparency library:
val client = OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("api.yourapp.com", "sha256/...")
.build()
)
.build()
Common Vulnerabilities
| Vulnerability | Mitigation |
|---|---|
| Cleartext HTTP | Block via network security config |
| Expired certificates | Rotate pins before expiration |
| Rogue CA certificates | Certificate pinning |
| Token leakage in logs | Disable BODY logging in production |
| Insecure storage | EncryptedSharedPreferences |
| Open redirect | Disable followSslRedirects or validate domains |
Quiz
1. What is the purpose of certificate pinning?
2. Why should you provide at least two certificate pins?
3. What does cleartextTrafficPermitted=false do in network_security_config.xml?
4. Why should tokens never be stored in plain SharedPreferences?
Flashcards
Question
What is a Man-in-the-Middle (MITM) attack?
Click to reveal answer
Answer
An attacker intercepts communication between the app and server by presenting a fraudulent certificate. Certificate pinning prevents this by only trusting certificates with known fingerprints.
Question
What is the difference between network_security_config.xml and OkHttp certificate pinning?
Click to reveal answer
Answer
network_security_config.xml is a system-level config that applies to all HTTP clients on the device. OkHttp certificate pinning applies only to requests made through that specific OkHttp client instance.
Question
What does EncryptedSharedPreferences use to protect data?
Click to reveal answer
Answer
It uses the Android Keystore system with AES-256 encryption. The encryption keys are stored in hardware-backed secure storage on supported devices.
Question
Why is hostname verification important?
Click to reveal answer
Answer
It ensures the server certificate is valid for the domain you are connecting to. Without it, an attacker could present a valid certificate for a different domain and intercept your traffic.
Revision Notes
Key Takeaways
- 1. Block cleartext traffic in production via network_security_config.xml
- 2. Pin at least two certificate hashes with a rotation strategy
- 3. Store sensitive data in EncryptedSharedPreferences, never plain storage
- 4. Never disable hostname verification or certificate validation in production
Interview Tips
- • Explain how certificate pinning prevents MITM attacks
- • Discuss the difference between system-level and app-level TLS configuration
- • Describe how you would rotate pinned certificates without breaking existing app versions
- • Know why Android blocks cleartext by default and when you might need exceptions
Cheat Sheet
Network Security Cheat Sheet
network_security_config.xml:
- cleartextTrafficPermitted=false blocks HTTP
- pin-set with SHA-256 pins for your domain
- domain-config for dev/local exceptions
- Reference in AndroidManifest.xml
Certificate Pinning:
- Always provide 2+ pins (current + backup)
- Set expiration date for pin rotation
- Use OkHttp CertificatePinner.Builder()
- Pin the SPKI hash, not the full certificate
Token Security:
- Store in EncryptedSharedPreferences
- Never log tokens or secrets
- Use Android Keystore for key management
- Clear tokens on logout
Checklist:
- Block cleartext in production
- Pin certificates for primary API
- Validate hostnames
- Never disable SSL verification