Question may be long but I will try to describe it in detail.
Here is a demo has issue like mine.
I have an android app and I want to add a function, which allow user to encrypt and save their passwords in SharedPreferences, and read and decrpty them from SharedPreferences. It's only available when the fingerprint has enrolled and fingerprint valid can be used as verification way to get these passwords.
when store:
user input password
create encrpty mode cipher by SecretKey generated by AndroidKeyStore
public Cipher getEncryptCipher() {
try {
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher;
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt pin ", e);
}
}
FingerprintManager valid the cipher(Cipher A) and get real encrpty cipher (Cipher B)
//mCryptoObject is generated by cipher in step 2
mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, FingerprintManager.AuthenticationCallback, null);
//in FingerprintManager.AuthenticationCallback
javax.crypto.Cipher cipher = result.getCryptoObject().getCipher();
encrpty password by the cipher(Cipher B) supported from FingerprintManager
store encrypted password and cipher iv in SharedPreferences
//In my app, we have many device, every could have one password. Pin is password.
public void encryptPin(String deviceId, String pin, javax.crypto.Cipher cipher) {
try {
if (cipher == null) return;
byte[] encryptedBytes = cipher.doFinal(pin.getBytes("utf-8"));
byte[] cipherIv = cipher.getIV();
String encodedPin = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
String encodeCipherIv = Base64.encodeToString(cipherIv, Base64.DEFAULT);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(createPinKey(deviceId), encodedPin);
editor.putString(createIvKey(deviceId), encodeCipherIv);
editor.apply();
} catch (IOException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("Failed to encrypt pin ", e);
}
}
when read:
read cipher iv and create decrpty mode cipher(Cipher C)
public Cipher getDecryptCipher(String deviceId) {
try {
String encodedIv = mSharedPreferences.getString(createIvKey(deviceId), "");
byte[] cipherIv = Base64.decode(encodedIv, Base64.DEFAULT);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
mKeyStore.load(null);
SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(cipherIv));
return cipher;
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt pin ", e);
}
}
valid fingerprint and get the real decrypt cipher(Cipher D)
//mCryptoObject is generated by cipher in step 1
mFingerprintManager.authenticate(mCryptoObject, mCancellationSignal, 0 /* flags */, FingerprintManager.AuthenticationCallback, null);
//in FingerprintManager.AuthenticationCallback
javax.crypto.Cipher cipher = result.getCryptoObject().getCipher();
read encrypted password and decrypt it by the real decrypt cipher(Cipher D)
public String decryptPin(String deviceId, javax.crypto.Cipher cipher) {
String encryptedPin = mSharedPreferences.getString(createPinKey(deviceId), "");
if (TextUtils.isEmpty(encryptedPin)) {
return "";
}
try {
if (cipher == null) return "";
byte[] decodedBytes = Base64.decode(encryptedPin, Base64.DEFAULT);
//BadPaddingException in this line
byte[] decryptBytes = cipher.doFinal(decodedBytes);
return new String(decryptBytes, Charset.forName("UTF8"));
} catch (Exception e) {
MyLog.d(TAG, "Failed to decrypt the data with the generated key." + e.getMessage());
e.printStackTrace();
return "";
}
}
My init method:
private void init() {
try {
mKeyStore = KeyStore.getInstance("AndroidKeyStore");
mKeyStore.load(null);
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(true);
}
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
builder.build());
keyGenerator.generateKey();
} catch (Exception e) {
throw new RuntimeException("Fail to init:" + e);
}
}
Everything is OK if not closing app!!!
However when I kill the process and restart it, I got BadPaddingException in decryptPin() method when do cipher.doFinal().
System.err: javax.crypto.BadPaddingException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:482)
at javax.crypto.Cipher.doFinal(Cipher.java:1502)
at com.xiaomi.smarthome.framework.page.verify.DeviceVerifyConfigCache.decryptPin(DeviceVerifyConfigCache.java:156)
at com.xiaomi.smarthome.framework.page.verify.VerifyManager.decryptPin(VerifyManager.java:173)
at com.xiaomi.smarthome.framework.page.verify.FingerPrintVerifyActivity$1.onAuthenticated(FingerPrintVerifyActivity.java:62)
at com.xiaomi.smarthome.framework.page.verify.view.FingerPrintOpenVerifyDialog.onAuthenticationSucceeded(FingerPrintOpenVerifyDialog.java:136)
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:5458)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
Caused by: android.security.KeyStoreException: Invalid argument
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)
... 13 more
Does anyone can help me to solve this problem? Does is caused by SecretKey?Thank you!!!
I have found my issue. It's my wrong to generate Key with same alias in my init() method. I solve the problem with adding a judgement condition。
if (!mKeyStore.containsAlias(KEY_NAME)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
builder.build());
keyGenerator.generateKey();
}
Related
I am facing issues when using AES/GCM/NoPadding, after a particular range its throwing exception and its not encrypting after a range of 65536.I have text length of 131072 , but the encryption is only happeing till 65536.
even tried the following as well,
AES/GCM/NoPadding
AES/CBC/pkcs7padding.but after a particular range it is throwing invalidexception.
**Note:**
If I use BouncyCastle provider it is working fine(encryption is happening). But when I use Android keystore with keyspec is not working.
even tried the following as well,
AES/GCM/NoPadding
AES/CBC/pkcs7padding.but after a particular range it is throwing invalidexception.
code:
public static EncryptCallBack encryptText(final byte[] textToEncrypt)
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
InvalidAlgorithmParameterException, SignatureException, BadPaddingException,
IllegalBlockSizeException {
EncryptCallBack encryptCallBack = new EncryptCallBack();
final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
/*Cipher update for Large data and Incremental Encryption */
byte[] encryptedValue = null;
byte[] input = textToEncrypt;
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = 0;
try {
ctLength = cipher.update(input, 0, input.length, cipherText, 0);
Log.d("Update ciper","Update"+ ctLength);
} catch (ShortBufferException e) {
e.printStackTrace();
}
try {
ctLength += cipher.doFinal(cipherText, ctLength);
} catch (ShortBufferException e) {
e.printStackTrace();
}
encryptedValue = cipherText;
Log.d("Encrypted data Length","Encyrptedlength :"+ encryptedValue.length);
/*Cipher update for Large data and Incremental Encryption */
encryptCallBack.setSpecIV(cipher.getIV());
encryptCallBack.setData(encryptedValue);
return encryptCallBack;
}
/*SecertKey*/
public static SecretKey getSecretKey() {
SecretKey key = null;
try {
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
} catch (KeyStoreException | CertificateException |
NoSuchAlgorithmException | IOException e) {
}
if (!keyStore.containsAlias("alias")) {
KeyGenerator keygen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("alias", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
keygen.init(spec);
key = keygen.generateKey();
} else {
KeyStore.SecretKeyEntry keyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry("alias", null);
key = keyEntry.getSecretKey();
}
} catch (Exception e) {
}
return key;
}
public static String decryptText(final byte[] encryptedData, byte[] specIV)
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException,
BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, specIV);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(),spec);
return new String(cipher.doFinal(encryptedData), "UTF-8");
}
if anyone come across this kind of situation. please assist.
I'm trying to encrypt data use Cipher with SecretKey loaded in KeyStore but always got this error :
Caused by: android.security.KeyStoreException: Key user not authenticated
I tried creating SecretKeySpec myself it worked. I'm using android Q to test.
Can anyone help me explain the problem?
My code
public Cipher createCipher() {
Cipher cipher;
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);
}
return cipher;
}
public boolean initCipher() {
try {
cipher = createCipher();
SecretKey key = KeyStoreTools.getSecretKey(KEY_NAME);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
KeyStoreTools.removeAlias(KEY_NAME);
return false;
} catch (Throwable e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
public static SecretKey getSecretKey(String keyName) {
Key key = getKey(keyName);
if (!keyExists(key)) {
key = generateKey(keyName);
}
return (SecretKey) key;
}
public static SecretKey generateKey(String keyName) {
SecretKey secretKey = null;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
builder.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7);
keyGenerator.init(builder.build());
secretKey = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | NoSuchProviderException exc) {
exc.printStackTrace();
} catch (Throwable e) {
throw new RuntimeException("Failed to generateKey", e);
}
return secretKey;
}
public String encrypt(Cipher cipher, byte[] dataToEncrypt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(dataToEncrypt);
byte[] enc = cipher.doFinal(md.digest());
String base64 = Base64.encodeToString(enc, Base64.DEFAULT);
return base64;
} catch (BadPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException e ) {
e.printStackTrace();
}
return "";
}
My issue throw because :
I encrypt using the cipher before the key has been flagged for successful user authentication. So move encrypt in onAuthenticationSucceeded and it's worked.
Hope my answer will help someone else.
i have the source code below to generate key and initialize the Chiper for the encryption later after passing the fingerprint authentication (with some global variable)
private KeyStore keyStore;
// Variable used for storing the key in the Android Keystore container
private static final String KEY_NAME = "androidHive";
private Cipher cipher;
private TextView textView;
#TargetApi(Build.VERSION_CODES.M)
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
KeyGenerator keyGenerator;
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);
}
}
#TargetApi(Build.VERSION_CODES.M)
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);
}
}
and the function is called like this after the fingerprint authentication code in fingerprint activity
generateKey();
if (cipherInit()) {
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
FingerprintHandler helper = new FingerprintHandler(this);
helper.startAuth(fingerprintManager, cryptoObject);
}
and this is the FingerprintHandler code
private Context context;
// Constructor
public FingerprintHandler(Context mContext) {
context = mContext;
}
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
#Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
this.update("Fingerprint Authentication error\n" + errString);
}
#Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
this.update("Fingerprint Authentication help\n" + helpString);
}
#Override
public void onAuthenticationFailed() {
this.update("Fingerprint Authentication failed.");
}
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
((Activity) context).finish();
Intent intent = new Intent(context, HomeActivity.class);
context.startActivity(intent);
}
private void update(String e){
TextView textView = (TextView) ((Activity)context).findViewById(R.id.errorText);
textView.setText(e);
}
after the authentication success in directing the activity to HomeActivity and i want to encrypt something with key that already generated and Chiper that already initialized earlier in the HomeActivity but i dont know how to call the key and Chiper that already generated before, any example how to do the encryption after the fingerprint authentication ? its for my final project on college, so i'm really appreciated anyone who can answer this
Once you generated the key in the keystore it's pretty easy. You just need to use the same initialization vector (IV) to encrypt and decrypt.
// encryption
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ivBytes = cipher.getIV();
String dataToEncrypt = "Super secret data";
byte[] data = dataToEncrypt.getBytes();
byte[] encryptedData = cipher.doFinal(data);
// decryption
cipher.init(Cipher.DECRYPT_MODE, key, ivBytes);
byte[] decryptedData = cipher.doFinal(encryptedData);
String originalData = new String(decryptedData);
EDIT :
I'm not sure you can pass the Cipher through activities, but you can retrieve the key in the KeyStore from any activity for sure. You just need the keyname you used to generate the key :
SecretKey cipherKey = (SecretKey) keyStore.getKey("keyName", null);
Then you need to initialize the Cipher again.
Important : Every time you want to encrypt a data you need to initialize the Cipher, otherwise it will throw an exception telling that you can't use two times the same initialization vector (IV) for encryption.
I'm creating an app where the user has two options to unlock their app, one is using a pin and the other is using a fingerprint. In order to use the fingerprint they must first set up a pin because this pin is the decryption key to get their encrypted details out of SharedPreferences.
So i've followed this tutorial here: http://www.techotopia.com/index.php/An_Android_Fingerprint_Authentication_Tutorial#Accessing_the_Android_Keystore_and_KeyGenerator
I've managed to get the app to read a fingerprint and say whether it is valid or not. But when the fingerprint is authorised I have no idea how to get that pin out of the Android keystore.
Here is some code to demonstrate:
#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 is the key(pin) i'm trying to store (I think).
Then in the FingerprintHandler class there is this method:
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {
Toast.makeText(appContext,
"Authentication succeeded.",
Toast.LENGTH_LONG).show();
}
But how do i get the key i want out of the result if at all?
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.
I'm trying to Encrypt & Decrypt the String using AES Algorithm & GCM mode.
My code is able to encrypt the string but i'm not able to decrypt the encoded data.
Steps followed :
Create Key()
Encrypt(string)
Decrypt(encded_data);
My Code :
Create Key
public void createKey() {
try {
if (!ks.containsAlias(keyName)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Log.e("MAinAcvtivity", "Current version is 23(MashMello)");
//Api level 23
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
generator.init(
new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey key = generator.generateKey();
} else {
Log.e("MAinAcvtivity", "Current version is < 23(MashMello)");
}
}else{
Log.e("MAinAcvtivity", "Key exist");
}
} catch (Exception e) {
Log.e("MAinAcvtivity", "Key didn't generated");
Log.e("MAinAcvtivity", Log.getStackTraceString(e));
}
}
Encryption :
public String doEncryption(String data) {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
ce = Cipher.getInstance("AES/GCM/NoPadding");
sKey = (SecretKey) ks.getKey(keyName, null);
ce.init(Cipher.ENCRYPT_MODE, sKey);
} else {
}
encodedData = ce.doFinal(data.getBytes());
mEncodedData = Base64.encodeToString(encodedData, Base64.DEFAULT);
} catch (Exception e) {
Log.e("Main", "RSA Encription Error.!");
Log.e("MainActivity", "RSA Decryption Error.!", e);
}
Log.e("Main", "encripted DATA =" + mEncodedData);
return mEncodedData;
}
Decryption :
public String doDecryption(String eData) {
String decryptedText = null;
encodedData = Base64.decode(eData, Base64.DEFAULT);
try {
Cipher c;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Log.i("MainActivity","in Decryption version m");
c = Cipher.getInstance("AES/GCM/NoPadding");
sKey = (SecretKey) ks.getKey(keyName, null);
Log.e("MainActivity", "After getting key : " );
c.init(Cipher.DECRYPT_MODE, sKey);
} else {
}
decodedData = c.doFinal(encodedData);
decryptedText = new String(decodedData, "UTF-8");
Log.e("MainActivity", "After decryption : "+ decryptedText);
} catch (Exception e) {
Log.e("MainActivity", "RSA Decryption Error.!", e);
}
return decryptedText;
}
Error Log :
MAinAcvtivity: Key exist
Main: encripted DATA =KrHmMXhcytb0owDzLaMY2wsQmwY=
MainActivity: in decryption : encoded data =KrHmMXhcytb0owDzLaMY2wsQmwY=
MainActivity: After getting key :
MainActivity: RSA Decryption Error.!
MainActivity: java.security.InvalidKeyException: IV required when decrypting. Use IvParameterSpec or AlgorithmParameters to provide it.
MainActivity: at android.security.keystore.AndroidKeyStoreAuthenticatedAESCipherSpi$GCM.initAlgorithmSpecificParameters(AndroidKeyStoreAuthenticatedAESCipherSpi.java:79)
MainActivity: at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:106)
Bingooo!
I got the solution.
In decryption i made this changes :
GCMParameterSpec spec = new GCMParameterSpec(128,ce.getIV());
c.init(Cipher.DECRYPT_MODE, sKey,spec);
This is tagsize calculation code that I used for AES/GCM mode. I don't prefer static usage.
byte[] pinBytes = pin.getBytes();
byte[] bytes = cipher.doFinal(pinBytes);
tagLenght= bytes.length - pin.length;