-
-
Save shanmugasanthosh7/b22dc3f154405cece1def1ceec57c5b0 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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.zzivi.core.security; | |
public interface EncryptionHelper { | |
String encrypt(String input) throws Exception; | |
String decrypt(String input) throws Exception; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment