Fingerprint
Android fingerprint authentication uses smartphone touch sensor to authenticate the user. Android Marshmallow has introduced a set of API that makes it easy to use the touch sensor. Before Android Marshmallow, the method to access the touch sensor was not standard.
There are several steps to follow to enable fingerprint authentication:
- Verify that the lock screen is secure, or in other words, it is protected by PIN, password or pattern
- Verify that at least one fingerprint is registered on the smartphone
- Get access to Android keystore to store the key used to encrypt/decrypt an object
- Generate an encryption key and the Cipher
- Start the authentication process
- Implement a callback class to handle authentication events
It is important to request permission to use the touch sensor and the fingerprint authentication. So in theManifest.xml
we included the fingerprint permission
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
The first step is verifying the secure lock screen. This can be done KeyguardManager and FingerprintManager. We get an instance of these two managers using getSystemService:
// Keyguard Manager
KeyguardManager keyguardManager = (KeyguardManager)
getSystemService(KEYGUARD_SERVICE);
// Fingerprint Manager
fingerprintManager = (FingerprintManager)
getSystemService(FINGERPRINT_SERVICE);
The next step is accessing to the Android keystore and generate the key to encrypt the data. The app does it in a separate method called generateKey().
@TargetApi(Build.VERSION_CODES.M)
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
KeyGenerator keyGenerator;
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);
}
}
Once the key is ready, the last step is creating the Android Cipher that uses the key, we have generated before. The source code is very simple:
@TargetApi(Build.VERSION_CODES.M)
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) {
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
The last step is creating the callback class so that we can receive event notification and we can know when the authentication succeeded or something went wrong. This class extends FingerprintManager.AuthenticationCallback
@TargetApi(Build.VERSION_CODES.M)
public class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
public Boolean getSuccess() {
return Success;
}
public void setSuccess(Boolean success) {
Success = success;
}
private Context context;
private Boolean Success=false;
private String sKey;
SessionManager sessionManager;
private static final String FINGERPREF = "fingerPref";
private SharedPreferences sp;
FingerprintUpdate fingerprintUpdate ;
// Constructor
public FingerprintHandler(Context mContext,String sKey) {
context = mContext;
this.sKey =sKey.trim();
sp = context.getSharedPreferences(FINGERPREF, MODE_PRIVATE);
}
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
this.update("Fingerprint Authentication error\n" + errString, false);
setSuccess(false);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
this.update("Fingerprint Authentication help\n" + helpString, false);
setSuccess(false);
}
@Override
public void onAuthenticationFailed() {
this.update("Fingerprint Authentication failed.", false);
setSuccess(false);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
this.update("Fingerprint Authentication succeeded.", true);
sessionManager = new SessionManager(context);
// Intent intent = new Intent(context, HospitalActivity.class);
// context.startActivity(intent);
setSuccess(true);
fingerprintUpdate = new FingerprintUpdate();
fingerprintUpdate.setUsername(sessionManager.getUserDetails().get(sessionManager.KEY_EMAIL));
fingerprintUpdate.setPassword(sessionManager.getUserDetails().get(sessionManager.KEY_PASSWORD));
fingerprintUpdate.setFingerKey(sKey);
Log.d("MyKEY", sKey);
listUpdate(fingerprintUpdate);
}
public void update(String e, Boolean success){
TextView textView = (TextView) ((Activity)context).findViewById(R.id.errorText);
textView.setText(e);
if(success){
textView.setTextColor(ContextCompat.getColor(context,R.color.colorPrimaryDark));
}
}
public void listUpdate(FingerprintUpdate request) {
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<Check> call = apiService.getFingerprintUpdate(request);
call.enqueue(new Callback<Check>() {
@Override
public void onResponse(Call<Check> call, Response<Check> response) {
if (response.code() == 401) {
// sessionManager.logoutUser();
//finish();
}else
if (!response.body().getSuccess().isEmpty()) {
final SweetAlertDialog failedAlert = new SweetAlertDialog(context, SweetAlertDialog.SUCCESS_TYPE);
failedAlert.setTitleText("Success")
.setContentText("Fingerprint saved")
.setConfirmText("ok")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sweetAlertDialog) {
// finish();
SharedPreferences.Editor editor = sp.edit();
editor.putString("email", fingerprintUpdate.getUsername());
editor.putString("password", fingerprintUpdate.getPassword());
editor.putString("fingerkey", fingerprintUpdate.getFingerKey());
editor.apply();
editor.commit();
Intent intent = new Intent(context, HospitalActivity.class);
context.startActivity(intent);
}
})
.show();
}
}
@Override
public void onFailure(Call<Check> call, Throwable t) {
new AlertPopup().alertConnectError(context);
}
});
}
}
.