Created
August 23, 2018 07:35
-
-
Save frengky/b2b96a4b1ec234080e9d8a9164240f1a to your computer and use it in GitHub Desktop.
Android fingerprint authentication (Encrypt/Decrypt purpose)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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