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.
Related
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.
Need to implement end-to-end encryption on an app. All data sent and received is going to be encrypted. Need to send symmetric secret key generated on app and stored in the Android Keystore to the server after encrypting it with the public key which I get from the server. Problem : I am unable to get the string/bytes value of the SecretKey which I want to encrypt.
I have a helper class which does the work of generating the SecretKey and stores and retrieves it when required from the keystore. Data gets correctly encrypted and decrypted, however due to the requirement of end-end encryption(all data in transit must also be encrypted), I need to send the secretkey to the server, but I cannot get access to it.
There are three getter methods in the SecretKey class.
getAlgortithm - This returns "AES" as expected
getEncoded - This returns null
getFormat - This also returns null
public class EncryptionKeyGenerator {
public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
public static final String KEY_ALIAS = "KEY_ALIAS";
private static final String KEY_STORE_FILE_NAME = "KEY_STORE";
private static final String KEY_STORE_PASSWORD = "KEY_STORE_PASSWORD";
#TargetApi(Build.VERSION_CODES.M)
static SecurityKey generateSecretKey(KeyStore keyStore) {
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(
KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build());
return new SecurityKey(keyGenerator.generateKey());
}
} catch (KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
CommonMethods.printStackTrace(e);
}
try {
final KeyStore.SecretKeyEntry entry =
(KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
return new SecurityKey(entry.getSecretKey());
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
static SecurityKey generateKeyPairPreM(Context context, KeyStore keyStore) {
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
//1 Year validity
end.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context).setAlias(KEY_ALIAS)
.setSubject(new X500Principal("CN=" + KEY_ALIAS))
.setSerialNumber(BigInteger.TEN)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", ANDROID_KEY_STORE);
kpg.initialize(spec);
kpg.generateKeyPair();
}
} catch (KeyStoreException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
CommonMethods.printStackTrace(e);
}
try {
final KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
return new SecurityKey(
new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()));
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
static SecurityKey generateSecretKeyPre18(Context context) {
try {
KeyStore androidCAStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = KEY_STORE_PASSWORD.toCharArray();
boolean isKeyStoreLoaded = loadKeyStore(context, androidCAStore, password);
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(password);
if (!isKeyStoreLoaded || !androidCAStore.containsAlias(KEY_ALIAS)) {
//Create and save new secret key
saveMyKeystore(context, androidCAStore, password, protParam);
}
// Fetch Secret Key
KeyStore.SecretKeyEntry pkEntry =
(KeyStore.SecretKeyEntry) androidCAStore.getEntry(KEY_ALIAS, protParam);
CommonMethods.printLog("e", "Secret Key Fetched :" + new String(pkEntry.getSecretKey().getEncoded(), "UTF-8"), EncryptionKeyGenerator.class.getSimpleName());
return new SecurityKey(pkEntry.getSecretKey());
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
private static boolean loadKeyStore(Context context, KeyStore androidCAStore, char[] password) {
java.io.FileInputStream fis;
try {
fis = context.openFileInput(KEY_STORE_FILE_NAME);
} catch (FileNotFoundException e) {
CommonMethods.printStackTrace(e);
return false;
}
try {
androidCAStore.load(fis, password);
return true;
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
CommonMethods.printStackTrace(e);
}
return false;
}
private static void saveMyKeystore(Context context, KeyStore androidCAStore, char[] password,
KeyStore.ProtectionParameter protParam)
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
javax.crypto.SecretKey mySecretKey = KeyGenerator.getInstance("AES").generateKey();
KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(mySecretKey);
androidCAStore.load(null);
androidCAStore.setEntry(KEY_ALIAS, skEntry, protParam);
java.io.FileOutputStream fos = null;
try {
fos = context.openFileOutput(KEY_STORE_FILE_NAME, Context.MODE_PRIVATE);
androidCAStore.store(fos, password);
} finally {
if (fos != null) {
fos.close();
}
}
CommonMethods.printLog("e", "Secret Key Saved : " + new String(mySecretKey.getEncoded(), "UTF-8"), EncryptionKeyGenerator.class.getSimpleName());
}
}
class SecurityKey {
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String AES_MODE_FOR_POST_API_23 = "AES/GCM/NoPadding";
private static final String AES_MODE_FOR_PRE_API_18 = "AES/CBC/PKCS5Padding";
private SecretKey secretKey;
private KeyPair keyPair;
SecurityKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
SecurityKey(KeyPair keyPair) {
this.keyPair = keyPair;
}
String encrypt(String token) {
if (token == null) return null;
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
byte[] encrypted = cipher.doFinal(token.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
} catch (GeneralSecurityException e) {
CommonMethods.printStackTrace(e);
}
//Unable to encrypt Token
return null;
}
String decrypt(String encryptedToken) {
if (encryptedToken == null) return null;
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
byte[] decoded = Base64.decode(encryptedToken, Base64.DEFAULT);
byte[] original = cipher.doFinal(decoded);
return new String(original);
} catch (GeneralSecurityException e) {
CommonMethods.printStackTrace(e);
}
//Unable to decrypt encrypted Token
return null;
}
private Cipher getCipher(int mode) throws GeneralSecurityException {
Cipher cipher;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cipher = Cipher.getInstance(AES_MODE_FOR_POST_API_23);
cipher.init(mode, secretKey, new GCMParameterSpec(128, AES_MODE_FOR_POST_API_23.getBytes(), 0, 12));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
cipher = Cipher.getInstance(RSA_MODE);
cipher.init(mode, mode == Cipher.DECRYPT_MODE ? keyPair.getPublic() : keyPair.getPrivate());
} else {
cipher = Cipher.getInstance(AES_MODE_FOR_PRE_API_18);
cipher.init(mode, secretKey, new IvParameterSpec(new byte[cipher.getBlockSize()]));
}
return cipher;
}
}
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.
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();
}
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;