11package com.tailoredapps.biometricsample
22
3+ import android.annotation.TargetApi
4+ import android.os.Build
35import android.os.Bundle
6+ import android.security.keystore.KeyGenParameterSpec
7+ import android.security.keystore.KeyProperties
48import android.support.v7.app.AppCompatActivity
59import android.util.Log
610import android.widget.Button
@@ -9,26 +13,50 @@ import com.tailoredapps.biometricauth.BiometricAuth
913import com.tailoredapps.biometricauth.BiometricAuthenticationCancelledException
1014import com.tailoredapps.biometricauth.BiometricAuthenticationException
1115import io.reactivex.android.schedulers.AndroidSchedulers
16+ import java.io.IOException
17+ import java.security.*
18+ import java.security.cert.CertificateException
19+ import javax.crypto.Cipher
20+ import javax.crypto.KeyGenerator
21+ import javax.crypto.NoSuchPaddingException
22+ import javax.crypto.SecretKey
23+
1224
1325class MainActivity : AppCompatActivity () {
1426
1527 companion object {
1628 private const val LOG_TAG = " MainActivity"
1729 }
1830
19- private val biometricAuth: BiometricAuth by lazy { BiometricAuth .create(this ) }
31+ private val cryptoManager: CryptoManager by lazy {
32+ CryptoManager ()
33+ }
34+
35+ private val biometricAuth: BiometricAuth by lazy {
36+ BiometricAuth .create(this )
37+ }
2038
21- private val button: Button by lazy { findViewById<Button >(R .id.button) }
39+ private val btnAuthenticate: Button by lazy { findViewById<Button >(R .id.btn_authenticate) }
40+ private val btnAuthenticateWithCrypto: Button by lazy { findViewById<Button >(R .id.btn_authenticate_with_crypto) }
2241
2342 override fun onCreate (savedInstanceState : Bundle ? ) {
2443 super .onCreate(savedInstanceState)
2544 setContentView(R .layout.activity_main)
2645
27- button .setOnClickListener {
46+ btnAuthenticate .setOnClickListener {
2847 testAuthenticate()
2948 }
49+
50+ btnAuthenticateWithCrypto.setOnClickListener {
51+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .M ) {
52+ Toast .makeText(this , " KeyGenerator only available on >= Marshmallow" , Toast .LENGTH_SHORT ).show()
53+ } else {
54+ testAuthenticateWithCrypto()
55+ }
56+ }
3057 }
3158
59+
3260 private fun testAuthenticate () {
3361 if (biometricAuth.hasFingerprintHardware.not ()) {
3462 Toast .makeText(this , " Devices provides no fingerprint hardware" , Toast .LENGTH_SHORT ).show()
@@ -65,4 +93,114 @@ class MainActivity : AppCompatActivity() {
6593 )
6694 }
6795 }
96+
97+
98+ @TargetApi(Build .VERSION_CODES .M )
99+ private fun testAuthenticateWithCrypto () {
100+ if (biometricAuth.hasFingerprintHardware.not ()) {
101+ Toast .makeText(this , " Devices provides no fingerprint hardware" , Toast .LENGTH_SHORT ).show()
102+ } else if (biometricAuth.hasFingerprintsEnrolled.not ()) {
103+ Toast .makeText(this , " No fingerprints enrolled" , Toast .LENGTH_SHORT ).show()
104+ } else {
105+ biometricAuth.authenticate(
106+ cryptoObject = BiometricAuth .Crypto (cryptoManager.cipher),
107+ title = " Please authenticate" ,
108+ subtitle = " Using 'Awesome Feature' requires your authentication." ,
109+ description = " 'Awesome Feature' exposes data private to you, which is why you need to authenticate." ,
110+ negativeButtonText = " Cancel" ,
111+ prompt = " Touch the fingerprint sensor" ,
112+ notRecognizedErrorText = " Not recognized"
113+ )
114+ .observeOn(AndroidSchedulers .mainThread())
115+ .subscribe(
116+ {
117+ Toast .makeText(this , " Success!" , Toast .LENGTH_SHORT ).show()
118+ Log .d(LOG_TAG , " onSuccess()" )
119+ },
120+ { throwable ->
121+ when (throwable) {
122+ is BiometricAuthenticationException -> {
123+ Toast .makeText(this , " Error: ${throwable.errorString} " , Toast .LENGTH_SHORT ).show()
124+ Log .e(LOG_TAG , " BiometricAuthenticationException(${throwable.errorMessageId} , '${throwable.errorString} ')" , throwable)
125+ }
126+ is BiometricAuthenticationCancelledException -> {
127+ Toast .makeText(this , " Cancelled" , Toast .LENGTH_SHORT ).show()
128+ Log .d(LOG_TAG , " onError(BiometricAuthenticationCancelledException)" )
129+ }
130+ else -> Log .e(LOG_TAG , " onError()" , throwable)
131+ }
132+ }
133+ )
134+ }
135+ }
136+
137+
138+ private class CryptoManager {
139+
140+ companion object {
141+ private const val KEY_NAME = " test-key"
142+ }
143+
144+ private var keyStore: KeyStore ? = null
145+ private var keyGenerator: KeyGenerator ? = null
146+
147+
148+ val cipher: Cipher by lazy {
149+ generateKey()
150+ initCipher()
151+ }
152+
153+
154+ @TargetApi(Build .VERSION_CODES .M )
155+ private fun generateKey () {
156+ try {
157+ keyStore = KeyStore .getInstance(" AndroidKeyStore" ).also { keyStore ->
158+ keyStore.load(null )
159+ }
160+
161+ keyGenerator = KeyGenerator .getInstance(KeyProperties .KEY_ALGORITHM_AES , " AndroidKeyStore" ).also { keyGenerator ->
162+ keyGenerator?.init (KeyGenParameterSpec .Builder (KEY_NAME , KeyProperties .PURPOSE_ENCRYPT or KeyProperties .PURPOSE_DECRYPT )
163+ .setBlockModes(KeyProperties .BLOCK_MODE_CBC )
164+ .setUserAuthenticationRequired(true )
165+ .setEncryptionPaddings(KeyProperties .ENCRYPTION_PADDING_PKCS7 )
166+ .build())
167+
168+ keyGenerator?.generateKey()
169+ }
170+ } catch (e: KeyStoreException ) {
171+ e.printStackTrace()
172+ } catch (e: NoSuchAlgorithmException ) {
173+ e.printStackTrace()
174+ } catch (e: NoSuchProviderException ) {
175+ e.printStackTrace()
176+ } catch (e: InvalidAlgorithmParameterException ) {
177+ e.printStackTrace()
178+ } catch (e: CertificateException ) {
179+ e.printStackTrace()
180+ } catch (e: IOException ) {
181+ e.printStackTrace()
182+ }
183+ }
184+
185+
186+ @TargetApi(Build .VERSION_CODES .M )
187+ private fun initCipher (): Cipher {
188+ try {
189+ val cipher = Cipher .getInstance(
190+ KeyProperties .KEY_ALGORITHM_AES + " /"
191+ + KeyProperties .BLOCK_MODE_CBC + " /"
192+ + KeyProperties .ENCRYPTION_PADDING_PKCS7 )
193+
194+ keyStore!! .load(null )
195+ val key = keyStore!! .getKey(KEY_NAME , null ) as SecretKey
196+ cipher.init (Cipher .ENCRYPT_MODE , key)
197+ return cipher
198+ } catch (e: NoSuchAlgorithmException ) {
199+ throw RuntimeException (" Failed to init cipher" , e)
200+ } catch (e: NoSuchPaddingException ) {
201+ throw RuntimeException (" Failed to init cipher" , e)
202+ }
203+ }
204+ }
205+
68206}
0 commit comments