Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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();
}
}
}

How use with api 18, please help me

Owner

josias1991 commented Oct 8, 2017

You will have to use one of the other init() and getInstance() methods provided by the KeyGenerator. See here: https://developer.android.com/reference/javax/crypto/KeyGenerator.html.

You can do something like this in the getSecretKey method:

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(ANDROID_KEY_STORE);
        
        // or something like 
        
        keyGenerator = KeyGenerator.getInstance("supported algorithm here", ANDROID_KEY_STORE);
        
        // use the supported init method here such as this one: https://developer.android.com/reference/javax/crypto/KeyGenerator.html#init(int, java.security.SecureRandom)   
        keyGenerator.init(/* ... */);
    }

    return keyGenerator.generateKey();
}

For supported init methods algorithms below api 23 you can see the documentation in the link provided above. Each algorithm specifies what api it is supported in.

Each algorithm is described in the KeyGenerator section of the Java Cryptography Architecture Standard Algorithm Name Documentation.

Hi! Thanks for the example. Maybe you can help me, I'm having some trouble understanding how this works across sessions.

From what I can see, in order to decrypt, you need to encrypt first in order to get the encrypted message and the iv. But how can we retrieve encrypted data that was saved in a previous session of the app, hence not having the encrypted message and the iv?

Thanks :)

oliverspryn commented Nov 1, 2017

@nemesis06101986 Saving information is the easy part. Now that the data is encrypted, you may store it using the PreferenceManager.

For example, in Kotlin, this saves a pre-encrypted string and the IV in the PreferenceManager. Keep in mind, IVs do not need to be kept secret:

class EncryptedInfo {
    var data: String? = null
    var iv: ByteArray? = null
}

object SettingsRepository {
    fun getProperty(key: String, context: Context): EncryptedInfo {
        val info = EncryptedInfo()

        info.data = PreferenceManager.getDefaultSharedPreferences(context)
            .getString(key, null)

        val iv = PreferenceManager.getDefaultSharedPreferences(context)
            .getString("${key}_iv", null)

        info.iv = Base64.decode(iv, Base64.DEFAULT)

        return info
    }

    fun setProperty(key: String, encryptedValue: String, iv: ByteArray, context: Context) {
        val ivString = Base64.encodeToString(iv, Base64.DEFAULT)

        val settingPref = PreferenceManager.getDefaultSharedPreferences(context).edit()
        settingPref.putString(key, encryptedValue)
        settingPref.apply()

        val settingIvPref = PreferenceManager.getDefaultSharedPreferences(context).edit()
        settingIvPref.putString("${key}_iv", ivString)
        settingIvPref.apply()
    }
}

Now, put it together:

val alias = "test alias"

// To encrypt
val encrytedString = "abcdef"
val iv = cipher.iv

SettingsRepository.setProperty(alias, encryptedString, iv, context)

// Somewhere later in the code, to decrypt
val info = SettingsRepository.getProperty(alias, context)

// decrypt them
// info?.data
// info?.iv

Note: This comment is intended for communal guidance only. I cannot be held responsible for any mishandled sensitive information.

GCMParameterSpec only works in >= Kitkat, do you have a workaround for this?

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