Skip to content

Instantly share code, notes, and snippets.

@JosiasSena
Last active September 12, 2023 12:40
Show Gist options
  • Save JosiasSena/3bf4ca59777f7dedcaf41a495d96d984 to your computer and use it in GitHub Desktop.
Save JosiasSena/3bf4ca59777f7dedcaf41a495d96d984 to your computer and use it in GitHub Desktop.
Encryptor and Decryptor for data encryption.decryption using the Android KeyStore.
/**
_____ _____ _
| __ \ / ____| | |
| | | | ___| | _ __ _ _ _ __ | |_ ___ _ __
| | | |/ _ \ | | '__| | | | '_ \| __/ _ \| '__|
| |__| | __/ |____| | | |_| | |_) | || (_) | |
|_____/ \___|\_____|_| \__, | .__/ \__\___/|_|
__/ | |
|___/|_|
*/
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();
}
}
}
@IgorGanapolsky
Copy link

IgorGanapolsky commented May 24, 2018

I am curious, why did you choose this transformation specifically :AES/GCM/NoPadding ?

@ebabel
Copy link

ebabel commented Jun 4, 2018

@JosiasSena You're code should be able to encrypt multiple values, right? I can't get that to work and not sure what I'm doing wrong.

    val enCryptor = EnCryptor()
    val alias = "My_AndroidKeyStore_Alias"
    val encryptText = enCryptor.encryptText(alias, "asdfasdfasdf")
    val iv = enCryptor.getIv()

    val deCryptor = DeCryptor()
    deCryptor.decryptData(alias, encryptText, iv).also { println("Decrypted1: $it") }

    // val enCryptor2 = enCryptor // also tried this, no difference
    val enCryptor2 = EnCryptor()
    val encryptText2 = enCryptor2.encryptText(alias, "asdfasdfasdf3")

    val iv2 = enCryptor2.getIv()
    deCryptor.decryptData(alias, encryptText2, iv2).also { println("Decrypted2: $it") }

    deCryptor.decryptData(alias, encryptText, iv).also { println("Decrypted1: $it") } 
    // Caused by: android.security.KeyStoreException: Signature/MAC verification failed

@raptus93
Copy link

raptus93 commented Aug 9, 2018

@JosiasSena
I get an AEADBadTagException trying to in the decrypt method, when calling:
return new String(cipher.doFinal(encryptedBytes), "UTF-8");

The android documentation of Cipher says:

if this cipher is decrypting in an AEAD mode (such as GCM/CCM), and the received authentication tag does not match the calculated value

Can you please explain this problem and its solution? What could be wrong?

Thank you!

@anbarasu-seven
Copy link

working excellent at first run. Very useful.

@saulai
Copy link

saulai commented Nov 2, 2018

@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.

@jollyjoker992
Copy link

@JosiasSena
I get an AEADBadTagException trying to in the decrypt method, when calling:
return new String(cipher.doFinal(encryptedBytes), "UTF-8");

The android documentation of Cipher says:

if this cipher is decrypting in an AEAD mode (such as GCM/CCM), and the received authentication tag does not match the calculated value

Can you please explain this problem and its solution? What could be wrong?

Thank you!

Similar to me. Looking for response

@gruschus
Copy link

@JosiasSena
I get an AEADBadTagException trying to in the decrypt method, when calling:
return new String(cipher.doFinal(encryptedBytes), "UTF-8");
The android documentation of Cipher says:

if this cipher is decrypting in an AEAD mode (such as GCM/CCM), and the received authentication tag does not match the calculated value

Can you please explain this problem and its solution? What could be wrong?
Thank you!

Similar to me. Looking for response

I have the same problem. the AEADBadTagException appears to be triggered by the following exception

Caused by: android.security.KeyStoreException: Signature/MAC verification failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:682)

Android 7

@githubmss
Copy link

I have a requirement I will get key from server and then I need to store that inside my AndroidKeystore and use it to encrypt and Decrypt my database , so by this private static final String SAMPLE_ALIAS = "MYALIAS"; does it means it can get any key and based on this it will generate private key to encrypt and decrypt the data ?

@tamirshina
Copy link

@JosiasSena
I get an AEADBadTagException trying to in the decrypt method when calling:
return new String(cipher.doFinal(encryptedBytes), "UTF-8");

Same problem here - you can check it out at https://stackoverflow.com/questions/54077125/aeadbadtagexception-when-decrypting-using-androidkeystore

@FedericoBotta
Copy link

@JosiasSena
I get an AEADBadTagException trying to in the decrypt method, when calling:
return new String(cipher.doFinal(encryptedBytes), "UTF-8");
The android documentation of Cipher says:

if this cipher is decrypting in an AEAD mode (such as GCM/CCM), and the received authentication tag does not match the calculated value

Can you please explain this problem and its solution? What could be wrong?
Thank you!

Similar to me. Looking for response

I have the same problem. the AEADBadTagException appears to be triggered by the following exception

Caused by: android.security.KeyStoreException: Signature/MAC verification failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:682)

Android 7

Yes, I am also having the same problem of the AEADBadTagException caused by Signature/MAC verification failed, on Android 8.

@virtualpathum
Copy link

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?

@ravikanasagra1
Copy link

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?

@kherembourg
Copy link

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 !

@bung428
Copy link

bung428 commented Jul 20, 2019

Thank you so much, i'm sorry

@cas4ey
Copy link

cas4ey commented Dec 27, 2019

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?

@msramalho
Copy link

msramalho commented Jan 13, 2020

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

@sinhpn92
Copy link

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?

@eoinahern
Copy link

@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.

@raulland08
Copy link

@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.

@sharmpuneet
Copy link

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.

@shubham696
Copy link

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

@ankitbaderiya
Copy link

ankitbaderiya commented Oct 4, 2020

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();
    }

@ankitbaderiya
Copy link

And how to delete key?

@Matthcw
Copy link

Matthcw commented Apr 8, 2021

Hi, what is the license on this code?

@JosiasSena
Copy link
Author

@Matthcw none, feel free to do whatever you want with it

@Matthcw
Copy link

Matthcw commented Apr 9, 2021 via email

@javimar
Copy link

javimar commented Sep 22, 2021

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!!

@AMoeid7
Copy link

AMoeid7 commented Jun 27, 2022

it gave error javax.crypto.illegalblocksizeexception while decrypting the code could you help me out

@Heritiana-william
Copy link

Thanks for sharing! This code is really helpful!
Anyway, can i use this code for encrypting/decrypting android fingerprint information (data)?

@luskan
Copy link

luskan commented Jul 13, 2023

If someone needs it, I have created Example app for this code: https://github.com/luskan/EncryptDecryptApp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment