So to do this I ended up encrypting the users pin in to shared preferences and then decrypting when the fingerprint auth was successful:
So to save the pin:
private static final String CHARSET_NAME = "UTF-8";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String TRANSFORMATION = KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7;
private static final int AUTHENTICATION_DURATION_SECONDS = 30;
private KeyguardManager keyguardManager;
private static final int SAVE_CREDENTIALS_REQUEST_CODE = 1;
public void saveUserPin(String pin) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
// encrypt the password
try {
SecretKey secretKey = createKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptionIv = cipher.getIV();
byte[] passwordBytes = pin.getBytes(CHARSET_NAME);
byte[] encryptedPasswordBytes = cipher.doFinal(passwordBytes);
String encryptedPassword = Base64.encodeToString(encryptedPasswordBytes, Base64.DEFAULT);
// store the login data in the shared preferences
// only the password is encrypted, IV used for the encryption is stored
SharedPreferences.Editor editor = BaseActivity.prefs.edit();
editor.putString("password", encryptedPassword);
editor.putString("encryptionIv", Base64.encodeToString(encryptionIv, Base64.DEFAULT));
editor.apply();
} catch (UserNotAuthenticatedException e) {
e.printStackTrace();
showAuthenticationScreen(SAVE_CREDENTIALS_REQUEST_CODE);
}
}
private SecretKey createKey() {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
keyGenerator.init(new KeyGenParameterSpec.Builder(Constants.KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
return keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException("Failed to create a symmetric key", e);
}
}
Then to decrypt:
public String getUserPin() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchPaddingException, UnrecoverableKeyException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
// load login data from shared preferences (
// only the password is encrypted, IV used for the encryption is loaded from shared preferences
SharedPreferences sharedPreferences = BaseActivity.prefs;
String base64EncryptedPassword = sharedPreferences.getString("password", null);
String base64EncryptionIv = sharedPreferences.getString("encryptionIv", null);
byte[] encryptionIv = Base64.decode(base64EncryptionIv, Base64.DEFAULT);
byte[] encryptedPassword = Base64.decode(base64EncryptedPassword, Base64.DEFAULT);
// decrypt the password
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
SecretKey secretKey = (SecretKey) keyStore.getKey(Constants.KEY_NAME, null);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(encryptionIv));
byte[] passwordBytes = cipher.doFinal(encryptedPassword);
String string = new String(passwordBytes, CHARSET_NAME);
return string;
}
The showAuthenticationScreen method that is called looks like this:
private void showAuthenticationScreen(int requestCode) {
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
startActivityForResult(intent, requestCode);
}
}
And then to get the result back from showAuthenticationScreen
just override onActivityResult
and call saveUserPin
or getUserPin
again whichever is required.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…