【问题标题】:Initializing FingerpringManager.Crypto Object, getting Crypto primitive not backed by AndroidKeyStore provider?初始化 FingerpringManager.Crypto 对象,获取不受 AndroidKeyStore 提供程序支持的 Crypto 原语?
【发布时间】:2016-10-27 06:49:05
【问题描述】:

我正在使用 Android FingerPrintManager API 并使用 KeyPairGenerator 创建密钥对,我想用公钥加密密码,然后在用户通过输入指纹进行身份验证时解密,但是一旦我运行我的项目,它就会崩溃并给出

引起:java.lang.IllegalArgumentException:加密原语不是 由 AndroidKeyStore 提供商提供支持

我从这里使用过代码:Android Fingerprint API Encryption and Decryption 这篇文章说他能够进行加密和解密,并且 di 遵循相同的代码和步骤。 这是我的代码

public KeyStore getKeyStore() {
    try {
        return KeyStore.getInstance("AndroidKeyStore");
    } catch (KeyStoreException exception) {
        throw new RuntimeException("Failed to get an instance of KeyStore", exception);
    }
}

public KeyPairGenerator getKeyPairGenerator() {
    try {
        return KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    } catch (NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

public Cipher getCipher() {
    try {
        return Cipher.getInstance("RSA");
    } catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator = getKeyPairGenerator();
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch (InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore = getKeyStore();
        mKeyStore.load(null);

        mCipher = getCipher();

        if (opmode == Cipher.ENCRYPT_MODE) {

            PublicKey key = mKeyStore.getCertificate(KEY_NAME).getPublicKey();

            PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                    .generatePublic(new X509EncodedKeySpec(key.getEncoded()));

            OAEPParameterSpec spec = new OAEPParameterSpec(
                    "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
            mCipher.init(opmode, unrestricted, spec);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch (KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}


private void encrypt(String password) {
    try {
        initCipher(Cipher.ENCRYPT_MODE);
        byte[] bytes = mCipher.doFinal(password.getBytes());
        enrcyptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
        Log.d("EncryptedText", enrcyptedPassword);
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decryptPassword(Cipher cipher) {
    try {
        initCipher(Cipher.DECRYPT_MODE);
        byte[] bytes = Base64.decode(enrcyptedPassword, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException | RuntimeException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}

从这里我开始初始化我的 CryptoObject:

createKeyPair();
    if (initCipher(Cipher.ENCRYPT_MODE)) {
        mCryptoObject = new FingerprintManager.CryptoObject
                (mCipher);
        encrypt("1111");
        if (!isFingerprintAuthAvailable()) {
            return;
        }
        mCancellationSignal = new CancellationSignal();
        mSelfCancelled = false;
        mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, this, null);

我在这一行遇到了异常:

mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, this, null);

【问题讨论】:

    标签: java android encryption android-keystore android-fingerprint-api


    【解决方案1】:

    @AlexKlyubin 是对的,你不需要使用指纹管理器进行加密,只需要解密。为了加密文本,你需要做的就是调用上面的encrypt(String password)方法。

    对于解密,您应该使用FingerprintManagerCompat 而不是FingerprintManager。为了监听指纹事件和解密密码,你需要扩展FingerprintManagerCompat.AuthenticationCallback。我扩展了这个类,并实现了回调接口:

    public class FingerprintAuthentication extends FingerprintManagerCompat.AuthenticationCallback {
    
        private final Callback mCallback;
    
        public FingerprintCallback(Callback callback) {
            mCallback = callback;
        }
    
        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
            mCallback.onAuthenticationSucceeded(result);
        }
    
        @Override
        public void onAuthenticationHelp(int messageId, CharSequence message) {
            mCallback.onAuthenticationHelp(messageId, message);
        }
    
        @Override
        public void onAuthenticationError(int messageId, CharSequence message) {
            mCallback.onAuthenticationError(messageId, message);
        }
    
        @Override
        public void onAuthenticationFailed() {
            mCallback.onAuthenticationFailed();
        }
    
        public interface Callback {
    
            void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result);
    
            void onAuthenticationHelp(int messageId, CharSequence message);
    
            void onAuthenticationError(int messageId, CharSequence message);
    
            void onAuthenticationFailed();
        }
    }
    

    这样你就可以在你的FragmentActivity中实现Callback接口,然后开始监听事件:

    private void startListening(boolean cipher) {
        Timber.v("Start listening for fingerprint input");
        mCancellationSignal = new CancellationSignal();
        if(cipher) {
            mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher),
                    0, mCancellationSignal, new FingerprintAuthentication(this), null);
        } else {
            setStage(Stage.CREDENTIALS);
        }
    }
    

    最后,只有指纹认证成功后才能解密密码:

    @Override
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
        try {
            mPassword = decryptPassword(result.getCryptoObject().getCipher());
        } catch (IllegalBlockSizeException | BadPaddingException exception) {
            exception.printStackTrace();
        }
    }
    

    基本上,当用户首次登录时,您希望向他们显示“将来使用指纹”的选项:

    如果用户选择此选项并单击登录,则此时您调用encrypt()。然后,下次要求用户登录时,您会显示指纹对话框:

    这是你打电话给startListening(initializeCipher(Cipher.DECRYPT_MODE))的时候。

    【讨论】:

    • 我在调用开始列表之前加密密码并在 onAuthenticationSuccess 回调中解密,我到底需要做什么
    • @TusharPurohit 您无需创建CryptoObject,或在加密时对FingerprintManager 执行任何操作。您需要做的就是确保密钥对已初始化,并在ENCRYPT_MODE 中初始化Cipher,然后在您的字符串上调用encrypt。这将允许您避免IllegalArgumentException,您添加的其余代码是不必要的。
    • 激活时是否需要记住共享偏好中的加密密码?否则我们如何在解密时创建密码。
    【解决方案2】:

    在加密模式下,您正在使用不是 Android Keystore 密钥的公钥 (unrestricted) 初始化 Cipher 实例。要解决此问题,您可以使用 Android Keystore 公钥(即 mKeyStore.getCertificate(KEY_NAME).getPublicKey() 而不是 unrestricted)对其进行初始化。

    但是,通过对用户授权进行公共密钥加密,您会获得什么并不清楚。任何人都可以在没有用户授权的情况下执行加密操作,因为加密使用根据定义不是秘密的公钥。在非对称加密中,唯一涉及私钥(根据定义不公开)的操作是解密和签名。因此,通常只有对用户授权进行门控解密或签名才有意义。

    【讨论】:

    • 我是问题中提到的原始代码的作者。 unrestricted 密码的原因是由于 Android 6.0 中的一个错误导致公钥被锁定在指纹认证后面。只有私钥应该以这种方式锁定,使用公钥的副本是解决此错误的方法。
    • 这里的主要问题是在指纹认证中使用公钥(而不是私钥或私钥)的门控加密操作可能是错误的。公钥不是秘密的,因此任何人都可以执行相同的操作,而无需用户授权。
    • @Bryan 所以在加密密码时我需要从初始化调用中获得不受限制的密钥吗,你能提供加密和解密流程的详细代码吗
    • @AlexKlyubin 你说的对,指纹认证做公开操作是不对的。我在回答中解决了这个问题。
    • 如果我们要求用户使用指纹进行身份验证才能在我们的应用程序中启用指纹身份验证,那么正确的工作流程是什么?这是我们无法回避的要求。我所做的是将公钥与指纹管理器一起使用,然后一旦完成,我就用该公钥加密用户密码。但是现在在 Android 8 上它给出了这个错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-24
    • 1970-01-01
    • 2015-07-16
    • 2011-11-22
    • 2019-04-27
    • 1970-01-01
    相关资源
    最近更新 更多