Search code examples
androidencryptionkotlinfingerprint

Use Fingerprint and pin


Hello I'm trying to use a fingerprint and pin code to access my android application. I encrypt the data using the Cipher. I store encrypted pin in a SharedPreferences. I can decrypt the pin code with a fingerprint, but I can not decrypt them if I enter the pin manually. I mean, I enter the pin in EditText and try to compare it with the encrypted pin in the SharedPreferences.

CryptoUtils class:

@TargetApi(Build.VERSION_CODES.M)
object CryptoUtils {
private val TAG = CryptoUtils::class.java.simpleName

private val KEY_ALIAS = "key_for_pin"
private val KEY_STORE = "AndroidKeyStore"
private val TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"

private var sKeyStore: KeyStore? = null
private var sKeyPairGenerator: KeyPairGenerator? = null
private var sCipher: Cipher? = null

private val keyStore: Boolean
    get() {
        try {
            sKeyStore = KeyStore.getInstance(KEY_STORE)
            sKeyStore!!.load(null)
            return true
        } catch (e: KeyStoreException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: CertificateException) {
            e.printStackTrace()
        }

        return false
     }


private val keyPairGenerator: Boolean
    @TargetApi(Build.VERSION_CODES.M)
    get() {
        try {
            sKeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEY_STORE)
            return true
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: NoSuchProviderException) {
            e.printStackTrace()
        }

        return false
    }


private val cipher: Boolean
    @SuppressLint("GetInstance")
    get() {
        try {
            sCipher = Cipher.getInstance(TRANSFORMATION)
            return true
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: NoSuchPaddingException) {
            e.printStackTrace()
        }

        return false
    }

private val key: Boolean
    get() {
        try {
            return sKeyStore!!.containsAlias(KEY_ALIAS) || generateNewKey()
        } catch (e: KeyStoreException) {
            e.printStackTrace()
        }

        return false

    }

val cryptoObject: FingerprintManagerCompat.CryptoObject?
    @Nullable
    get() = if (prepare() && initCipher(Cipher.DECRYPT_MODE)) {
        FingerprintManagerCompat.CryptoObject(sCipher!!)
    } else null

fun encode(inputString: String): String? {
    try {
        if (prepare() && initCipher(Cipher.ENCRYPT_MODE)) {
            val bytes = sCipher!!.doFinal(inputString.toByteArray())
            return Base64.encodeToString(bytes, Base64.NO_WRAP)
        }
    } catch (exception: IllegalBlockSizeException) {
        exception.printStackTrace()
    } catch (exception: BadPaddingException) {
        exception.printStackTrace()
    }

    return null
}

fun decode(encodedString: String, cipher: Cipher): String? {
    try {
        val bytes = Base64.decode(encodedString, Base64.NO_WRAP)
        return String(cipher.doFinal(bytes))
    } catch (exception: IllegalBlockSizeException) {
        exception.printStackTrace()
    } catch (exception: BadPaddingException) {
        exception.printStackTrace()
    }

    return null
}

private fun prepare(): Boolean {
    return keyStore && cipher && key
}


@TargetApi(Build.VERSION_CODES.M)
private fun generateNewKey(): Boolean {

    if (keyPairGenerator) {

        try {
            sKeyPairGenerator!!.initialize(
                KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                    .setUserAuthenticationRequired(true)
                    .build())
            sKeyPairGenerator!!.generateKeyPair()
            return true
        } catch (e: InvalidAlgorithmParameterException) {
            e.printStackTrace()
        }

    }
    return false
}


private fun initCipher(mode: Int): Boolean {
    try {
        sKeyStore!!.load(null)

        when (mode) {
            Cipher.ENCRYPT_MODE -> initEncodeCipher(mode)

            Cipher.DECRYPT_MODE -> initDecodeCipher(mode)
            else -> return false //this cipher is only for encode\decode
        }
        return true

    } catch (exception: KeyPermanentlyInvalidatedException) {
        deleteInvalidKey()

    } catch (e: KeyStoreException) {
        e.printStackTrace()
    } catch (e: CertificateException) {
        e.printStackTrace()
    } catch (e: UnrecoverableKeyException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
    } catch (e: InvalidKeyException) {
        e.printStackTrace()
    } catch (e: InvalidKeySpecException) {
        e.printStackTrace()
    } catch (e: InvalidAlgorithmParameterException) {
        e.printStackTrace()
    }

    return false
}

@Throws(KeyStoreException::class, NoSuchAlgorithmException::class, UnrecoverableKeyException::class, InvalidKeyException::class)
private fun initDecodeCipher(mode: Int) {
    val key = sKeyStore!!.getKey(KEY_ALIAS, null) as PrivateKey
    sCipher!!.init(mode, key)
}


@Throws(KeyStoreException::class, InvalidKeySpecException::class, NoSuchAlgorithmException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
private fun initEncodeCipher(mode: Int) {
    val key = sKeyStore!!.getCertificate(KEY_ALIAS).publicKey

    // workaround for using public key
    // from https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html
    val unrestricted = KeyFactory.getInstance(key.getAlgorithm()).generatePublic(X509EncodedKeySpec(key.getEncoded()))
    // from https://code.google.com/p/android/issues/detail?id=197719
    val spec = OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT)

    sCipher!!.init(mode, unrestricted, spec)
}

fun deleteInvalidKey() {
    if (keyStore) {
        try {
            sKeyStore!!.deleteEntry(KEY_ALIAS)
        } catch (e: KeyStoreException) {
            e.printStackTrace()
        }

    }
}


}

FingerprintUtils class:

enum class SensorState {
NOT_SUPPORTED,
NOT_BLOCKED,
NO_FINGERPRINTS,
READY
}

fun checkFingerprintCompatibility(context: Context): Boolean {
    return FingerprintManagerCompat.from(context).isHardwareDetected
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
fun checkSensorState(context: Context): SensorState {
    if (checkFingerprintCompatibility(context)) {

    val keyguardManager = 
    context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
    if (!keyguardManager.isKeyguardSecure) {
        return SensorState.NOT_BLOCKED
    }

    return if (!FingerprintManagerCompat.from(context).hasEnrolledFingerprints()) {
        SensorState.NO_FINGERPRINTS
    } else SensorState.READY

} else {
    return SensorState.NOT_SUPPORTED
}

}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
fun isSensorStateAt(state: SensorState, context: Context): Boolean {
    return checkSensorState(context) == state
}

FingerPrintHelper class which I use in Activity:

class FingerprintHelper(private val mContext: Context, private val etPincode: PinField) : FingerprintManagerCompat.AuthenticationCallback() {
private var mCancellationSignal: CancellationSignal? = null

internal fun startAuth(cryptoObject: FingerprintManagerCompat.CryptoObject) {
    mCancellationSignal = CancellationSignal()
    val manager = FingerprintManagerCompat.from(mContext)
    manager.authenticate(cryptoObject, 0, mCancellationSignal, this, null)
}

internal fun cancel() {
    if (mCancellationSignal != null) {
        mCancellationSignal!!.cancel()
    }
}

override fun onAuthenticationError(errMsgId: Int, errString: CharSequence?) {
    Toast.makeText(mContext, errString, Toast.LENGTH_SHORT).show()
}

override fun onAuthenticationHelp(helpMsgId: Int, helpString: CharSequence?) {
    Toast.makeText(mContext, helpString, Toast.LENGTH_SHORT).show()
}

override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) {
    val cipher = result!!.cryptoObject.cipher
    val encoded = PasswordStorage.getPasswordCode()
    val decoded = CryptoUtils.decode(encoded!!, cipher!!)
    etPincode.setText(decoded)
    Toast.makeText(mContext, "success", Toast.LENGTH_SHORT).show()
}

override fun onAuthenticationFailed() {
    Toast.makeText(mContext, "try again", Toast.LENGTH_SHORT).show()
}

}

Fingerprint work perfectly. It decrypt my pincode from SharedPreferences. But how can I decrypt it manually without fingerprint?


Solution

  • I would encrypt a salted password and store it (as SHA256) in Android preferences and then when user enters password I would encrypt the password again and checks if the product of SHA256 is the same.