Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sticky Authentication with Fingerprints

Sticky Authentication with Fingerprints

A quick talk exploring the new Fingerprint authentication API introduced in Android P.

Presented at droidcon Berlin 2018 (droidcon.de)

Segun Famisa

June 27, 2018
Tweet

More Decks by Segun Famisa

Other Decks in Programming

Transcript

  1. Sticky Authentication
    with Fingerprints

    View Slide

  2. segunfamisa
    segunfamisa.com

    View Slide

  3. Some background story

    View Slide

  4. First introduced to Android in M

    View Slide

  5. Why should I care?

    View Slide

  6. ● Easy and quick
    Why should I care?

    View Slide

  7. Why should I care?
    ● Easy and quick
    ● Safe from peeking while they type in their password

    View Slide

  8. Why should I care?
    https://findbiometrics.com/acuity-biometric-smartphones-are-officially-mainstream-302125/

    View Slide

  9. Why should I care?
    https://www.statista.com/statistics/522058/global-smartphone-fingerprint-penetration/

    View Slide

  10. With the new feature came an API
    FingerprintManager
    Allows you to listen for fingerprint
    authentication events

    View Slide

  11. With FingerprintManager came design
    guidelines

    View Slide

  12. ...but still,inconsistent UI across apps

    View Slide

  13. The new FingerprintDialog API

    View Slide

  14. The new FingerprintDialog API

    View Slide

  15. The new FingerprintDialog API
    BiometricPrompt

    View Slide

  16. BiometricPrompt API
    ● Known as FingerprintDialog API in early versions of P
    Preview (as at the time of submission of this talk)

    View Slide

  17. BiometricPrompt API
    ● Known as FingerprintDialog API in early versions of P
    Preview (as at the time of submission of this talk)
    ● Provide consistent and easy API for developers

    View Slide

  18. BiometricPrompt API
    ● Known as FingerprintDialog API in early versions of P
    Preview (as at the time of submission of this talk)
    ● Provide consistent and easy API for developers
    ● Provide consistent system UI that is sensor-agnostic

    View Slide

  19. Before going further, let’s
    understand some terms

    View Slide

  20. Symmetric vs Asymmetric Keystore
    Symmetric: aka “secret key” - same key for
    encryption and decryption
    Asymmetric: aka “public key” - different keys
    for encryption and decryption
    https://www.cs.utexas.edu/users/byoung/cs361/lecture44.pdf

    View Slide

  21. Android Keystore
    Hardware backed keystore that lets you store
    cryptographic keys in a container and makes it
    more difficult to extract from the device.

    View Slide

  22. Cipher
    Loosely speaking - it’s the algorithm used to
    encrypt/decrypt data
    Usually a set of operations or transformations
    to be performed on inputs

    View Slide

  23. KeyGenerator
    Generates symmetric (secret) key based on
    different algorithms.
    E.g AES, HMAC-SHA

    View Slide

  24. Authenticating with
    BiometricPrompt API

    View Slide

  25. ...


    Previously, requesting permission looked like:
    0. Request permission

    View Slide

  26. 0. Request permission
    ...


    Previously, requesting permission looked like:
    ...


    New permission:

    View Slide

  27. 1. Check device requirements
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // Proceed with fingerprint authentication
    } else {
    // Fallback to password or manual authentication
    }

    View Slide

  28. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}

    View Slide

  29. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}
    // Show error - can't proceed without fingerprint
    if (!fingerprintManager.hasEnrolledFingerprints()) {...}

    View Slide

  30. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}
    // Show error - can't proceed without fingerprint
    if (!fingerprintManager.hasEnrolledFingerprints()) {...}

    View Slide

  31. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}
    // Show error - can't proceed without fingerprint
    if (!fingerprintManager.hasEnrolledFingerprints()) {...}
    // Proceed with auth

    View Slide

  32. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}
    // Show error - can't proceed without fingerprint
    if (!fingerprintManager.hasEnrolledFingerprints()) {...}
    // Proceed with auth
    Deprecated

    View Slide

  33. Check other criteria
    val fingerprintManager = getSystemService(FingerprintManager::class.java)
    val keyguardManager = getSystemService(KeyguardManager::class.java)
    // Show error - can't proceed without sensor
    if (!fingerprintManager.isHardwareDetected) {...}
    // Show error - can't proceed without fingerprint
    if (!fingerprintManager.hasEnrolledFingerprints()) {...}
    // Proceed with auth
    New API offers callbacks to handle errors and
    unsatisfied criteria

    View Slide

  34. 2. Create & initialize a cipher

    View Slide

  35. Create key store from the AndroidKeyStore
    // Create Keystore
    try {
    keyStore = KeyStore.getInstance("AndroidKeyStore")
    } catch (e: KeyStoreException) {
    throw RuntimeException("Failed to get an instance of KeyStore", e)
    }
    2. Create & initialize a cipher

    View Slide

  36. Create KeyGenerator that will be used to create the symmetric key
    // Create Keystore
    try {
    keyStore = KeyStore.getInstance("AndroidKeyStore")
    } catch (e: KeyStoreException) {
    throw RuntimeException("Failed to get an instance of KeyStore", e)
    }
    // Create KeyGenerator (this will be used to create the key)
    try {
    keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM_AES, "AndroidKeyStore")
    } catch (e: Exception) {
    when (e) {
    is NoSuchAlgorithmException,
    is NoSuchProviderException -> throw RuntimeException(...)
    else -> throw e
    }
    2. Create & initialize a cipher

    View Slide

  37. Use KeyGenerator to generate keys
    try {
    keyStore.load(null)
    val keyProperties = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    val builder = KeyGenParameterSpec.Builder(“default_key”, keyProperties)
    .setBlockModes(BLOCK_MODE_CBC)
    .setUserAuthenticationRequired(true)
    .setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7)
    .setInvalidatedByBiometricEnrollment(true)
    // generate keys
    keyGenerator.init(builder.build())
    keyGenerator.generateKey()
    } catch (e: Exception) {
    // handle exceptions
    }

    View Slide

  38. Use KeyGenerator to generate keys
    try {
    keyStore.load(null)
    val keyProperties = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    val builder = KeyGenParameterSpec.Builder(“default_key”, keyProperties)
    .setBlockModes(BLOCK_MODE_CBC)
    .setUserAuthenticationRequired(true)
    .setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7)
    .setInvalidatedByBiometricEnrollment(true)
    // generate keys
    keyGenerator.init(builder.build())
    keyGenerator.generateKey()
    } catch (e: Exception) {
    // handle exceptions
    }
    Key is authorized to be used only when user
    has been authenticated (e.g unlocked phone)

    View Slide

  39. Key is invalidated when a new fingerprint is
    added
    Use KeyGenerator to generate keys
    try {
    keyStore.load(null)
    val keyProperties = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    val builder = KeyGenParameterSpec.Builder(“default_key”, keyProperties)
    .setBlockModes(BLOCK_MODE_CBC)
    .setUserAuthenticationRequired(true)
    .setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7)
    .setInvalidatedByBiometricEnrollment(true)
    // generate keys
    keyGenerator.init(builder.build())
    keyGenerator.generateKey()
    } catch (e: Exception) {
    // handle exceptions
    }

    View Slide

  40. Create a cipher
    // Create a cipher
    val cipher : Cipher
    try {
    val cipherString = "$KEY_ALGORITHM_AES/$BLOCK_MODE_CBC/$ENCRYPTION_PADDING_PKCS7"
    cipher = Cipher.getInstance(cipherString)
    } catch (e: Exception) {
    when (e) {
    is NoSuchAlgorithmException,
    is NoSuchPaddingException ->
    throw RuntimeException("Failed to get an instance of Cipher", e)
    else -> throw e
    }
    }

    View Slide

  41. Initialize the cipher
    fun initCipher(cipher: Cipher): Boolean {
    try {
    keyStore.load(null)
    val secretKey = keyStore.getKey("key_name", null) as SecretKey
    // init
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
    return true
    } catch (e: Exception) {
    when (e) {
    is KeyPermanentlyInvalidatedException -> return false
    ...
    else -> throw e
    }
    }
    }

    View Slide

  42. Initialize the cipher
    fun initCipher(cipher: Cipher): Boolean {
    try {
    keyStore.load(null)
    val secretKey = keyStore.getKey("default_key", null) as SecretKey
    // init
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
    return true
    } catch (e: Exception) {
    when (e) {
    is KeyPermanentlyInvalidatedException -> return false
    ...
    else -> throw e
    }
    }
    }
    Unrecoverably invalidated due to - change in
    security settings of the phone, or since we
    configured setInvalidatedByBiometricEnrollment to
    enrollment of new fingerprint

    View Slide

  43. val cancellationSignal = new CancellationSignal();
    val cryptoObject = FingerprintManager.CryptoObject(cipher)
    // Warm up the sensor and start listening for authentication
    val manager = getSystemService(FingerprintManager::class.java)
    manager.authenticate(cryptoObject, cancellationSignal, 0, callback, null);
    3. Listen for fingerprint auth events
    Authenticating with the old FingerprintManager

    View Slide

  44. val cancellationSignal = new CancellationSignal();
    val cryptoObject = FingerprintManager.CryptoObject(cipher)
    // Warm up the sensor and start listening for authentication
    val manager = getSystemService(FingerprintManager::class.java)
    manager.authenticate(cryptoObject, cancellationSignal, 0, callback, null);
    3. Listen for fingerprint auth events
    Authenticating with the old FingerprintManager
    Deprecated

    View Slide

  45. Authenticating with the new APIs
    // Create builder
    val builder = BiometricPrompt.Builder(this)
    .setTitle("Confirm purchase")
    .setSubtitle("Currywurst - €5,50")
    .setDescription("Touch the fingerprint sensor....")
    .setNegativeButton("Cancel", mainExecutor, object : DialogInterface.OnClickListener {
    override fun onClick(dialog: DialogInterface?, p1: Int) {
    // handle cancel action
    }
    })
    .build()
    3. Listen for fingerprint auth events

    View Slide

  46. Authenticating with the new APIs
    // Create builder
    val builder = BiometricPrompt.Builder(this)
    .setTitle("Confirm purchase")
    .setSubtitle("Currywurst - €5,50")
    .setDescription("Touch the fingerprint sensor....")
    .setNegativeButton("Cancel", mainExecutor, object : DialogInterface.OnClickListener {
    override fun onClick(dialog: DialogInterface?, p1: Int) {
    // handle cancel action
    }
    })
    .build()
    // Authenticate and listen for events
    builder.authenticate(BiometricPrompt.CryptoObject(cipher), cancellationSignal, mainExecutor, callback)

    View Slide

  47. BiometricPrompt in practice
    Negative button
    Description text
    Subtitle
    Title

    View Slide

  48. Handling results

    View Slide

  49. Handling results
    val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
    // handle error
    }
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    // handle result
    }
    override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
    // handle help message if necessary
    }
    override fun onAuthenticationFailed() {
    // fingerprint recognized but doesn't match
    }
    }

    View Slide

  50. Handling results
    val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
    // handle error
    }
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    // handle result
    }
    override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
    // handle help message if necessary
    }
    override fun onAuthenticationFailed() {
    // fingerprint recognized but doesn't match
    }
    }

    View Slide

  51. Handling results - error codes
    BIOMETRIC_ERROR_CANCELED
    BIOMETRIC_ERROR_HW_NOT_PRESENT
    BIOMETRIC_ERROR_HW_UNAVAILABLE
    BIOMETRIC_ERROR_LOCKOUT
    BIOMETRIC_ERROR_LOCKOUT_PERMANENT
    BIOMETRIC_ERROR_NO_BIOMETRICS
    BIOMETRIC_ERROR_NO_SPACE
    BIOMETRIC_ERROR_TIMEOUT
    BIOMETRIC_ERROR_UNABLE_TO_PROCESS
    BIOMETRIC_ERROR_USER_CANCELED
    BIOMETRIC_ERROR_VENDOR

    View Slide

  52. Handling results
    val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
    // handle error
    }
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    // handle result
    }
    override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
    // handle help message if necessary
    }
    override fun onAuthenticationFailed() {
    // fingerprint recognized but doesn't match
    }
    }

    View Slide

  53. Handling results
    val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
    // handle error
    }
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    // handle result
    }
    override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
    // handle help message if necessary
    }
    override fun onAuthenticationFailed() {
    // fingerprint recognized but doesn't match
    }
    }

    View Slide

  54. Handling results
    val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
    // handle error
    }
    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
    // handle result
    }
    override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
    // handle help message if necessary
    }
    override fun onAuthenticationFailed() {
    // fingerprint recognized but doesn't match
    }
    }

    View Slide

  55. Handling results
    builder.authenticate()
    onAuthenticationHelp() onAuthenticationFailed() Recoverable
    events

    View Slide

  56. onAuthenticationError() onAuthenticationSucceeded()
    Handling results
    builder.authenticate()
    onAuthenticationHelp() onAuthenticationFailed()
    Non-recoverable
    (terminal)
    events
    Recoverable
    events

    View Slide

  57. Last words

    View Slide

  58. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)

    View Slide

  59. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)
    ● Subtle enrollment - there should be a place in your
    settings where users can enroll to use fingerprint
    authentication in your app

    View Slide

  60. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)
    ● Subtle enrollment - there should be a place in your
    settings where users can enroll to use fingerprint
    authentication in your app
    ● Provide as much info as possible to your users to help
    them

    View Slide

  61. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)
    ● Subtle enrollment - there should be a place in your
    settings where users can enroll to use fingerprint
    authentication in your app
    ● Provide as much info as possible to your users to help
    them
    ● Ensure good API practices with signed requests

    View Slide

  62. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)
    ● Subtle enrollment - there should be a place in your
    settings where users can enroll to use fingerprint
    authentication in your app
    ● Provide as much info as possible to your users to help
    them
    ● Ensure good API practices with signed requests
    ● The APIs may not be final yet

    View Slide

  63. Last words...
    ● Provide alternative ways to authenticate (password, OTP,
    and other ways)
    ● Subtle enrollment - there should be a place in your
    settings where users can enroll to use fingerprint
    authentication in your app
    ● Provide as much info as possible to your users to help
    them
    ● Ensure good API practices with signed requests
    ● The APIs may not be final yet
    ● No Compat APIs yet

    View Slide

  64. Thank you!
    segunfamisa

    View Slide