Android指纹API加密解密
Android Fingerprint API Encryption and Decryption
我正在使用 Android M Fingerprint API 允许用户登录应用程序。为此,我需要在设备上存储用户名和密码。目前我有登录工作,以及指纹API,但用户名和密码都以明文形式存储。我想在存储密码之前对其进行加密,并在用户使用他们的指纹进行身份验证后能够检索它。
我很难让它工作。我一直在尝试应用我能从 Android Security samples 中获得的东西,但每个示例似乎只处理加密或签名,而从不处理解密。
到目前为止,我必须获得 AndroidKeyStore
、一个 KeyPairGenerator
和一个 Cipher
的实例,使用非对称加密来允许使用 Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true)
。非对称加密的原因是因为如果用户未通过身份验证,setUserAuthenticationRequired
方法将阻止 任何 使用密钥,但是:
This authorization applies only to secret key and private key operations. Public key operations are not restricted.
这应该允许我在用户使用他们的指纹进行身份验证之前使用 public 密钥加密密码,然后仅在用户通过身份验证后使用私钥解密。
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);
}
}
老实说,我不确定这是否正确,这是我能找到的关于该主题的所有内容的零碎内容。我更改的所有内容都会引发不同的异常,而这个特定的构建不会 运行 因为我无法实例化 Cipher
,它会引发 NoSuchAlgorithmException: No provider found for EC
。我也试过切换到 RSA
,但我得到了类似的错误。
所以我的问题基本上是这样的;如何在 Android 上加密明文,并在用户通过指纹 API 验证后使其可用于解密?
我取得了一些进步,主要是由于在 KeyGenParameterSpec
文档页面上发现了信息。
我保持 getKeyStore
、encryptePassword
、decryptPassword
、getKeyPairGenerator
和 getCipher
基本相同,但我更改了 KeyPairGenerator.getInstance
和 Cipher.getInstance
分别变为 "RSA"
和 "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
。
我还将其余代码更改为 RSA 而不是椭圆曲线,因为据我了解,Java 1.7(因此 Android)不支持使用 EC 进行加密和解密。我根据文档页面上的 "RSA key pair for encryption/decryption using RSA OAEP" 示例更改了 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
方法:
A known bug in Android 6.0 (API Level 23) causes user authentication-related authorizations to be enforced even for public keys. To work around this issue extract the public key material to use outside of Android Keystore.
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()));
mCipher.init(opmode, unrestricted);
} 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);
}
}
现在我可以加密密码,并保存加密后的密码。但是当我获得加密密码并尝试解密时,我得到一个 KeyStoreException
Unknown error...
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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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)
我在 Android Issue Tracker 上找到了最后一块拼图,另一个已知错误会导致在使用 OAEP 时不受限制的 PublicKey
与 Cipher
不兼容。解决方法是在初始化时向 Cipher
添加一个新的 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);
}
}
我正在使用 Android M Fingerprint API 允许用户登录应用程序。为此,我需要在设备上存储用户名和密码。目前我有登录工作,以及指纹API,但用户名和密码都以明文形式存储。我想在存储密码之前对其进行加密,并在用户使用他们的指纹进行身份验证后能够检索它。
我很难让它工作。我一直在尝试应用我能从 Android Security samples 中获得的东西,但每个示例似乎只处理加密或签名,而从不处理解密。
到目前为止,我必须获得 AndroidKeyStore
、一个 KeyPairGenerator
和一个 Cipher
的实例,使用非对称加密来允许使用 Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true)
。非对称加密的原因是因为如果用户未通过身份验证,setUserAuthenticationRequired
方法将阻止 任何 使用密钥,但是:
This authorization applies only to secret key and private key operations. Public key operations are not restricted.
这应该允许我在用户使用他们的指纹进行身份验证之前使用 public 密钥加密密码,然后仅在用户通过身份验证后使用私钥解密。
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);
}
}
老实说,我不确定这是否正确,这是我能找到的关于该主题的所有内容的零碎内容。我更改的所有内容都会引发不同的异常,而这个特定的构建不会 运行 因为我无法实例化 Cipher
,它会引发 NoSuchAlgorithmException: No provider found for EC
。我也试过切换到 RSA
,但我得到了类似的错误。
所以我的问题基本上是这样的;如何在 Android 上加密明文,并在用户通过指纹 API 验证后使其可用于解密?
我取得了一些进步,主要是由于在 KeyGenParameterSpec
文档页面上发现了信息。
我保持 getKeyStore
、encryptePassword
、decryptPassword
、getKeyPairGenerator
和 getCipher
基本相同,但我更改了 KeyPairGenerator.getInstance
和 Cipher.getInstance
分别变为 "RSA"
和 "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
。
我还将其余代码更改为 RSA 而不是椭圆曲线,因为据我了解,Java 1.7(因此 Android)不支持使用 EC 进行加密和解密。我根据文档页面上的 "RSA key pair for encryption/decryption using RSA OAEP" 示例更改了 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
方法:
A known bug in Android 6.0 (API Level 23) causes user authentication-related authorizations to be enforced even for public keys. To work around this issue extract the public key material to use outside of Android Keystore.
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()));
mCipher.init(opmode, unrestricted);
} 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);
}
}
现在我可以加密密码,并保存加密后的密码。但是当我获得加密密码并尝试解密时,我得到一个 KeyStoreException
Unknown error...
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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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.onAuthenticationSucceeded(FingerprintManagerCompat.java:301)
at android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23.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)
我在 Android Issue Tracker 上找到了最后一块拼图,另一个已知错误会导致在使用 OAEP 时不受限制的 PublicKey
与 Cipher
不兼容。解决方法是在初始化时向 Cipher
添加一个新的 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);
}
}