As mobile applications become increasingly central to both personal and business activities, they also become more attractive targets for attackers. From financial apps handling sensitive transactions to social platforms storing personal data, mobile applications often process valuable information that needs protection. This article outlines essential security best practices that developers should implement to safeguard their applications and user data.

The Mobile App Security Landscape

Mobile applications face a unique set of security challenges compared to traditional web applications:

According to recent reports, 63% of application security incidents originate at the application layer rather than the network level, highlighting the importance of implementing robust security measures within the application itself.

1. Secure Data Storage

Mobile applications often need to store sensitive data locally. Protecting this data is essential to prevent unauthorized access if a device is compromised.

Best Practices:

Implementation Example: For Android, you can use the EncryptedSharedPreferences class to store sensitive key-value pairs with encryption:


MasterKey masterKey = new MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// Store encrypted data
sharedPreferences.edit().putString("api_token", "secret_token_value").apply();
            

2. Secure Network Communication

Data transmitted between mobile apps and backend servers is vulnerable to interception. Implementing proper encryption and verification is crucial.

Best Practices:

Implementation Example: For iOS, you can implement certificate pinning using URLSession:


class CertificatePinningDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
                   completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        guard let serverTrust = challenge.protectionSpace.serverTrust,
              challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              challenge.protectionSpace.host == "api.yourdomain.com" else {
                  completionHandler(.cancelAuthenticationChallenge, nil)
                  return
              }

        // Get certificate data from your bundled certificate
        guard let certificatePath = Bundle.main.path(forResource: "your-certificate", ofType: "cer"),
              let certificateData = try? Data(contentsOf: URL(fileURLWithPath: certificatePath)),
              let certificate = SecCertificateCreateWithData(nil, certificateData as CFData) else {
                  completionHandler(.cancelAuthenticationChallenge, nil)
                  return
              }

        // Set your certificate as the only trusted one
        SecTrustSetAnchorCertificates(serverTrust, [certificate] as CFArray)

        // Evaluate the trust
        var result: SecTrustResultType = .invalid
        SecTrustEvaluate(serverTrust, &result)

        if result == .unspecified || result == .proceed {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}
            

3. Authentication and Authorization

Properly verifying user identity and controlling access to resources is fundamental to mobile app security.

Best Practices:

Implementation Example: Implementing biometric authentication on Android:


BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
    .setTitle("Biometric Authentication")
    .setSubtitle("Log in using your biometric credential")
    .setNegativeButtonText("Cancel")
    .build();

BiometricPrompt biometricPrompt = new BiometricPrompt(this,
    executor, new BiometricPrompt.AuthenticationCallback() {
        @Override
        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
            super.onAuthenticationSucceeded(result);
            // Authentication succeeded, proceed with secure operation
            proceedToSecureArea();
        }

        @Override
        public void onAuthenticationFailed() {
            super.onAuthenticationFailed();
            // Authentication failed
            showAuthenticationFailedMessage();
        }
    });

biometricPrompt.authenticate(promptInfo);
            

4. Code Protection

Mobile applications can be decompiled and analyzed, potentially exposing security vulnerabilities or sensitive information embedded in the code.

Best Practices:

Implementation Example: Detecting jailbroken iOS devices:


func isDeviceJailbroken() -> Bool {
    #if targetEnvironment(simulator)
        return false
    #else
        // Check for common jailbreak files
        let jailbreakPaths = [
            "/Applications/Cydia.app",
            "/Library/MobileSubstrate/MobileSubstrate.dylib",
            "/bin/bash",
            "/usr/sbin/sshd",
            "/etc/apt",
            "/usr/bin/ssh"
        ]

        for path in jailbreakPaths {
            if FileManager.default.fileExists(atPath: path) {
                return true
            }
        }

        // Check if app can write to system directories
        let path = "/private/jailbreak.txt"
        do {
            try "jailbreak test".write(toFile: path, atomically: true, encoding: .utf8)
            try FileManager.default.removeItem(atPath: path)
            return true
        } catch {
            return false
        }
    #endif
}
            

5. Secure Data Transmission

Beyond using HTTPS, additional measures can protect data being sent to and from your application.

Best Practices:

Implementation Example: Encrypting data before transmission in JavaScript:


async function encryptData(publicKeyPem, data) {
    // Import the RSA public key
    const publicKey = await window.crypto.subtle.importKey(
        "spki",
        pemToArrayBuffer(publicKeyPem),
        {
            name: "RSA-OAEP",
            hash: { name: "SHA-256" },
        },
        false,
        ["encrypt"]
    );

    // Generate a random AES key
    const aesKey = await window.crypto.subtle.generateKey(
        {
            name: "AES-GCM",
            length: 256,
        },
        true,
        ["encrypt", "decrypt"]
    );

    // Encrypt the data with the AES key
    const iv = window.crypto.getRandomValues(new Uint8Array(12));
    const encryptedData = await window.crypto.subtle.encrypt(
        {
            name: "AES-GCM",
            iv: iv,
        },
        aesKey,
        new TextEncoder().encode(JSON.stringify(data))
    );

    // Encrypt the AES key with the RSA public key
    const exportedAesKey = await window.crypto.subtle.exportKey("raw", aesKey);
    const encryptedKey = await window.crypto.subtle.encrypt(
        {
            name: "RSA-OAEP",
        },
        publicKey,
        exportedAesKey
    );

    // Return the encrypted package
    return {
        encryptedData: arrayBufferToBase64(encryptedData),
        encryptedKey: arrayBufferToBase64(encryptedKey),
        iv: arrayBufferToBase64(iv)
    };
}
            

6. Input Validation and Output Encoding

Proper validation and encoding prevent many common security vulnerabilities such as injection attacks.

Best Practices:

Implementation Example: Using parameterized queries with Room database in Android:


@Dao
public interface UserDao {
    @Query("SELECT * FROM users WHERE username = :username")
    User getUserByUsername(String username);

    @Insert
    void insertUser(User user);
}

// Usage
userDao.getUserByUsername(usernameInput); // Safe from SQL injection
            

7. Regular Security Testing

Proactive security testing helps identify and address vulnerabilities before they can be exploited.

Best Practices:

Recommended Tools:

8. Secure Deployment and Updates

Security doesn't end with development; secure deployment and update processes are essential.

Best Practices:

9. Privacy Considerations

Security and privacy are closely related, and privacy protections are increasingly required by regulations like GDPR and CCPA.

Best Practices:

10. Incident Response Planning

Despite best efforts, security incidents can occur. Being prepared to respond is essential.

Best Practices:

Conclusion

Mobile app security is not a one-time task but an ongoing process that requires attention throughout the development lifecycle and beyond. By implementing these best practices, developers can significantly reduce the risk of security breaches and protect both their users and their reputation.

At PinkSale, we integrate security into every stage of the mobile app development process. Our development teams work closely with security specialists to ensure that all applications we build implement these best practices and maintain the highest security standards.

Remember that the mobile security landscape is constantly evolving, with new threats emerging regularly. Staying informed about the latest security developments and regularly updating your security measures is essential for maintaining a secure mobile application over time.