Secure Input for PIN Entry in Financial Android Apps

Table of Contents

Imagine a user is logging into their banking app while grabbing coffee in a crowded cafe. They quickly type in their PIN, maybe not noticing someone glancing over their shoulder — or that a vulnerability in the app could put their data at risk. That’s exactly why Secure Input for PIN Entry is so important, especially in financial apps where a PIN is more than just a few numbers; it’s a gateway to sensitive information.

In this guide, we’ll build a secure PIN entry system for Android. I’ll walk you through the Kotlin code step-by-step, along with key security tips. So stay tuned..!

Why is Secure PIN Entry So Important?

In financial apps, PINs are often the key to user authentication, making secure PIN entry essential. Here’s why it matters:

  • Prevent Shoulder Surfing: Stop others from sneaking a peek at the PIN in crowded or public spaces.
  • Block Unauthorized Access: Strengthen PIN handling to eliminate weak security points.
  • Protect Against Data Leaks: Safeguard sensitive data by avoiding insecure storage or logging practices.

Best Practices for Secure Input for PIN Entry

  1. Use a Custom View for Input Masking
    • Default Android input views may not be secure enough, as they’re designed for generic inputs. Creating a custom view for PIN entry adds control over how data is handled and stored.
  2. Minimize PIN Storage Duration
    • Store PIN data in memory only as long as needed. Clear it from memory once used.
  3. Use Secure Storage for Sensitive Data
    • Don’t store the PIN itself; instead, use tokens or session IDs post-authentication.
  4. Disable Screenshots
    • Prevent screenshots and screen recording to avoid capturing the PIN on-screen.
  5. Avoid Logging Sensitive Data
    • Ensure that the PIN isn’t logged or displayed in the logcat.
  6. Use Obfuscation Techniques
    • Obfuscate sensitive parts of the codebase to make reverse engineering harder.

Implementing Secure PIN Entry in Kotlin

Alright, without wasting time, let’s jump into the Kotlin code and start building our secure PIN entry feature.

Disable Screenshots

Preventing screenshots and screen recording ensures that no sensitive data gets captured visually.

In your Activity class, disable screenshots by adding this line in the onCreate method.

Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
    setContentView(R.layout.activity_pin_entry)
}

The FLAG_SECURE flag prevents the app from taking screenshots or recording the screen when the PIN entry screen is open.

Create a Custom PIN Entry View

A custom PIN entry view allows us to control how each input character behaves and ensures that data is stored only in memory for the duration needed.

Create a SecurePinEntryView class that extends LinearLayout.

Kotlin
class SecurePinEntryView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private val pinDigits = mutableListOf<EditText>() // Holds the EditTexts for each PIN digit
    private val maxPinLength = 4 // Number of PIN digits

    init {
        orientation = HORIZONTAL
        setupPinFields()
    }

    private fun setupPinFields() {
        for (i in 0 until maxPinLength) {
            val digitField = createDigitField()
            pinDigits.add(digitField)
            addView(digitField)
        }
    }

    private fun createDigitField(): EditText {
        return EditText(context).apply {
            inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
            setBackgroundColor(Color.TRANSPARENT)
            filters = arrayOf(InputFilter.LengthFilter(1))
            isCursorVisible = false
            textAlignment = View.TEXT_ALIGNMENT_CENTER
            layoutParams = LayoutParams(0, LayoutParams.MATCH_PARENT, 1f) // Distribute space evenly

            setOnFocusChangeListener { _, hasFocus ->
                if (hasFocus && text.isEmpty()) {
                    this.text.clear() // Clear text only if empty to avoid accidental deletion
                }
            }
        }
    }

    // Collects the PIN entered by the user
    fun getPin(): String {
        return pinDigits.joinToString("") { it.text.toString() }
    }

    // Clears the entered PIN
    fun clearPin() {
        pinDigits.forEach { it.text.clear() }
    }
}

Here,

  • Orientation & Style: We set the orientation to HORIZONTAL to align the PIN digits in a row.createDigitField(): Creates a customized EditText for each PIN digit.
  • InputType.TYPE_NUMBER_VARIATION_PASSWORD hides the PIN visually by displaying dots.
  • Each digit field is limited to one character using InputFilter.LengthFilter(1).
  • isCursorVisible is set to false to remove the blinking cursor, which makes it harder for onlookers to see which digit is currently being entered.

Handle PIN Submission

Once the user enters the PIN, we’ll verify it securely. Here’s an example of how to collect and handle the PIN securely.

Kotlin
val securePinEntryView = findViewById<SecurePinEntryView>(R.id.securePinEntryView)
val submitButton = findViewById<Button>(R.id.submitButton)

submitButton.setOnClickListener {
    val enteredPin = securePinEntryView.getPin()
    
    // Verify the PIN here or pass it to the next step
    if (verifyPin(enteredPin)) {
        // Handle successful PIN entry
        Toast.makeText(this, "PIN verified!", Toast.LENGTH_SHORT).show()
    } else {
        // Clear PIN for incorrect attempts
        securePinEntryView.clearPin()
        Toast.makeText(this, "Incorrect PIN. Try again.", Toast.LENGTH_SHORT).show()
    }
}

private fun verifyPin(pin: String): Boolean {
    // Replace this with actual PIN verification logic
    return pin == "1234" // Example PIN for demonstration
}

In this code,

  • getPin(): Collects the entered PIN from the custom view.
  • verifyPin(): Checks if the entered PIN matches the stored PIN. In a real application, use a secure method to validate the PIN.

Clear PIN on Incorrect Attempts

Clearing the PIN on incorrect attempts prevents attackers from guessing and observing patterns.

Kotlin
private fun handleIncorrectPin() {
    securePinEntryView.clearPin() // Clears the entered PIN
    Toast.makeText(this, "Incorrect PIN. Try again.", Toast.LENGTH_SHORT).show()
}

Hash, Encode, Encrypt, and Store

Always hash sensitive data and use Base64 encoding before encrypting and storing it.

Since we haven’t added any PIN security logic yet, let’s take the next step and organize it into its own class, following the Single Responsibility Principle. This way, we’ll keep things clean and put the logic in the SecurePinManager class.

Kotlin
import android.content.Context
import android.text.InputType
import android.widget.EditText
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import java.security.MessageDigest
import java.util.*

class SecurePinManager(context: Context) {
    private val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
    private val encryptedPrefs = EncryptedSharedPreferences.create(
        "secure_prefs",
        masterKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    fun setupPinInputField(editText: EditText) {
        editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
    }

    fun savePin(pin: String) {
        val hashedPin = hashPin(pin) // Hash the PIN before saving
        encryptedPrefs.edit().putString("user_pin", hashedPin).apply()
    }

    fun verifyPin(inputPin: String): Boolean {
        val storedHashedPin = encryptedPrefs.getString("user_pin", null)
        val inputHashedPin = hashPin(inputPin) // Hash the input before comparison
        return storedHashedPin == inputHashedPin
    }

    // Hashes the PIN using SHA-256
    private fun hashPin(pin: String): String {
        val digest = MessageDigest.getInstance("SHA-256")
        val hashedBytes = digest.digest(pin.toByteArray())
        return Base64.getEncoder().encodeToString(hashedBytes) // Encode the hashed bytes in Base64
    }
}
  • PIN Hashing: The PIN is now hashed using SHA-256 before saving and comparing. This adds a layer of security by ensuring the raw PIN is never stored.
  • Base64 Encoding: The hashed PIN is encoded using Base64 to store it as a string in EncryptedSharedPreferences.

Prevent Sensitive Data from Being Logged

Avoid logging the entered PIN or sensitive information.

Kotlin
// BAD: Never log sensitive data
Log.d("PinEntry", "Entered PIN: $enteredPin") 

// GOOD: No sensitive data in logs
Log.d("PinEntry", "PIN entry attempt")

Additional Key Security Tips

  • Limit Accessibility During PIN Entry: Restrict accessibility features like screen readers or magnification during PIN entry to prevent accidental exposure of sensitive information.
  • Enable Biometric Authentication: Consider using biometric authentication (e.g., fingerprint, face recognition) for an extra layer of security, alongside the PIN.
  • Encrypt Sensitive Data: While PINs themselves shouldn’t be stored directly, always hash sensitive data and use Base64 encoding before encrypting and storing it.
  • Regularly Clear Sensitive Data: If you’re using data holders like LiveData or other similar components, ensure that sensitive data is cleared when it is no longer needed. Properly manage the lifecycle of such data to avoid unintentional retention in memory or storage.

Conclusion

Building a secure PIN entry system isn’t just about protecting data—it’s about earning user trust and safeguarding their sensitive information. With Kotlin’s secure handling features, you can create a seamless, safe experience for your users.

By following these steps, you’re not only securing their PINs but also ensuring their data is treated with the highest level of care in your financial app. Let’s keep security at the forefront and provide users the peace of mind they deserve..!

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!