Skip to content

Instantly share code, notes, and snippets.

@msramalho
Created January 13, 2020 22:59
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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);
}
}
@BelooS
Copy link

BelooS commented Apr 15, 2020

I see that you are using AES/CBC/PKCS7Padding configuration. Be aware that cipher on encryption or decryption with key provided from keystore with this configuration and padding specifically are failing on android api 26 and 27 with android.security.KeyStoreException: Unknown error. We have faced it in our cryptographer implementation having same padding and encryption algorithm

@funnel20
Copy link

funnel20 commented Nov 17, 2020

Issue
decrypt() will show error "String to decrypt must be of the form: 'BASE64_DATA" + SEPARATOR + "BASE64_IV'" because option Base64.DEFAULT in encrypt() adds a new line character by default.

Solution
Replace both usages of Base64.DEFAULT by Base64.NO_WRAP in encrypt(). See: https://stackoverflow.com/a/25011746/2439941

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