How to get key from keystore on successful fingerprint auth

你。 提交于 2019-11-29 02:32:59

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.

The way in which the tutorial you mentioned, as well as the Fingerprint Dialog Sample provided by Google, handles authentication is by assuming that the user is authentic when onAuthenticationSucceeded() is called. The Google sample takes this a step further by checking if the Cipher provided by the can encrypt arbitrary data:

/**
 * Proceed the purchase operation
 *
 * @param withFingerprint {@code true} if the purchase was made by using a fingerprint
 * @param cryptoObject the Crypto object
 */
public void onPurchased(boolean withFingerprint,
        @Nullable FingerprintManager.CryptoObject cryptoObject) {
    if (withFingerprint) {
        // If the user has authenticated with fingerprint, verify that using cryptography and
        // then show the confirmation message.
        assert cryptoObject != null;
        tryEncrypt(cryptoObject.getCipher());
    } else {
        // Authentication happened with backup password. Just show the confirmation message.
        showConfirmation(null);
    }
}

/**
 * Tries to encrypt some data with the generated key in {@link #createKey} which is
 * only works if the user has just authenticated via fingerprint.
 */
private void tryEncrypt(Cipher cipher) {
    try {
        byte[] encrypted = cipher.doFinal(SECRET_MESSAGE.getBytes());
        showConfirmation(encrypted);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
        Toast.makeText(this, "Failed to encrypt the data with the generated key. "
                + "Retry the purchase", Toast.LENGTH_LONG).show();
        Log.e(TAG, "Failed to encrypt the data with the generated key." + e.getMessage());
    }
}

This is a valid form of authentication, but if you need to actually store and retrieve a secret (in your case a pin), it is not sufficient. Instead, you can use asymmetric cryptography to encrypt your secret, then decrypt it upon onAuthenticationSucceeded(). This is similar to how authentication is handled in the Asymmetric Fingerprint Dialog Sample, although without a back end server.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!