-
-
Save JosiasSena/3bf4ca59777f7dedcaf41a495d96d984 to your computer and use it in GitHub Desktop.
/** | |
_____ _____ _ | |
| __ \ / ____| | | | |
| | | | ___| | _ __ _ _ _ __ | |_ ___ _ __ | |
| | | |/ _ \ | | '__| | | | '_ \| __/ _ \| '__| | |
| |__| | __/ |____| | | |_| | |_) | || (_) | | | |
|_____/ \___|\_____|_| \__, | .__/ \__\___/|_| | |
__/ | | | |
|___/|_| | |
*/ | |
class DeCryptor { | |
private static final String TRANSFORMATION = "AES/GCM/NoPadding"; | |
private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; | |
private KeyStore keyStore; | |
DeCryptor() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, | |
IOException { | |
initKeyStore(); | |
} | |
private void initKeyStore() throws KeyStoreException, CertificateException, | |
NoSuchAlgorithmException, IOException { | |
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); | |
keyStore.load(null); | |
} | |
String decryptData(final String alias, final byte[] encryptedData, final byte[] encryptionIv) | |
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException, | |
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException, | |
BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException { | |
final Cipher cipher = Cipher.getInstance(TRANSFORMATION); | |
final GCMParameterSpec spec = new GCMParameterSpec(128, encryptionIv); | |
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias), spec); | |
return new String(cipher.doFinal(encryptedData), "UTF-8"); | |
} | |
private SecretKey getSecretKey(final String alias) throws NoSuchAlgorithmException, | |
UnrecoverableEntryException, KeyStoreException { | |
return ((KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null)).getSecretKey(); | |
} | |
} |
/** | |
______ _____ _ | |
| ____| / ____| | | | |
| |__ _ __ | | _ __ _ _ _ __ | |_ ___ _ __ | |
| __| | '_ \| | | '__| | | | '_ \| __/ _ \| '__| | |
| |____| | | | |____| | | |_| | |_) | || (_) | | | |
|______|_| |_|\_____|_| \__, | .__/ \__\___/|_| | |
__/ | | | |
|___/|_| | |
*/ | |
class EnCryptor { | |
private static final String TRANSFORMATION = "AES/GCM/NoPadding"; | |
private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; | |
private byte[] encryption; | |
private byte[] iv; | |
EnCryptor() { | |
} | |
byte[] encryptText(final String alias, final String textToEncrypt) | |
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException, | |
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException, | |
InvalidAlgorithmParameterException, SignatureException, BadPaddingException, | |
IllegalBlockSizeException { | |
final Cipher cipher = Cipher.getInstance(TRANSFORMATION); | |
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias)); | |
iv = cipher.getIV(); | |
return (encryption = cipher.doFinal(textToEncrypt.getBytes("UTF-8"))); | |
} | |
@NonNull | |
private SecretKey getSecretKey(final String alias) throws NoSuchAlgorithmException, | |
NoSuchProviderException, InvalidAlgorithmParameterException { | |
final KeyGenerator keyGenerator = KeyGenerator | |
.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); | |
keyGenerator.init(new KeyGenParameterSpec.Builder(alias, | |
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) | |
.setBlockModes(KeyProperties.BLOCK_MODE_GCM) | |
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) | |
.build()); | |
return keyGenerator.generateKey(); | |
} | |
byte[] getEncryption() { | |
return encryption; | |
} | |
byte[] getIv() { | |
return iv; | |
} | |
} |
/** | |
_____ _ _ _ | |
/ ____| | | | | | | | |
| (___ __ _ _ __ ___ _ __ | | ___ | | | |___ __ _ __ _ ___ | |
\___ \ / _` | '_ ` _ \| '_ \| |/ _ \ | | | / __|/ _` |/ _` |/ _ \ | |
____) | (_| | | | | | | |_) | | __/ | |__| \__ \ (_| | (_| | __/ | |
|_____/ \__,_|_| |_| |_| .__/|_|\___| \____/|___/\__,_|\__, |\___| | |
| | __/ | | |
|_| |___/ | |
*/ | |
public class MainActivity extends AppCompatActivity { | |
private static final String TAG = MainActivity.class.getSimpleName(); | |
private static final String SAMPLE_ALIAS = "MYALIAS"; | |
@BindView (R.id.toolbar) | |
Toolbar toolbar; | |
@BindView (R.id.ed_text_to_encrypt) | |
EditText edTextToEncrypt; | |
@BindView (R.id.tv_encrypted_text) | |
TextView tvEncryptedText; | |
@BindView (R.id.tv_decrypted_text) | |
TextView tvDecryptedText; | |
private EnCryptor encryptor; | |
private DeCryptor decryptor; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
ButterKnife.bind(this); | |
setSupportActionBar(toolbar); | |
encryptor = new EnCryptor(); | |
try { | |
decryptor = new DeCryptor(); | |
} catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | | |
IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@OnClick ({R.id.btn_encrypt, R.id.btn_decrypt}) | |
public void onClick(final View view) { | |
final int id = view.getId(); | |
switch (id) { | |
case R.id.btn_encrypt: | |
encryptText(); | |
break; | |
case R.id.btn_decrypt: | |
decryptText(); | |
break; | |
} | |
} | |
private void decryptText() { | |
try { | |
tvDecryptedText.setText(decryptor | |
.decryptData(SAMPLE_ALIAS, encryptor.getEncryption(), encryptor.getIv())); | |
} catch (UnrecoverableEntryException | NoSuchAlgorithmException | | |
KeyStoreException | NoSuchPaddingException | NoSuchProviderException | | |
IOException | InvalidKeyException e) { | |
Log.e(TAG, "decryptData() called with: " + e.getMessage(), e); | |
} catch (IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) { | |
e.printStackTrace(); | |
} | |
} | |
private void encryptText() { | |
try { | |
final byte[] encryptedText = encryptor | |
.encryptText(SAMPLE_ALIAS, edTextToEncrypt.getText().toString()); | |
tvEncryptedText.setText(Base64.encodeToString(encryptedText, Base64.DEFAULT)); | |
} catch (UnrecoverableEntryException | NoSuchAlgorithmException | NoSuchProviderException | | |
KeyStoreException | IOException | NoSuchPaddingException | InvalidKeyException e) { | |
Log.e(TAG, "onClick() called with: " + e.getMessage(), e); | |
} catch (InvalidAlgorithmParameterException | SignatureException | | |
IllegalBlockSizeException | BadPaddingException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
Caused by: android.security.KeyStoreException: Signature/MAC verification failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:682)
I was able to fix this error by following the approach which @oliverspryn has mentioned in his comment. However, I have doubts about saving the iv in shared prefs. When an attacker found out the encrypted data which we saved in shared pref, don't we support him by giving the iv as well?
I also received exception on Android 8 javax.crypto.AEADBadTagException due to android.security.KeyStoreException: Signature/MAC verification failed.
@JosiasSena - Have you found fix for this exception?
The solution I found is to store the encrypted data and cypher IV in Base64 and then decode it when needed.
Like the code showed by oliverspryn, only I do it to for the encryptedString
I don't have the AEADBadTagException anymore, it works fine !
Thank you so much, i'm sorry
Why does keyGenerator.generateKey()
invoked on every Encryptor::getSecretKey()
?
Don't you need to check keyStore.getEntry(alias, null) != null
to reuse previously generated key?
New single-file version for more recent
Since I had trouble using this code when developing for Android SDK>=28 (since it gave several errors), I implemented my own version as a standalone file using "AES/CBC/PKCS7Padding"
that can be used like so:
Cryptography c = new Cryptography("CHOOSE_YOUR_KEYNAME_FOR_STORAGE");
String encrypted = c.encrypt("plain text"); // returns base 64 data: 'BASE64_DATA,BASE64_IV'
String decrypted = c.decrypt("encrypted"); // returns "plain text"
It's available in this gist
* This also takes care of proper IV usage and ensures the same key is used even if the user exits and reopens the app
So in this example, We need to store IV
in somewhere, right?. That's can be more risky in secure. Can we do any other without store any data?
@ebabel I also had to create a custom implementation to encrypt several values.
@raptus93 I got that same exception.I guess more context on your implementation would be necessary, but I'll explain what I did in case it helps anyone.
In my case I was encrypting several Strings, save them to preferences, and then try to decrypt all of them resulting in AEADBadTagException.
My mistake: I was building a new KeyGenarator each time encryptText() was called.
The fix was to retrieve the existing entry:if(!keyStore.containsAlias(alias)) { keyGenerator.init(new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); } else { return ((KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null)).getSecretKey(); }@JosiasSena thanks for sharing
Hope it helps.
cheers Bro, Had the same issue with the AEADBadTagException when decrypting a string saved in shareprefs.
@ebabel I also had to create a custom implementation to encrypt several values.
@raptus93 I got that same exception.I guess more context on your implementation would be necessary, but I'll explain what I did in case it helps anyone.
In my case I was encrypting several Strings, save them to preferences, and then try to decrypt all of them resulting in AEADBadTagException.
My mistake: I was building a new KeyGenarator each time encryptText() was called.
The fix was to retrieve the existing entry:if(!keyStore.containsAlias(alias)) { keyGenerator.init(new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); } else { return ((KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null)).getSecretKey(); }@JosiasSena thanks for sharing
Hope it helps.
Hi, can you explain how do you decrypt all of the encrypted Strings? Because I've tried a lot of solutions but I always got the last value encrypted. Thanks in advance.
I also have the same issue on this solution when I try to encrypt and decrypt multiple keys. After decryption, I am getting only last value encrypted.
I also have the same issue with this solution when I try to encrypt and decrypt multiple keys. After decryption, I am getting only the last value encrypted.
@sharmpuneet are you able to solve this
Here's what I have done to handle API < 23:
private SecretKey getSecretKey(final String alias) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidAlgorithmParameterException {
KeyGenerator keyGenerator;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
keyGenerator.init(new KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
} else {
keyGenerator = KeyGenerator.getInstance("AES", ANDROID_KEY_STORE);
SecureRandom secureRandom = new SecureRandom(alias.getBytes());
keyGenerator.init(KEY_SIZE, secureRandom);
}
return keyGenerator.generateKey();
}
And how to delete key?
Hi, what is the license on this code?
@Matthcw none, feel free to do whatever you want with it
Hello, my question is if there is a way to retrieve a stored value with just knowing the alias. The thing is that the method to retreive it is coupled with the encryption and the iv... thanks!!
it gave error javax.crypto.illegalblocksizeexception while decrypting the code could you help me out
Thanks for sharing! This code is really helpful!
Anyway, can i use this code for encrypting/decrypting android fingerprint information (data)?
If someone needs it, I have created Example app for this code: https://github.com/luskan/EncryptDecryptApp
Yes, I am also having the same problem of the AEADBadTagException caused by Signature/MAC verification failed, on Android 8.