Convert KeyStoreEncryption to Kotlin

pull/996/head
Alex Baker 4 years ago
parent 9bf5216081
commit d1ad84f281

@ -67,11 +67,11 @@ class CaldavAccount : Parcelable {
}
fun getPassword(encryption: KeyStoreEncryption): String {
return encryption.decrypt(password)
return encryption.decrypt(password) ?: ""
}
fun getEncryptionPassword(encryption: KeyStoreEncryption): String {
return encryption.decrypt(encryptionKey)
return encryption.decrypt(encryptionKey) ?: ""
}
val isCaldavAccount: Boolean

@ -1,130 +0,0 @@
package org.tasks.security;
import static org.tasks.Strings.isNullOrEmpty;
import android.annotation.SuppressLint;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
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.Arrays;
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.GCMParameterSpec;
import javax.inject.Inject;
import org.tasks.injection.ApplicationScope;
import timber.log.Timber;
@ApplicationScope
public class KeyStoreEncryption {
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
private static final String ALIAS = "passwords";
private static final Charset ENCODING = StandardCharsets.UTF_8;
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 16;
private KeyStore keyStore;
@Inject
public KeyStoreEncryption() {
try {
keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
throw new IllegalStateException();
}
}
public String encrypt(String text) {
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, iv);
try {
byte[] output = cipher.doFinal(text.getBytes(ENCODING));
byte[] result = new byte[iv.length + output.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(output, 0, result, iv.length, output.length);
return Base64.encodeToString(result, Base64.DEFAULT);
} catch (IllegalBlockSizeException | BadPaddingException e) {
Timber.e(e);
return null;
}
}
public String decrypt(String text) {
if (isNullOrEmpty(text)) {
return null;
}
byte[] decoded = Base64.decode(text, Base64.DEFAULT);
byte[] iv = Arrays.copyOfRange(decoded, 0, GCM_IV_LENGTH);
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, iv);
try {
byte[] decrypted = cipher.doFinal(decoded, GCM_IV_LENGTH, decoded.length - GCM_IV_LENGTH);
return new String(decrypted, ENCODING);
} catch (IllegalBlockSizeException | BadPaddingException e) {
Timber.e(e);
return "";
}
}
private Cipher getCipher(int cipherMode, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(cipherMode, getSecretKey(), new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv));
return cipher;
} catch (NoSuchAlgorithmException
| NoSuchPaddingException
| InvalidAlgorithmParameterException
| InvalidKeyException e) {
throw new IllegalArgumentException(e);
}
}
private SecretKey getSecretKey() {
try {
Entry entry = keyStore.getEntry(ALIAS, null);
return entry == null ? generateNewKey() : ((KeyStore.SecretKeyEntry) entry).getSecretKey();
} catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableEntryException e) {
throw new IllegalStateException();
}
}
@SuppressLint("TrulyRandom")
private SecretKey generateNewKey() {
try {
final KeyGenerator keyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
keyGenerator.init(
new KeyGenParameterSpec.Builder(
ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setRandomizedEncryptionRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
return keyGenerator.generateKey();
} catch (NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| NoSuchProviderException e) {
throw new IllegalStateException();
}
}
}

@ -0,0 +1,97 @@
package org.tasks.security
import android.annotation.SuppressLint
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import org.tasks.Strings.isNullOrEmpty
import org.tasks.injection.ApplicationScope
import timber.log.Timber
import java.nio.charset.StandardCharsets
import java.security.KeyStore
import java.security.SecureRandom
import java.util.*
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec
import javax.inject.Inject
@ApplicationScope
class KeyStoreEncryption @Inject constructor() {
private val keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
fun encrypt(text: String): String? {
val iv = ByteArray(GCM_IV_LENGTH)
SecureRandom().nextBytes(iv)
val cipher = getCipher(Cipher.ENCRYPT_MODE, iv)
return try {
val output = cipher.doFinal(text.toByteArray(ENCODING))
val result = ByteArray(iv.size + output.size)
System.arraycopy(iv, 0, result, 0, iv.size)
System.arraycopy(output, 0, result, iv.size, output.size)
Base64.encodeToString(result, Base64.DEFAULT)
} catch (e: IllegalBlockSizeException) {
Timber.e(e)
null
} catch (e: BadPaddingException) {
Timber.e(e)
null
}
}
fun decrypt(text: String?): String? {
if (isNullOrEmpty(text)) {
return null
}
val decoded = Base64.decode(text, Base64.DEFAULT)
val iv = Arrays.copyOfRange(decoded, 0, GCM_IV_LENGTH)
val cipher = getCipher(Cipher.DECRYPT_MODE, iv)
return try {
val decrypted = cipher.doFinal(decoded, GCM_IV_LENGTH, decoded.size - GCM_IV_LENGTH)
String(decrypted, ENCODING)
} catch (e: IllegalBlockSizeException) {
Timber.e(e)
""
} catch (e: BadPaddingException) {
Timber.e(e)
""
}
}
private fun getCipher(cipherMode: Int, iv: ByteArray): Cipher {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(cipherMode, secretKey, GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, iv))
return cipher
}
private val secretKey: SecretKey
get() {
val entry: KeyStore.Entry? = keyStore.getEntry(ALIAS, null)
return (entry as KeyStore.SecretKeyEntry?)?.secretKey ?: generateNewKey()
}
@SuppressLint("TrulyRandom")
private fun generateNewKey(): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
keyGenerator.init(
KeyGenParameterSpec.Builder(
ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setRandomizedEncryptionRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build())
return keyGenerator.generateKey()
}
init {
keyStore.load(null)
}
companion object {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
private const val ALIAS = "passwords"
private val ENCODING = StandardCharsets.UTF_8
private const val GCM_IV_LENGTH = 12
private const val GCM_TAG_LENGTH = 16
}
}
Loading…
Cancel
Save