Skip to content

Instantly share code, notes, and snippets.

@frengky
Created August 23, 2018 07:35
Show Gist options
  • Save frengky/b2b96a4b1ec234080e9d8a9164240f1a to your computer and use it in GitHub Desktop.
Save frengky/b2b96a4b1ec234080e9d8a9164240f1a to your computer and use it in GitHub Desktop.
Android fingerprint authentication (Encrypt/Decrypt purpose)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/divider_vertical"
android:showDividers="middle"
android:theme="@style/AppTheme"
android:background="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_edge"
android:paddingBottom="@dimen/margin_edge"
android:orientation="vertical">
<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
style="@style/TextHighlight"
android:textSize="@dimen/text_size_large"
tools:text="Login" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
style="@style/TextNormal"
android:textSize="@dimen/text_size_normal"
android:text="Sentuh sensor sidik jari anda" />
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/margin_default"
app:srcCompat="@drawable/placeholder_fingerprint" />
<TextView
android:id="@+id/error_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginTop="@dimen/margin_default"
style="@style/TextNormal"
android:textColor="@color/red_500"
android:textSize="@dimen/text_size_normal"
android:text="Coba lagi"
android:visibility="gone"
tools:visibility="gone" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatButton
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:textColor="@color/colorPrimary"
style="?android:attr/borderlessButtonStyle"
android:text="Batal" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/use_password_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:textColor="@color/colorPrimary"
style="?android:attr/borderlessButtonStyle"
android:text="Gunakan Password" />
</RelativeLayout>
</LinearLayout>
public class FingerprintDialogFragment extends BottomSheetDialogFragment {
private static final String TAG = FingerprintDialogFragment.class.getSimpleName();
private static final String TITLE = "title";
private static final String SUBJECT = "subject";
private static final String MODE = "mode";
public static final int MODE_ENCRYPT = Cipher.ENCRYPT_MODE;
public static final int MODE_DECRYPT = Cipher.DECRYPT_MODE;
private static final String KEY_ALIAS = "any-alias";
private static final String KEY_STORE = "AndroidKeyStore";
private static final String SHARED_PREFERENCE_KEY_PASS = "keypass";
private static final String SHARED_PREFERENCE_KEY_IV = "iv";
private TextView titleTextView;
private TextView errorTextView;
private AppCompatButton cancelButton;
private AppCompatButton usePasswordButton;
private String title;
private String subject;
private int mode;
private KeyStore keyStore;
private SecretKey secretKey;
private Cipher cipher;
private CancellationSignal cancellationSignal;
private FingerprintManager fingerprintManager;
private FingerprintAuthListener listener;
public static FingerprintDialogFragment newInstance(String title, String subject, int mode) {
FingerprintDialogFragment fragment = new FingerprintDialogFragment();
Bundle args = new Bundle();
args.putString(TITLE, title);
args.putString(SUBJECT, subject);
args.putInt(MODE, mode);
fragment.setArguments(args);
return fragment;
}
public interface FingerprintAuthListener {
void onAuthenticationSucceeded(String result);
void onCancel();
void onDisable();
}
public void setFingerprintAuthListener(FingerprintAuthListener listener) {
this.listener = listener;
}
@RequiresApi(Build.VERSION_CODES.M)
private class AuthenticationCallback extends FingerprintManager.AuthenticationCallback {
public AuthenticationCallback() {
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
if (mode == Cipher.ENCRYPT_MODE) {
encryptResult(cipher);
} else {
decryptResult(cipher);
}
dismiss();
}
private void encryptResult(Cipher cipher) {
try {
byte[] bytes = cipher.doFinal(subject.getBytes());
String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP);
if (listener != null) {
listener.onAuthenticationSucceeded(encrypted);
}
} catch (IllegalBlockSizeException e) {
//
} catch (BadPaddingException e) {
//
}
}
private void decryptResult(Cipher cipher) {
try {
byte[] bytes = Base64.decode(subject, Base64.NO_WRAP);
String decrypted = new String(cipher.doFinal(bytes));
if (listener != null) {
listener.onAuthenticationSucceeded(decrypted);
}
} catch (IllegalBlockSizeException e) {
//
} catch (BadPaddingException e) {
//
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
errorTextView.setVisibility(View.VISIBLE);
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
errorTextView.setVisibility(View.VISIBLE);
}
@Override
public void onAuthenticationFailed() {
errorTextView.setVisibility(View.VISIBLE);
}
}
@RequiresApi(Build.VERSION_CODES.M)
private boolean initCipher(Context context, int mode) throws InvalidKeyException {
try {
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putString(SHARED_PREFERENCE_KEY_IV, Base64.encodeToString(cipher.getIV(), Base64.NO_WRAP))
.apply();
return true;
} else {
String keyIV = PreferenceManager.getDefaultSharedPreferences(context)
.getString(SHARED_PREFERENCE_KEY_IV, "");
byte[] iv = Base64.decode(keyIV, Base64.NO_WRAP);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
return true;
}
} catch (NoSuchAlgorithmException e) {
//
} catch (NoSuchPaddingException e) {
//
} catch (InvalidAlgorithmParameterException e) {
//
}
return false;
}
@RequiresApi(Build.VERSION_CODES.M)
private void deleteKeyPair() {
try {
KeyStore keyStore = KeyStore.getInstance(KEY_STORE);
keyStore.load(null);
keyStore.deleteEntry(KEY_ALIAS);
} catch (KeyStoreException e) {
//
} catch (CertificateException e) {
//
} catch (NoSuchAlgorithmException e) {
//
} catch (IOException e) {
//
}
}
@RequiresApi(Build.VERSION_CODES.M)
private boolean createKeyPair(boolean forceCreate) {
try {
keyStore = KeyStore.getInstance(KEY_STORE);
keyStore.load(null);
if (forceCreate) {
keyStore.deleteEntry(KEY_ALIAS);
}
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
keyGenerator.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()
);
keyGenerator.generateKey();
}
secretKey = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
return true;
} catch (KeyStoreException e) {
//
} catch (CertificateException e) {
//
} catch (UnrecoverableKeyException e) {
//
} catch (InvalidAlgorithmParameterException e) {
//
} catch (NoSuchAlgorithmException e) {
//
} catch (NoSuchProviderException e) {
//
} catch (IOException e) {
//
}
return false;
}
@RequiresApi(Build.VERSION_CODES.M)
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Logger.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.dialog_fingerprint, container, false);
Bundle args = getArguments();
if (args != null) {
title = args.getString(TITLE);
subject = args.getString(SUBJECT);
mode = args.getInt(MODE);
}
titleTextView = view.findViewById(R.id.title_text);
titleTextView.setText(title);
errorTextView = view.findViewById(R.id.error_text);
cancelButton = view.findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
if (listener != null) {
listener.onCancel();
}
}
});
usePasswordButton = view.findViewById(R.id.use_password_button);
usePasswordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
deleteKeyPair();
if (listener != null) {
listener.onDisable();
}
}
});
if (createKeyPair(false)) {
try {
if (initCipher(getContext(), mode)) {
cancellationSignal = new CancellationSignal();
fingerprintManager = (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
fingerprintManager.authenticate(
new FingerprintManager.CryptoObject(cipher),
cancellationSignal,
0,
new AuthenticationCallback(),
null
);
}
} catch (InvalidKeyException e) {
errorTextView.setText("Sidik jari tidak tersedia");
}
} else {
errorTextView.setText("Sidik jari tidak tersedia");
}
setCancelable(false);
return view;
}
@Override
public void onPause() {
if (cancellationSignal != null) {
Logger.d(TAG, "cancellationSignal.cancel()");
cancellationSignal.cancel();
cancellationSignal = null;
}
super.onPause();
}
public static boolean isFingerprintAvailable(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (!keyguardManager.isKeyguardSecure()) {
Logger.d(TAG, "Lock screen is not enabled");
return false;
}
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
Logger.d(TAG, "Missing permission USE_FINGERPRINT");
return false;
}
FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
if (fingerprintManager == null || !fingerprintManager.isHardwareDetected()) {
Logger.d(TAG, "Fingerprint support not available on this device");
return false;
}
if (!fingerprintManager.hasEnrolledFingerprints()) {
Logger.d(TAG, "No fingerprint has been enrolled on this device");
return false;
}
return true;
}
}
<vector android:height="96dp" android:viewportHeight="48"
android:viewportWidth="48" android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/white" android:pathData="M35.62,8.94c-0.16,0 -0.31,-0.04 -0.46,-0.11C31.33,6.85 28,6 24.02,6c-3.97,0 -7.71,0.95 -11.14,2.82 -0.49,0.26 -1.09,0.09 -1.36,-0.4 -0.26,-0.49 -0.09,-1.09 0.4,-1.36C15.65,5.03 19.72,4 24.02,4c4.26,0 7.98,0.94 12.06,3.05 0.49,0.25 0.68,0.86 0.43,1.35 -0.18,0.34 -0.53,0.54 -0.89,0.54zM7,19.44c-0.2,0 -0.4,-0.06 -0.58,-0.18 -0.45,-0.32 -0.56,-0.94 -0.24,-1.39 1.98,-2.8 4.51,-5 7.51,-6.55 6.29,-3.25 14.33,-3.26 20.63,-0.02 2.99,1.54 5.51,3.72 7.5,6.5 0.32,0.45 0.22,1.07 -0.23,1.39 -0.45,0.32 -1.08,0.22 -1.4,-0.23 -1.8,-2.52 -4.08,-4.5 -6.78,-5.88 -5.74,-2.95 -13.07,-2.94 -18.8,0.02 -2.71,1.4 -5,3.39 -6.79,5.93 -0.2,0.27 -0.51,0.41 -0.82,0.41zM19.51,43.57c-0.26,0 -0.51,-0.1 -0.71,-0.3 -1.73,-1.75 -2.67,-2.86 -4.02,-5.27 -1.38,-2.46 -2.11,-5.47 -2.11,-8.69 0,-5.94 5.08,-10.78 11.33,-10.78s11.33,4.83 11.33,10.78c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1c0,-4.84 -4.18,-8.78 -9.33,-8.78 -5.14,0 -9.33,3.94 -9.33,8.78 0,2.88 0.64,5.54 1.85,7.71 1.29,2.3 2.15,3.29 3.69,4.84 0.39,0.39 0.39,1.03 -0.01,1.41 -0.18,0.21 -0.44,0.3 -0.69,0.3zM33.84,39.87c-2.38,0 -4.47,-0.6 -6.2,-1.77 -2.97,-2.02 -4.75,-5.3 -4.75,-8.78 0,-0.55 0.45,-1 1,-1s1,0.45 1,1c0,2.81 1.45,5.47 3.88,7.12 1.41,0.96 3.07,1.43 5.07,1.43 0.48,0 1.29,-0.05 2.09,-0.19 0.54,-0.1 1.06,0.27 1.16,0.81 0.1,0.54 -0.27,1.06 -0.81,1.16 -1.17,0.21 -2.16,0.22 -2.44,0.22zM29.81,44c-0.09,0 -0.18,-0.01 -0.26,-0.04 -3.19,-0.87 -5.27,-2.05 -7.43,-4.2 -2.79,-2.78 -4.33,-6.49 -4.33,-10.44 0,-3.25 2.76,-5.89 6.16,-5.89 3.4,0 6.16,2.64 6.16,5.89 0,2.14 1.87,3.89 4.16,3.89s4.16,-1.74 4.16,-3.89c0,-7.54 -6.5,-13.67 -14.49,-13.67 -5.69,0 -10.88,3.16 -13.22,8.06 -0.78,1.62 -1.17,3.51 -1.17,5.61 0,1.56 0.14,4.02 1.33,7.21 0.19,0.52 -0.07,1.09 -0.59,1.29 -0.52,0.19 -1.09,-0.07 -1.29,-0.59 -0.98,-2.63 -1.46,-5.21 -1.46,-7.91 0,-2.4 0.46,-4.58 1.37,-6.47 2.67,-5.58 8.57,-9.19 15.02,-9.19 9.09,0 16.49,7.03 16.49,15.67 0,3.25 -2.77,5.89 -6.16,5.89s-6.16,-2.64 -6.16,-5.89c0,-2.14 -1.87,-3.89 -4.16,-3.89s-4.16,1.74 -4.16,3.89c0,3.41 1.33,6.62 3.74,9.02 1.89,1.88 3.73,2.92 6.55,3.69 0.53,0.15 0.85,0.7 0.7,1.23 -0.12,0.44 -0.52,0.73 -0.96,0.73z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval" >
<solid android:color="@color/colorPrimary" />
</shape>
</item>
<item android:drawable="@drawable/ic_fingerprint"
android:gravity="center"
android:top="@dimen/margin_small"
android:bottom="@dimen/margin_small"
android:left="@dimen/margin_small"
android:right="@dimen/margin_small">
</item>
</layer-list>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment