Android指纹API加密和解密

Android指纹API加密和解密,android,encryption,android-keystore,android-fingerprint-api,Android,Encryption,Android Keystore,Android Fingerprint Api,我使用安卓M指纹API允许用户登录到应用程序。为此,我需要在设备上存储用户名和密码。目前,我有登录工作,以及指纹API,但用户名和密码都存储为明文。我希望在存储密码之前对其进行加密,并在用户使用指纹进行身份验证后能够检索密码 我有一个很大的困难让这个工作。我一直在尝试应用我能从中得到的,但每个示例似乎只处理加密或签名,而从不处理解密 到目前为止,我拥有的是一个AndroidKeyStore的实例,一个KeyPairGenerator和一个Cipher,使用非对称加密技术来允许Android的使用

我使用安卓M指纹API允许用户登录到应用程序。为此,我需要在设备上存储用户名和密码。目前,我有登录工作,以及指纹API,但用户名和密码都存储为明文。我希望在存储密码之前对其进行加密,并在用户使用指纹进行身份验证后能够检索密码

我有一个很大的困难让这个工作。我一直在尝试应用我能从中得到的,但每个示例似乎只处理加密或签名,而从不处理解密

到目前为止,我拥有的是一个
AndroidKeyStore
的实例,一个
KeyPairGenerator
和一个
Cipher
,使用非对称加密技术来允许Android的使用。非对称加密的原因是,如果用户未经身份验证,则
setUserAuthenticationRequired
方法将阻止密钥的任何使用,但是:

此授权仅适用于密钥和私钥操作。公钥操作不受限制

这应该允许我在用户使用指纹进行身份验证之前使用公钥加密密码,然后仅在用户通过身份验证之后使用私钥解密

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("EC", "AndroidKeyStore");
    } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

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

private void createKey() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

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

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();
            mCipher.init(opmode, key);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | 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());
        String encryptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encryptedPassword);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decryptPassword(Cipher cipher) {
    try {
        String encryptedPassword = mPreferences.getString("password").get();
        byte[] bytes = Base64.decode(encryptedPassword, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}
老实说,我不确定这些是否正确,这些都是我在这个问题上所能找到的零零碎碎的东西。我更改的所有内容都会引发一个不同的异常,而这个特定的构建不会运行,因为我无法实例化
密码
,它会引发
NoSuchAlgorithmException:找不到EC的提供程序
。我也尝试过切换到
RSA
,但我也遇到了类似的错误

所以我的问题基本上是这个,;如何在Android上加密明文,并在用户通过指纹API认证后使其可用于解密


我取得了一些进展,主要是由于在文档页面上发现了信息

我保持了
getKeyStore
encryptePassword
decryptPassword
getKeyPairGenerator
getCipher
基本相同,但我将
KeyPairGenerator.getInstance
更改为
“RSA”
“RSA/ECB/OAEPWithSHA-256和mgf1填充”

我还将代码的其余部分改为RSA而不是椭圆曲线,因为据我所知,Java1.7(以及Android)不支持使用EC进行加密和解密。我根据文档页面上的“使用RSA OAEP进行加密/解密的RSA密钥对”示例更改了我的
createKeyPair
方法:

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, 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);
    }
}
我还根据
KeyGenParameterSpec
文档中的已知问题更改了我的
initCipher
方法:

安卓6.0(API级别23)中的一个已知错误导致即使对公钥也强制执行与用户身份验证相关的授权。要解决此问题,请提取公钥材料以在Android密钥库之外使用

现在我可以加密密码,并保存加密的密码。但是当我获得加密密码并尝试解密时,我得到一个
KeyStoreException
未知错误

03-15 10:06:58.074 14702-14702/com.example.app E/LoginFragment: Failed to decrypt password
        javax.crypto.IllegalBlockSizeException
            at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:486)
            at javax.crypto.Cipher.doFinal(Cipher.java:1502)
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251)
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21)
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96)
            at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805)
            at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:148)
            at android.app.ActivityThread.main(ActivityThread.java:5417)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
        Caused by: android.security.KeyStoreException: Unknown error
            at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
            at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
            at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
            at javax.crypto.Cipher.doFinal(Cipher.java:1502) 
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.java:251) 
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.java:21) 
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.java:301) 
            at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.java:96) 
            at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:805) 
            at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:757) 
            at android.os.Handler.dispatchMessage(Handler.java:102) 
            at android.os.Looper.loop(Looper.java:148) 
            at android.app.ActivityThread.main(ActivityThread.java:5417) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

我在上找到了谜题的最后一块,另一个已知的bug导致使用OAEP时,无限制的
公钥
密码
不兼容。解决方法是在初始化时向密码添加新的
OAEPParameterSpec

OAEPParameterSpec spec = new OAEPParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);

mCipher.init(opmode, unrestricted, spec);
以下是最终代码:

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/ECB/OAEPWithSHA-256AndMGF1Padding");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, 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("Failed to generate key pair", exception);
    }
}

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

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).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_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | 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());
        String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encrypted);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

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

嗨,我尝试了更多的代码,其中之一就是你的代码。我得到一个错误“加密原语未初始化”。你能成功地运行代码吗?我需要加密,解密。我决定先使用RSA,但我无法解决我的问题,于是我将代码改为AES。感谢您对@Caleb感兴趣。在我从
密钥库
中获得以前生成的
密钥对
后,我打电话给
解密(密码)
,这是在指纹管理Compat.AuthenticationCallback的OnAuthenticationSuccessed中完成的。AuthenticationCallback@Androidme即使关闭并重新启动应用程序,一切都按预期工作。听起来您可以在每次启动时生成一个新的
PrivateKey
,替换存储在
KeyStore
中的
PrivateKey
。但如果没有看到一些代码,我就无法确定。问一个新问题,我会看一看。@Androidme这是API的一个限制
setUserAuthenticationRequired(true)
要求每次使用
PrivateKey
时都进行身份验证。因此,用户需要重新验证才能使用密钥两次。一个简单的解决方法是将用户名和密码连接成一个由空格分隔的
字符串
,然后将它们一起加密/解密。嘿,你能为完成此操作所需的完整代码创建一个要点吗?我一直在寻找解决这个问题的办法,大约有两个星期了now@TheAndroidDev老实说,我没有很好地将UI代码与加密/解密代码分开,并且代码依赖于Dagger和RxJava,因此制作一个易于重用的gist可能不是一件小事。我会看看我能想出什么。但现在,大部分代码(无匕首)都在我的另一个问题中:。你能回答我的问题吗?
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/ECB/OAEPWithSHA-256AndMGF1Padding");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, 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("Failed to generate key pair", exception);
    }
}

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

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).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_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | 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());
        String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encrypted);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

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