Created
October 28, 2019 19:36
-
-
Save diefferson/c13da15dcceee6784a1a31078ffb47b5 to your computer and use it in GitHub Desktop.
Secure preferences
This file contains hidden or 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 br.com.juno.data.repository.preferences | |
import android.annotation.SuppressLint | |
import android.content.Context | |
import android.content.SharedPreferences | |
import android.security.KeyChain | |
import android.security.keystore.KeyGenParameterSpec | |
import android.security.keystore.KeyInfo | |
import android.security.keystore.KeyProperties | |
import android.util.Base64 | |
import timber.log.Timber | |
import java.math.BigInteger | |
import java.security.* | |
import java.security.spec.AlgorithmParameterSpec | |
import java.security.spec.InvalidKeySpecException | |
import java.util.* | |
import javax.crypto.BadPaddingException | |
import javax.crypto.Cipher | |
import javax.crypto.IllegalBlockSizeException | |
import javax.crypto.NoSuchPaddingException | |
import javax.security.auth.x500.X500Principal | |
class SecurePreferences(private val context: Context, | |
private val preferences: SharedPreferences) { | |
private var alias = BuildConfig.APPLICATION_ID | |
var initialized = false | |
@SuppressLint("NewApi", "TrulyRandom") | |
fun init() { | |
val ks: KeyStore? | |
try { | |
ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE) | |
//Use null to load Keystore with default parameters. | |
ks?.load(null) | |
// Check if Private and Public already keys exists. If so we don't need to generate them again | |
val privateKey = ks?.getKey(alias, null) as PrivateKey? | |
if (privateKey != null && ks?.getCertificate(alias) != null) { | |
val publicKey = ks.getCertificate(alias).publicKey | |
if (publicKey != null) { | |
// All keys are available. | |
initialized = true | |
return | |
} | |
} | |
} catch (ex: Exception) { | |
return | |
} | |
// Create a start and end time, for the validity range of the key pair that's about to be | |
// generated. | |
val start = GregorianCalendar() | |
val end = GregorianCalendar() | |
end.add(Calendar.YEAR, 10) | |
// Specify the parameters object which will be passed to KeyPairGenerator | |
val spec: AlgorithmParameterSpec | |
if (android.os.Build.VERSION.SDK_INT < 23) { | |
spec = android.security.KeyPairGeneratorSpec.Builder(context) | |
// Alias - is a key for your KeyPair, to obtain it from Keystore in future. | |
.setAlias(alias?:"") | |
// The subject used for the self-signed certificate of the generated pair | |
.setSubject(X500Principal("CN=$alias")) | |
// The serial number used for the self-signed certificate of the generated pair. | |
.setSerialNumber(BigInteger.valueOf(1337)) | |
// Date range of validity for the generated pair. | |
.setStartDate(start.time).setEndDate(end.time) | |
.build() | |
} else { | |
spec = KeyGenParameterSpec.Builder(alias?:"", KeyProperties.PURPOSE_DECRYPT) | |
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) | |
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) | |
.build() | |
} | |
// Initialize a KeyPair generator using the the intended algorithm (in this example, RSA | |
// and the KeyStore. This example uses the AndroidKeyStore. | |
val kpGenerator: KeyPairGenerator | |
try { | |
kpGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER_ANDROID_KEYSTORE) | |
kpGenerator.initialize(spec) | |
// Generate private/public keys | |
kpGenerator.generateKeyPair() | |
} catch (e: java.lang.Exception) { | |
try { | |
ks?.deleteEntry(alias) | |
} catch (e1: Exception) { | |
// Just ignore any errors here | |
} | |
} | |
// Check if device support Hardware-backed keystore | |
try { | |
val isHardwareBackedKeystoreSupported: Boolean | |
isHardwareBackedKeystoreSupported = if (android.os.Build.VERSION.SDK_INT < 23) { | |
KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA) | |
} else { | |
val privateKey = ks.getKey(alias, null) as PrivateKey | |
KeyChain.isBoundKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA) | |
val keyFactory = KeyFactory.getInstance(privateKey.algorithm, "AndroidKeyStore") | |
val keyInfo = keyFactory.getKeySpec(privateKey, KeyInfo::class.java) | |
keyInfo.isInsideSecureHardware | |
} | |
Timber.d(LOG_TAG, "Hardware-Backed Keystore Supported: $isHardwareBackedKeystoreSupported") | |
} catch (e: Exception) { | |
} | |
initialized = true | |
} | |
fun setData(key: String, stringData: String) { | |
if(!initialized){ | |
init() | |
} | |
val data = stringData.toByteArray() | |
var ks: KeyStore? = null | |
try { | |
ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE) | |
ks?.load(null) | |
if (ks.getCertificate(alias) == null) return | |
val publicKey = ks.getCertificate(alias).publicKey | |
if (publicKey == null) { | |
Timber.d(LOG_TAG, "Error: Public key was not found in Keystore") | |
return | |
} | |
val value = encrypt(publicKey, data) | |
val editor = preferences.edit() | |
editor?.putString(key, value) | |
editor?.commit() | |
} catch (e: Exception) { | |
try { | |
ks?.deleteEntry(alias) | |
} catch (e1: Exception) { | |
// Just ignore any errors here | |
} | |
} | |
} | |
fun getData(key: String): String { | |
if(!initialized){ | |
init() | |
} | |
var ks: KeyStore? = null | |
try { | |
ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE) | |
ks?.load(null) | |
val privateKey = ks.getKey(alias, null) as PrivateKey | |
val result = decrypt(privateKey, preferences.getString(key, null)) | |
if(result!= null){ | |
return String(result) | |
} | |
} catch (e: Exception) { | |
try { | |
ks?.deleteEntry(alias) | |
} catch (e1: Exception) { | |
// Just ignore any errors here | |
} | |
} | |
return "" | |
} | |
fun remove(key: String) { | |
val editor = preferences.edit() | |
editor?.remove(key) | |
editor?.commit() | |
} | |
fun clear() { | |
val editor = preferences.edit() | |
editor?.clear() | |
editor?.commit() | |
} | |
companion object { | |
const val KEYSTORE = "KEYSTORE" | |
private val LOG_TAG = SecurePreferences::class.java.simpleName | |
private const val KEY_ALGORITHM_RSA = "RSA" | |
private const val KEYSTORE_PROVIDER_ANDROID_KEYSTORE = "AndroidKeyStore" | |
private const val RSA_ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding" | |
@SuppressLint("TrulyRandom") | |
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class, NoSuchProviderException::class, InvalidKeySpecException::class) | |
private fun encrypt(encryptionKey: PublicKey, data: ByteArray?): String { | |
val cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING) | |
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey) | |
val encrypted = cipher.doFinal(data) | |
return Base64.encodeToString(encrypted, Base64.NO_WRAP) | |
} | |
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, IllegalBlockSizeException::class, BadPaddingException::class, NoSuchProviderException::class) | |
private fun decrypt(decryptionKey: PrivateKey, encryptedData: String?): ByteArray? { | |
if (encryptedData == null) | |
return null | |
val encryptedBuffer = Base64.decode(encryptedData, Base64.NO_WRAP) | |
val cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING) | |
cipher.init(Cipher.DECRYPT_MODE, decryptionKey) | |
return cipher.doFinal(encryptedBuffer) | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment