Skip to content

Instantly share code, notes, and snippets.

@diefferson
Created October 28, 2019 19:36
Show Gist options
  • Save diefferson/c13da15dcceee6784a1a31078ffb47b5 to your computer and use it in GitHub Desktop.
Save diefferson/c13da15dcceee6784a1a31078ffb47b5 to your computer and use it in GitHub Desktop.
Secure preferences
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