Skip to content

Instantly share code, notes, and snippets.

@rliuzzi
Last active November 8, 2019 14:46
Show Gist options
  • Save rliuzzi/64a3d077dc0cdc5a0f9743423a06ff18 to your computer and use it in GitHub Desktop.
Save rliuzzi/64a3d077dc0cdc5a0f9743423a06ff18 to your computer and use it in GitHub Desktop.
Encryption Cheat sheet, pre and post M. Just leaving this here as a demo. I couldn't recommend using different Encryption providers that depend on Android version as encrypted files could be corrupt after a OS update. Use at your own risk. Coded while playing around with this article: https://medium.com/@ericfu/securely-storing-secrets-in-an-and…
package com.zzivi.core.security;
public interface EncryptionHelper {
String encrypt(String input) throws Exception;
String decrypt(String input) throws Exception;
}
package com.zzivi.core.security;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
/**
* Created by rominaliuzzi on 14/12/2017.
*/
public class EncryptionHelperKeystore implements EncryptionHelper {
public static final String KEY = "zZiv!_encrpt_KeY";
private static final String FIXED_IV = "IV+zZiv!_encrpt_KeY";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String AES_MODE = "AES/GCM/NoPadding";
private Key getKey() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException,
InvalidAlgorithmParameterException, NoSuchProviderException, IllegalStateException{
Key key = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
if (!keyStore.containsAlias(KEY)) {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
keyGenerator.init(
new KeyGenParameterSpec.Builder(KEY,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false) //Allow using a fixed Initialization Vector
.build());
keyGenerator.generateKey();
}
key = keyStore.getKey(KEY, null);
} else {
throw new IllegalStateException(String.format("Method not supported for OS version: %s", Build.VERSION.SDK_INT) );
}
return key;
}
/**
* throws: NoSuchAlgorithmException
* throws: InvalidKeyException
* throws: InvalidAlgorithmParameterException
* throws: NoSuchPaddingException
* throws: BadPaddingException
* throws: IllegalBlockSizeException
*/
@Override
public String encrypt(String input) throws Exception{
String encryptedBase64Encoded = null;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Cipher c = Cipher.getInstance(AES_MODE);
c.init(Cipher.ENCRYPT_MODE, getKey(), new GCMParameterSpec(128, FIXED_IV.getBytes()));
byte[] encodedBytes = c.doFinal(input.getBytes());
encryptedBase64Encoded = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
} else {
throw new IllegalStateException(String.format("Method not supported for OS version: %s", Build.VERSION.SDK_INT) );
}
return encryptedBase64Encoded;
}
/**
* throws: NoSuchAlgorithmException
* throws: InvalidKeyException
* throws: InvalidAlgorithmParameterException
* throws: NoSuchPaddingException
* throws: BadPaddingException
* throws: IllegalBlockSizeException
* */
@Override
public String decrypt(String encrypted) throws Exception{
String decodedString = null;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Cipher c = Cipher.getInstance(AES_MODE);
c.init(Cipher.DECRYPT_MODE, getKey(), new GCMParameterSpec(128, FIXED_IV.getBytes()));
byte[] decodedBytes = c.doFinal(encrypted.getBytes());
decodedString = new String(decodedBytes);
} else {
throw new IllegalStateException(String.format("Method not supported for OS version: %s", Build.VERSION.SDK_INT) );
}
return decodedString;
}
}
package com.zzivi.core.security;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import com.zzivi.core.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Calendar;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
public class EncryptionHelperKeystorePreM implements EncryptionHelper {
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
public static final String KEY_ALIAS = "zZivi_encrpt_KeY";
public static final String PREF_ENCRYPTED_AES_KEY = "zZivi_encrpted_KeY";
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String AES_MODE = "AES/ECB/PKCS7Padding";
Context context;
public EncryptionHelperKeystorePreM(Context context) {
this.context = context;
}
/**
* throws: InvalidKeyException
* throws: NoSuchPaddingException
* throws: BadPaddingException
* throws: NoSuchProviderException
* throws: IllegalBlockSizeException
* */
@Override
public String encrypt(String input) throws Exception {
Cipher c = Cipher.getInstance(AES_MODE, "BC");
c.init(Cipher.ENCRYPT_MODE, getKey());
byte[] inputBytes = input.getBytes(Charset.forName("UTF8"));
byte[] encryptedBytes = c.doFinal(inputBytes);
String encryptedBytesBase64Encoded = Base64.encodeToString(encryptedBytes, Base64.NO_PADDING);
return encryptedBytesBase64Encoded;
}
/**
* throws: InvalidKeyException
* throws: NoSuchAlgorithmException
* throws: NoSuchPaddingException
* throws: BadPaddingException
* throws: NoSuchProviderException
* throws: IllegalBlockSizeException
* */
@Override
public String decrypt(String encryptedEncoded) throws Exception {
Cipher c = Cipher.getInstance(AES_MODE, "BC");
c.init(Cipher.DECRYPT_MODE, getKey());
byte[] encryptedBytes = Base64.decode(encryptedEncoded, Base64.NO_PADDING);
byte[] plainBytes = c.doFinal(encryptedBytes);
String plainString = new String(plainBytes, Charset.forName("UTF8"));
return plainString;
}
/**
* Get the encryption key from shared prefs.
* Generate a new key if the key is null.
* throws: NoSuchAlgorithmException
* throws: NoSuchPaddingException
* throws: NoSuchProviderException
* throws: InvalidKeyException
* throws: IOException
* */
private Key getKey() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, IOException,
CertificateException, UnrecoverableEntryException, KeyStoreException, InvalidAlgorithmParameterException {
SharedPreferences pref = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
String base64EncodedRsaEncryptedEncryptionKey = pref.getString(PREF_ENCRYPTED_AES_KEY, null);
if(base64EncodedRsaEncryptedEncryptionKey == null){
base64EncodedRsaEncryptedEncryptionKey = generateRSAKey();
}
byte[] rsaEncryptedEncryptionKey = Base64.decode(base64EncodedRsaEncryptedEncryptionKey, Base64.NO_PADDING);
byte[] encryptionKey = rsaDecrypt(rsaEncryptedEncryptionKey);
return new SecretKeySpec(encryptionKey, "AES");
}
/**
* First request will generate RSA Key and save to shared prefs
* throws: IOException
* throws: KeyStoreException
* throws: CertificateException
* throws: NoSuchAlgorithmException
* throws: UnrecoverableEntryException
* throws: InvalidAlgorithmParameterException
* throws: NoSuchProviderException
* throws: IllegalStateException
* throws: CertificateException
* throws: UnrecoverableEntryException
* throws: KeyStoreException
* throws: InvalidAlgorithmParameterException
* */
private String generateRSAKey() throws NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IOException,
CertificateException, UnrecoverableEntryException, KeyStoreException, InvalidAlgorithmParameterException {
byte[] key = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
byte[] rsaEncryptedKey = rsaEncrypt(key);
String base64EncodedRsaEnryptedKey = Base64.encodeToString(rsaEncryptedKey, Base64.NO_PADDING);
SharedPreferences pref = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
SharedPreferences.Editor edit = pref.edit();
edit.putString(PREF_ENCRYPTED_AES_KEY, base64EncodedRsaEnryptedKey);
edit.commit();
return base64EncodedRsaEnryptedKey;
}
/**
* First request will generate Keystore Entry
* throws: IOException
* throws: KeyStoreException
* throws: CertificateException
* throws: NoSuchAlgorithmException
* throws: UnrecoverableEntryException
* throws: InvalidAlgorithmParameterException
* throws: NoSuchProviderException
* throws: IllegalStateException
* */
private KeyStore.Entry getKeystoreEntry() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException,
InvalidAlgorithmParameterException, NoSuchProviderException, IllegalStateException {
KeyStore.Entry keystoreEntry = null;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
// Generate the RSA key pairs
if (!keyStore.containsAlias(KEY_ALIAS)) {
// Generate a key pair for encryption
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 30);
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(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
kpg.initialize(spec);
kpg.generateKeyPair();
}
keystoreEntry = keyStore.getEntry(KEY_ALIAS, null);
} else {
throw new IllegalStateException(String.format("Method not supported for OS version: %s", Build.VERSION.SDK_INT) );
}
return keystoreEntry;
}
/**
* Encrypt key using Keystore entry public key
* throws: IOException
* throws: NoSuchPaddingException
* throws: NoSuchAlgorithmException
* throws: NoSuchProviderException
* throws: InvalidKeyException
* throws: CertificateException
* throws: UnrecoverableEntryException
* throws: KeyStoreException
* throws: InvalidAlgorithmParameterException
* */
private byte[] rsaEncrypt(byte[] rsaKey) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
CertificateException, UnrecoverableEntryException, KeyStoreException, InvalidAlgorithmParameterException {
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) getKeystoreEntry();
Cipher inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
inputCipher.init(Cipher.ENCRYPT_MODE, keyEntry.getCertificate().getPublicKey());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
cipherOutputStream.write(rsaKey);
cipherOutputStream.close();
byte[] vals = outputStream.toByteArray();
return vals;
}
/**
* Decrypt key using Keystore entry private key
* throws: IOException
* throws: NoSuchPaddingException
* throws: NoSuchAlgorithmException
* throws: NoSuchProviderException
* throws: InvalidKeyException
* throws: CertificateException
* throws: UnrecoverableEntryException
* throws: KeyStoreException
* throws: InvalidAlgorithmParameterException
* */
private byte[] rsaDecrypt(byte[] encryptionKey) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
CertificateException, UnrecoverableEntryException, KeyStoreException, InvalidAlgorithmParameterException {
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) getKeystoreEntry();
Cipher output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
output.init(Cipher.DECRYPT_MODE, keyEntry.getPrivateKey());
CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(encryptionKey), output);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte)nextByte);
}
byte[] bytes = new byte[values.size()];
for(int i = 0; i < bytes.length; i++) {
bytes[i] = values.get(i).byteValue();
}
return bytes;
}
}
@MichaelObi
Copy link

In EncryptionHelperKeystore.java on line 95, the encrypted string has to be Base64 decoded.
That line should be:

byte[] decodedBytes = c.doFinal(Base64.decode(encrypted, Base64.DEFAULT));

@shanmugasanthosh7
Copy link

Thanks, everything works well.

Note: FIXED_IV = "IV+zZiv!_encrpt_KeY" for IV it allows only 12 bytes otherwise it throws an exception.

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