Skip to content

Instantly share code, notes, and snippets.

@msramalho
Created January 13, 2020 22:59
Show Gist options
  • Save msramalho/a95b1ea880fa3f3d6f70099ccf72ff62 to your computer and use it in GitHub Desktop.
Save msramalho/a95b1ea880fa3f3d6f70099ccf72ff62 to your computer and use it in GitHub Desktop.
Java Class to manage cryptography keys in Android Keystore using "AES/CBC/PKCS7Padding" - encrypt and decrypt strings

Instructions

Simply import the class and then

String KEY_NAME = "CHOOSE_YOUR_KEYNAME_FOR_STORAGE"

Cryptography c = new Cryptography(KEY_NAME);

String encrypted = c.encrypt("plain text"); // returns base 64 data: 'BASE64_DATA,BASE64_IV'

String decrypted = c.decrypt("encrypted");
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
/**
* Manage cryptographic key in keystore
* requires previous user authentication to have been performed
*/
public class Cryptography {
private static final String TRANSFORMATION = KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7;
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String SEPARATOR = ",";
private String keyName;
private KeyStore keyStore;
private SecretKey secretKey;
public Cryptography(String keyName) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, NoSuchProviderException, InvalidAlgorithmParameterException {
this.keyName = keyName;
initKeystore();
loadOrGenerateKey();
}
private void loadOrGenerateKey() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
getKey();
if (secretKey == null) generateKey();
}
private void initKeystore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
}
private void getKey() {
try {
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(keyName, null);
// if no key was found -> generate new
if (secretKeyEntry != null) secretKey = secretKeyEntry.getSecretKey();
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
// failed to retrieve -> will generate new
e.printStackTrace();
}
}
private void generateKey() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
final KeyGenParameterSpec keyGenParameterSpec =
new KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build();
keyGenerator.init(keyGenParameterSpec);
secretKey = keyGenerator.generateKey();
}
public String encrypt(String toEncrypt) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
String iv = Base64.encodeToString(cipher.getIV(), Base64.DEFAULT);
String encrypted = Base64.encodeToString(cipher.doFinal(toEncrypt.getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT);
return encrypted + SEPARATOR + iv;
}
public String decrypt(String toDecrypt) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String[] parts = toDecrypt.split(SEPARATOR);
if (parts.length != 2)
throw new AssertionError("String to decrypt must be of the form: 'BASE64_DATA" + SEPARATOR + "BASE64_IV'");
byte[] encrypted = Base64.decode(parts[0], Base64.DEFAULT),
iv = Base64.decode(parts[1], Base64.DEFAULT);
final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
IvParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
}
}
Copy link

ghost commented May 28, 2021

This is the Kotlin way of Cryptography -> https://gist.github.com/abalta/8509bdcccc11924529bc595f399b7bcf

@vincent-paing
Copy link

What does it means by "requires previous user authentication to have been performed"?

@KennyGoers
Copy link

Yes, what does the "requires previous" even mean in this context?

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