6

我正在创建一个应用程序,用户有两个选项来解锁他们的应用程序,一个是使用 pin,另一个是使用指纹。为了使用指纹,他们必须首先设置一个密码,因为这个密码是解密密钥,可以从中获取他们的加密细节SharedPreferences

所以我在这里遵循了本教程:http ://www.techotopia.com/index.php/An_Android_Fingerprint_Authentication_Tutorial#Accessing_the_Android_Keystore_and_KeyGenerator

我已经设法让应用程序读取指纹并说出它是否有效。但是当指纹被授权时,我不知道如何从 Android 密钥库中取出该引脚。

下面是一些代码来演示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
    fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);

    if (!keyguardManager.isKeyguardSecure()) {

        Toast.makeText(this, "Lock screen security not enabled in Settings", Toast.LENGTH_LONG).show();
        return;
    }

    if (ActivityCompat.checkSelfPermission(this,
            Manifest.permission.USE_FINGERPRINT) !=
            PackageManager.PERMISSION_GRANTED) {
        Toast.makeText(this, "Fingerprint authentication permission not enabled", Toast.LENGTH_LONG).show();

        return;
    }

    if (!fingerprintManager.hasEnrolledFingerprints()) {

        // This happens when no fingerprints are registered.
        Toast.makeText(this, "Register at least one fingerprint in Settings", Toast.LENGTH_LONG).show();
        return;
    }

    generateKey();

    if (cipherInit()) {
        cryptoObject = new FingerprintManager.CryptoObject(cipher);
        FingerprintHandler helper = new FingerprintHandler(this);

        helper.startAuth(fingerprintManager, cryptoObject);
    }

}

protected void generateKey() {
    try {
        keyStore = KeyStore.getInstance("AndroidKeyStore");
    } catch (Exception e) {
        e.printStackTrace();
    }

    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);
    }
}

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);
    }
}

KEY_NAME 是我要存储的密钥(pin)(我认为)。

然后在FingerprintHandler类中有这个方法:

public void onAuthenticationSucceeded(
        FingerprintManager.AuthenticationResult result) {

    Toast.makeText(appContext,
            "Authentication succeeded.",
            Toast.LENGTH_LONG).show();

}

但是我如何从 if 中得到我想要的钥匙result呢?

4

2 回答 2

9

所以为了做到这一点,我最终加密了用户锁定到共享首选项,然后在指纹认证成功时解密:

所以要保存引脚:

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);
    }
}

然后解密:

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;
}

调用的 showAuthenticationScreen 方法如下所示:

private void showAuthenticationScreen(int requestCode) {
    Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
    if (intent != null) {
        startActivityForResult(intent, requestCode);
    }
}

然后从showAuthenticationScreen只是覆盖onActivityResult和调用saveUserPingetUserPin再次获得结果,无论需要什么。

于 2016-11-30T16:35:21.723 回答
-2

您提到的教程以及 Google 提供的指纹对话框示例处理身份验证的方式是假设用户在onAuthenticationSucceeded()被调用时是真实的。Cipher谷歌示例通过检查提供的是否可以加密任意数据,更进一步:

/**
 * 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());
    }
}

这是一种有效的身份验证形式,但如果您需要实际存储和检索秘密(在您的情况下为 pin),这还不够。相反,您可以使用非对称密码术来加密您的秘密,然后在onAuthenticationSucceeded(). 这类似于在 中处理身份验证的方式Asymmetric Fingerprint Dialog Sample,尽管没有后端服务器。

于 2016-11-23T20:13:25.177 回答