M01: Improper Credential Usage - Examples
Table of Contents
Vulnerable Examples
❌ Example 1: Hardcoded API Key
Vulnerable Code (Android - Java):``java
public class ApiClient {
// VULNERABLE: API key hardcoded in source code
private static final String API_KEY = "AIzaSyDxVW2E9vZpQN7h8dK2eZvN9vZpQN7h8d";
private static final String API_SECRET = "sk_live_51H7h8dK2eZvN9vZpQN7h8";
public Response makeApiCall(String endpoint) {
HttpClient client = new HttpClient();
client.addHeader("X-API-Key", API_KEY);
client.addHeader("X-API-Secret", API_SECRET);
return client.get(endpoint);
}
}
`
Why It's Vulnerable:
- API keys are embedded in compiled bytecode
- Anyone can decompile the APK and extract keys
- Keys can't be rotated without app update
- Same keys used for all app installations
❌ Example 2: Plain Text Password Storage
Vulnerable Code (Android - Java):
`java
public class LoginManager {
private SharedPreferences prefs;
public void saveCredentials(String username, String password) {
// VULNERABLE: Storing password in plain text
prefs.edit()
.putString("username", username)
.putString("password", password)
.apply();
}
public boolean login() {
String username = prefs.getString("username", "");
String password = prefs.getString("password", "");
return authenticate(username, password);
}
}
`
Why It's Vulnerable:
- SharedPreferences stored as XML in plain text
- Accessible on rooted devices
- Included in device backups
- No encryption protection
❌ Example 3: Logging Sensitive Information
Vulnerable Code (Android - Java):
`java
public class AuthService {
public void login(String email, String password) {
// VULNERABLE: Logging credentials
Log.d("Auth", "Attempting login for: " + email);
Log.d("Auth", "Password: " + password);
try {
Response response = api.login(email, password);
Log.d("Auth", "Auth token: " + response.getToken());
} catch (Exception e) {
Log.e("Auth", "Login failed: " + e.getMessage());
}
}
}
`
Why It's Vulnerable:
- Credentials visible in Logcat
- Logs may be included in crash reports
- Third-party monitoring tools capture logs
- Logs persisted on device
❌ Example 4: Credentials in Configuration Files
Vulnerable Code (Android - XML):
`xml
AIzaSyDxVW2E9vZpQN7h8dK2eZv
secret_key_12345
mysql://admin:password@db.example.com
`
Why It's Vulnerable:
- Easily extracted from APK
- No encryption
- Version controlled with app
- Can't be changed without app update
❌ Example 5: Weak Encryption
Vulnerable Code (iOS - Swift):
`swift
class CredentialStorage {
// VULNERABLE: Using simple Base64 encoding (not encryption)
func savePassword(_ password: String) {
let encodedData = password.data(using: .utf8)?.base64EncodedString()
UserDefaults.standard.set(encodedData, forKey: "password")
}
func getPassword() -> String? {
guard let encoded = UserDefaults.standard.string(forKey: "password"),
let data = Data(base64Encoded: encoded),
let password = String(data: data, encoding: .utf8) else {
return nil
}
return password
}
}
`
Why It's Vulnerable:
- Base64 is encoding, not encryption
- Trivially reversible
- Provides no security
- False sense of protection
Secure Examples
✅ Example 1: Using Android KeyStore
Secure Code (Android - Java):
`java
public class SecureCredentialManager {
private static final String KEY_ALIAS = "MySecureKeyAlias";
// Generate encryption key in KeyStore
private void generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(new KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
keyGenerator.generateKey();
}
// Securely store credentials
public void storeCredential(String key, String value) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8));
// Store encrypted data using EncryptedSharedPreferences
getEncryptedPrefs()
.edit()
.putString(key + "_value", Base64.encodeToString(encrypted, Base64.DEFAULT))
.putString(key + "_iv", Base64.encodeToString(iv, Base64.DEFAULT))
.apply();
}
}
`
✅ Example 2: Using iOS Keychain
Secure Code (iOS - Swift):
`swift
class KeychainManager {
// Store credential securely in iOS Keychain
func storeCredential(key: String, value: String) -> Bool {
guard let valueData = value.data(using: .utf8) else {
return false
}
// Delete existing item first
deleteCredential(key: key)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: valueData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
// Retrieve credential from Keychain
func retrieveCredential(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let data = result as? Data,
let credential = String(data: data, encoding: .utf8) else {
return nil
}
return credential
}
// Delete credential
func deleteCredential(key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
SecItemDelete(query as CFDictionary)
}
}
`
✅ Example 3: Runtime API Key Fetching
Secure Code (Android - Kotlin):
`kotlin
class SecureApiClient(private val context: Context) {
private var apiKey: String? = null
// Fetch API key from server at runtime
suspend fun initialize() {
try {
// Authenticate user first
val authToken = getStoredAuthToken()
// Request API key from backend
val response = apiService.requestApiKey(authToken)
// Store encrypted API key
storeApiKeySecurely(response.apiKey)
this.apiKey = response.apiKey
} catch (e: Exception) {
Log.e("SecureApiClient", "Failed to fetch API key", e)
throw SecurityException("Could not initialize secure client")
}
}
private fun storeApiKeySecurely(key: String) {
val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"api_prefs",
getMasterKey(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptedPrefs.edit()
.putString("api_key", key)
.putLong("key_timestamp", System.currentTimeMillis())
.apply()
}
}
`
✅ Example 4: Secure Logging
Secure Code (Android - Kotlin):
`kotlin
object SecureLogger {
private const val TAG = "SecureApp"
// Sanitize sensitive data before logging
fun logAuth(message: String) {
if (BuildConfig.DEBUG) {
// Even in debug, sanitize sensitive data
val sanitized = sanitizeMessage(message)
Log.d(TAG, sanitized)
}
// In production, consider using proper logging service
}
private fun sanitizeMessage(message: String): String {
var sanitized = message
// Remove potential passwords
sanitized = sanitized.replace(
Regex("password[\"']?\\s[:=]\\s[\"']?[^\"'\\s,}]+", RegexOption.IGNORE_CASE),
"password=*"
)
// Remove potential tokens
sanitized = sanitized.replace(
Regex("token[\"']?\\s[:=]\\s[\"']?[^\"'\\s,}]+", RegexOption.IGNORE_CASE),
"token=*"
)
// Remove potential email addresses
sanitized = sanitized.replace(
Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"),
"@.*"
)
return sanitized
}
}
`
✅ Example 5: Token Rotation
Secure Code (Kotlin):
`kotlin
class TokenManager(private val context: Context) {
private val secureStorage = SecureStorage(context)
companion object {
private const val TOKEN_LIFETIME_MILLIS = 15 60 1000 // 15 minutes
private const val REFRESH_BUFFER_MILLIS = 2 60 1000 // 2 minutes
}
suspend fun getValidToken(): String {
val token = secureStorage.getToken()
if (token == null || isTokenExpired(token)) {
return refreshToken()
}
// Proactively refresh if nearing expiration
if (isTokenNearExpiry(token)) {
lifecycleScope.launch {
refreshToken()
}
}
return token.accessToken
}
private fun isTokenExpired(token: Token): Boolean {
return System.currentTimeMillis() >= token.expiryTime
}
private fun isTokenNearExpiry(token: Token): Boolean {
val timeUntilExpiry = token.expiryTime - System.currentTimeMillis()
return timeUntilExpiry < REFRESH_BUFFER_MILLIS
}
private suspend fun refreshToken(): String {
val currentToken = secureStorage.getToken()
val refreshToken = currentToken?.refreshToken
?: throw SecurityException("No refresh token available")
try {
val newToken = apiService.refreshAccessToken(refreshToken)
secureStorage.storeToken(newToken)
return newToken.accessToken
} catch (e: Exception) {
// Refresh failed, require re-authentication
clearTokens()
throw AuthenticationRequiredException()
}
}
}
`
Common Patterns
Pattern 1: Backend-Provided Configuration
Concept: Never ship credentials with the app. Fetch them after authentication.
`kotlin
// App startup flow
class AppInitializer {
suspend fun initialize() {
// 1. User authenticates
val userToken = authenticateUser()
// 2. Fetch app configuration from backend
val config = backendService.getAppConfig(userToken)
// 3. Store configuration securely
secureStorage.storeConfig(config)
// 4. Initialize app with fetched configuration
initializeServices(config)
}
}
`
Pattern 2: Certificate Pinning
Concept: Prevent man-in-the-middle attacks by pinning certificates.
`kotlin
class SecureNetworkClient {
fun createOkHttpClient(): OkHttpClient {
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
return OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
}
}
`
Pattern 3: Biometric Authentication
Concept: Require biometric authentication to access stored credentials.
`kotlin
class BiometricCredentialAccess(private val activity: FragmentActivity) {
fun accessCredentials(onSuccess: (String) -> Unit, onFailure: () -> Unit) {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(activity, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
// Biometric verified, access credentials
val credential = secureStorage.getCredential()
onSuccess(credential)
}
override fun onAuthenticationFailed() {
onFailure()
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate to access account")
.setSubtitle("Use your fingerprint or face")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
`
Framework-Specific Examples
React Native
`javascript
// Using react-native-keychain
import * as Keychain from 'react-native-keychain';
// Store credentials
async function storeCredentials(username, password) {
try {
await Keychain.setGenericPassword(username, password, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
} catch (error) {
console.error('Failed to store credentials', error);
}
}
// Retrieve credentials
async function getCredentials() {
try {
const credentials = await Keychain.getGenericPassword();
if (credentials) {
return {
username: credentials.username,
password: credentials.password
};
}
} catch (error) {
console.error('Failed to retrieve credentials', error);
}
return null;
}
`
Flutter
`dart
// Using flutter_secure_storage
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureCredentialManager {
final storage = FlutterSecureStorage();
// Store credential
Future storeCredential(String key, String value) async {
await storage.write(
key: key,
value: value,
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: IOSAccessibility.first_unlock_this_device),
);
}
// Retrieve credential
Future getCredential(String key) async {
return await storage.read(key: key);
}
// Delete credential
Future deleteCredential(String key) async {
await storage.delete(key: key);
}
}
``
Comparison: Vulnerable vs Secure
| Aspect | ❌ Vulnerable | ✅ Secure |
|--------|--------------|----------|
| Storage | SharedPreferences (plain) | KeyStore + EncryptedSharedPrefs |
| API Keys | Hardcoded in code | Fetched from backend at runtime |
| Passwords | Plain text or Base64 | Never stored (use tokens) |
| Tokens | Long-lived, no rotation | Short-lived with auto-rotation |
| Logging | Credentials in logs | Sanitized or no logging |
| Backup | Included in backups | Excluded from backups |
| Network | HTTP, no pinning | HTTPS with certificate pinning |
---
Key Principle: Assume the app binary and device storage are compromised. Design accordingly. Part of OWASP Mobile Top 10 - Educational Repository