Next Generation Login - Fingerprint Authentication

Sejal Baraiya
IndiaNIC
Published in
7 min readApr 22, 2017

The Fingerprint Authentication was already introduced in a Lollipop version (Android 5.0). So, it is not a new term for the Android app developers, but in the Lollipop version, we can do only swipe left, right, down and up types of gesture to authenticate your fingerprint. So, now have login with fingerprint.

Now, we can see a new fingerprint gesture-Single Tap in the smartphones with a Marshmallow version. Other Gestures are also available now including Double Tap, Fling, Scroll, Down, Up, etc. and each of these gestures can correspond to one of the following actions:

  • Back button
  • Home button
  • Recent apps
  • Sleep (root required)
  • Power button menu
  • Scroll down (root required)
  • Scroll up (root required)
  • Open notification panel
  • Toggle notification panel
  • Open quick settings
  • Play/pause song
  • Next song
  • Previous song
  • Launch an app
  • Flashlight
  • Toggle ringer mode
  • Toggle split-screen (in Nougat)

Okay. Now comes the real part: First of all, we need to know how to store the Fingerprint and how we can authenticate that stored fingerprint. The Fingerprint authentication requires that the app requests the USE_FINGERPRINT permission within the project manifest file.

<uses-permission android:name=”android.permission.USE_FINGERPRINT” />

Accessing the Keyguard and Fingerprint Manager Services

Now, we are going to initialize the functions that are used for the fingerprint service and for checking whether or not the fingerprint is recorded along with the lock screen option’s fingerprint capability.

keyguardManager =
(KeyguardManager) getSystemService(KEYGUARD_SERVICE);
fingerprintManager =
(FingerprintManager) getSystemService(FINGERPRINT_SERVICE);

if (!keyguardManager.isKeyguardSecure()) {
Toast.makeText(this, "Lock screen security not enabled in Settings",Toast.LENGTH_LONG).show();
return;
}
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Fingerprint authentication permission not enabled",Toast.LENGTH_LONG).show();
return;
}

if (!fingerprintManager.hasEnrolledFingerprints()) {
// This happens when no fingerprints are registered.
Toast.makeText(this, "Register at least one fingerprint in Settings",Toast.LENGTH_LONG).show();
return;
}

Generate Key

After this accessing and enabling the fingerprint services, we have to Generate Key as follows:

protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
} catch (NoSuchAlgorithmException |
NoSuchProviderException e) {
throw new RuntimeException(
"Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}

Initializing the Cipher

Now that the key has been generated the next step is to initialize the cipher that will be used to create the encrypted FingerprintManager CryptoObject instance.

This CryptoObject will, in turn, be used during the fingerprint authentication process. Cipher configuration involves obtaining a Cipher instance and initializing it with the key stored in the Keystore container.

public boolean cipherInit() {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"+ KeyProperties.BLOCK_MODE_CBC +
"/"+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Failed to get Cipher", e);
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
createNewKey(true); // Retry after clearing entry
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}

If we don’t have cipherkey then we need to create New key as follow:

public boolean createNewKey(boolean forceCreate) {
print("Creating new key...");
try {
if (forceCreate)
keyStore.deleteEntry(KEY_ALIAS);

if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE);

generator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.build()
);

generator.generateKey();
print("Key created.");
} else
print("Key exists.");

return true;
} catch (Exception e) {
print(e.getMessage());
}

return false;
}

Then initialized this both method in onCreate() as follows:

generateKey();
if (cipherInit()) {
cryptoObject = new FingerprintManager.CryptoObject(cipher);
FingerprintHandler helper = new FingerprintHandler(this);
helper.startAuth(fingerprintManager, cryptoObject);
}

FingerprintHandler matches the key with the recorder fingerprint and gives us the result like authentication is succeeding or failing.

public class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
private CancellationSignal cancellationSignal;
private Context appContext;

public FingerprintHandler(Context context) {
appContext = context;
}

public void startAuth(FingerprintManager manager,
FingerprintManager.CryptoObject cryptoObject) {

cancellationSignal = new CancellationSignal();

if (ActivityCompat.checkSelfPermission(appContext,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}

@Override
public void onAuthenticationError(int errMsgId,
CharSequence errString) {
Toast.makeText(appContext,
"Authentication error\n" + errString,
Toast.LENGTH_LONG).show();
}

@Override
public void onAuthenticationHelp(int helpMsgId,
CharSequence helpString) {
Toast.makeText(appContext,
"Authentication help\n" + helpString,
Toast.LENGTH_LONG).show();
}

@Override
public void onAuthenticationFailed() {
Toast.makeText(appContext,
"Authentication failed.",
Toast.LENGTH_LONG).show();
}

@Override
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {

Toast.makeText(appContext,
"Authentication succeeded.",
Toast.LENGTH_LONG).show();
}
}

Now, all the things mentioned above are supposed to authenticate your fingerprint. The Next point is applying gesture which we discussed earlier.

For that, we need to implement GestureDetector on GestureListener, and GestureDetector on DoubleTapListener in MainActivity, or else we can make one filter which has all these functionalities as given below:

public class SimpleGestureFilter extends SimpleOnGestureListener
{
public final static int SWIPE_UP = 1;
public final static int SWIPE_DOWN = 2;
public final static int SWIPE_LEFT = 3;
public final static int SWIPE_RIGHT = 4;
public final static int MODE_TRANSPARENT = 0;
public final static int MODE_SOLID = 1;
public final static int MODE_DYNAMIC = 2;
private final static int ACTION_FAKE = -13;
private int swipe_Min_Distance = 100;
private int swipe_Max_Distance = 350;
private int swipe_Min_Velocity = 100;
private int mode = MODE_DYNAMIC;
private boolean running = true;
private boolean tapIndicator = false;
private Activity context;
private GestureDetector detector;
private SimpleGestureListener listener;
public SimpleGestureFilter(Activity context,SimpleGestureListener sgf)
{
this.context = context;
this.detector = new GestureDetector(context, this);
this.listener = sgf;
}
public void onTouchEvent(MotionEvent me)
{
// TODO Auto-generated method stub
if
(!this.running)
return;
boolean result=this.detector.onTouchEvent(me);
if(this.mode==MODE_SOLID)
me.setAction(MotionEvent.ACTION_CANCEL);
else if(this.mode==MODE_DYNAMIC)
{
if(me.getAction()==ACTION_FAKE)
me.setAction(MotionEvent.ACTION_UP);
else if(result)
me.setAction(MotionEvent.ACTION_CANCEL);
else if(this.tapIndicator)
{
me.setAction(MotionEvent.ACTION_DOWN);
this.tapIndicator=false;
}
}
}
public void setMode(int m)
{
this.mode=m;
}
public int getMode()
{
return this.mode;
}
public void setEnabled(boolean status)
{
this.running=status;
}
public void setSwipeMaxDistance(int distance)
{
this.swipe_Max_Distance=distance;
}
public void setSwipeMinDistance(int distance)
{
this.swipe_Min_Distance=distance;
}
public int getSwipeMaxDistance()
{
return this.swipe_Max_Distance;
}
public int getSwipeMinDistance()
{
return this.swipe_Min_Distance;
}
public int getSwipeMinVelocity()
{
return this.swipe_Min_Velocity;
}

public boolean onFling(MotionEvent e1,MotionEvent e2,float velocityX,float velocityY)
{
final float xDistance=Math.abs(e1.getX()-e2.getX());
final float yDistance=Math.abs(e1.getY()-e2.getY());
if(xDistance>this.swipe_Max_Distance || yDistance> this.swipe_Max_Distance)

return false;
velocityX = Math.abs(velocityX);
velocityY = Math.abs(velocityY);
boolean result=false;
if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance)
{
if(e1.getX() > e2.getX()) // right to left Move
this.listener.onSwipe(SWIPE_LEFT);
else
this
.listener.onSwipe(SWIPE_RIGHT);
result=true;
}
else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance)
{
if(e1.getY() > e2.getY()) // bottom to top Move
this.listener.onSwipe(SWIPE_UP);
else
this
.listener.onSwipe(SWIPE_DOWN);
result=true;
}
return result;
}
public boolean onSingleTapUp(MotionEvent e)
{
this.tapIndicator=true;
Toast.makeText(context, "SingleTapUp", Toast.LENGTH_SHORT).show();
return false;
}
public boolean onDoubleTap(MotionEvent e)
{
this.listener.onDoubleTap();
Toast.makeText(context, "DoubleTap", Toast.LENGTH_SHORT).show();
return false;
}
public boolean onDoubleTapEvent(MotionEvent e)
{
this.listener.onDoubleTap();

return true;
}
public boolean onSingleTapConfirmed(MotionEvent e)
{
if(this.mode==MODE_DYNAMIC)
{
e.setAction(ACTION_FAKE);
this.context.dispatchTouchEvent(e);
}
return false;
}
static interface SimpleGestureListener
{
void onSwipe(int direction);
void onDoubleTap();
}
}

Now onward FingerPrint Gesture, we can do the authentication with SingleTap only, but if we want Notification of Fingerprint identification on touch with Different Gesture, then it is possible for this application.

Now, how can we Login with Fingerprint?

We need to make LoginActivity and implement the email and password container details for login with Fingerprint.

First of all create LoginActivity

After making this LoginActivity implements,LoaderCallbacks<Cursor>,FingerPrintHelper which is shown at earlier, implement it, but in LoginActivity you have to get the key from authenticationSucceeded() method and encrypt and decrypt the String that we get. You can this all functions as follows:

@Override
public void authenticationFailed(String error) {
print(error);
}
@Override
public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) {
print("Authentication succeeded!");
cipher = result.getCryptoObject().getCipher();

if (encrypting) {
String textToEncrypt = mPasswordView.getText().toString();
encryptString(textToEncrypt);
print(R.string.register_success);

mEmailView.setText("");
mPasswordView.setText("");
} else {
String encryptedText = sharedPreferences.getString(PREFERENCES_KEY_PASS, "");
decryptString(encryptedText);
print(R.string.fingerprint_authenticate_success);
}
}

public void encryptString(String initialText) {
print("Encrypting...");
try {
byte[] bytes = cipher.doFinal(initialText.getBytes());
String encryptedText = Base64.encodeToString(bytes, Base64.NO_WRAP);

SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(PREFERENCES_KEY_PASS, encryptedText);
editor.apply();

} catch (Exception e) {
print(e.getMessage());
}
}

public void decryptString(String cipherText) {
print("Decrypting...");
try {
byte[] bytes = Base64.decode(cipherText, Base64.NO_WRAP);
String finalText = new String(cipher.doFinal(bytes));
mPasswordView.setText(finalText);
mEmailView.setText(sharedPreferences.getString(PREFERENCES_KEY_EMAIL, ""));
} catch (Exception e) {
print(e.getMessage());
}
}

On clicking of Login and Sign in using Fingerprint, we have to check first that fingerprint is registered or not as earlier I said.

Now, the next step is storing the email and passwords in preference with apply validation. Then call getCipher() till then we have generated the key and initCipher.

private boolean getCipher() {
print("Getting cipher...");
try {
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);

return true;
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
print(e.getMessage());
}

return false;
}

Then get the login authentication with its AsyncTask as follows:

/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
private class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

private final String mEmail;
private final String mPassword;
private final Boolean mRegister; // if false, authenticate instead

UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
mRegister = true;
fingerprintHelper = new FingerprintHelper(LoginActivity.this);
}

UserLoginTask() {
mRegister = false;
mEmail = null;
mPassword = null;
fingerprintHelper = new FingerprintHelper(LoginActivity.this);
}

@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected Boolean doInBackground(Void... params) {
if (!getKeyStore())
return false;

if (!createNewKey(false))
return false;

// Inside doInBackground
if (!getCipher())
return false;

// Inside doInBackground
if (mRegister) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(PREFERENCES_KEY_EMAIL, mEmail);
editor.apply();

encrypting = true;

if (!initCipher(Cipher.ENCRYPT_MODE))
return false;
} else {
encrypting = false;
if (!initCipher(Cipher.DECRYPT_MODE))
return false;
}

return initCryptObject();

}

@Override
protected void onPostExecute(final Boolean success) {
onCancelled();

if (!success) {
if (mRegister)
print(R.string.register_fail);
else
print(R.string.fingerprint_authenticate_fail);
} else {
fingerprintHelper.startAuth(LoginActivity.this.fingerprintManager, cryptoObject);
print("Authenticate using fingerprint!");
}
}

@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}

public void print(String text) {
mNoteTextView.setText(text + "\n" + mNoteTextView.getText());
}

public void print(int id) {
print(getString(id));
}

After that, you can give contact permission for getting “profile” information and fill automatically in an email, but this is irrelevant, which you can delete in this case.

So, finally, we have many options for fingerprint authentication. You can see the login with a fingerprint in the video given below:

Hope this will help you kick start learning and R&D about the fingerprint authentication with login and performing an action on the fingerprint.

Thank you :)

Happy Coding!!!

--

--

Sejal Baraiya
IndiaNIC

Software Quality Assurance Analyst and Android Developer | Always be ready for learning new things. It’s exciting….