Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cf7e4d1
chore(inji-214): api for device supports hardware backed keystore
dhivya0413 Jul 17, 2023
342ab5a
chore(inji-214): add encrypt and decrypt functions to secure key store
tilak-puli Jul 19, 2023
aee7e9a
chore(inji-214): extract CipherBox and KeyGenerator from SecureKeystore
tilak-puli Jul 19, 2023
3c6ac81
chore(inji-214): update GenerateKeyPair to return PEM encoded public …
tilak-puli Jul 20, 2023
50cb5f4
chore(inji-214): create sign method to sign with RSA private Key
tilak-puli Jul 20, 2023
b958317
feat(inji-214): [Tilak|Dhivya] add hasAlias impl
dhivya0413 Jul 25, 2023
dc83d77
feat(inji-214): [Tilak|Dhivya] add hasAlias impl
dhivya0413 Jul 25, 2023
bebd99a
feat(inji-214): [Tilak|Dhivya] fix generate key pair pem string by us…
tilak-puli Jul 25, 2023
3dd785a
feat(inji-214): [Tilak|Dhivya] add generate HmacSha method to generat…
tilak-puli Jul 26, 2023
065d174
feat(inji-214): [Tilak|Dhivya] validate encrypted text before decryption
tilak-puli Jul 27, 2023
150bc35
feat(inji-214): [Tilak|Dhivya] resolve conflicts
dhivya0413 Jul 27, 2023
059481b
feat(inji-214): [Dhivya|Tilak] refactor encryption and sign the priva…
dhivya0413 Jul 27, 2023
98b1f66
feat(inji-214): [Tilak|Dhivya] add auth prompt for signing with keypair
tilak-puli Jul 30, 2023
7685f2f
feat(inji-214): [Tilak|Dhivya] use consistent error code for creat si…
tilak-puli Jul 30, 2023
d962d1c
feat(inji-214): [Tilak|Dhivya] enable device credential for auth in c…
tilak-puli Jul 30, 2023
4d88e61
feat(inji-214): [Tilak|Dhivya] remove device credential for auth in c…
tilak-puli Jul 30, 2023
08d9c8d
feat(inji-214): [Tilak|Dhivya] update documentation
tilak-puli Aug 1, 2023
8cf5c81
feat(inji-214): [Tilak|Dhivya] use key to generate hmac and remove da…
tilak-puli Aug 2, 2023
0a73fcf
feat(inji-214): [Tilak|Dhivya] use AndroidX for backward support and …
tilak-puli Aug 8, 2023
9b1a4d2
feat(inji-214): [Tilak|Dhivya] Use kotlin co routines to block thread…
tilak-puli Aug 9, 2023
6397a93
feat(inji-214): [Tilak|Dhivya] fix timeout based key auth is failing
tilak-puli Aug 9, 2023
6cc5a08
feat(inji-214): [Tilak|Dhivya] rename authenticateAndPerform paramete…
tilak-puli Aug 14, 2023
32c32ee
feat(INJI-214): generate hmacsha256 key
dhivya0413 Aug 11, 2023
cc8c763
feat(inji-214): [Tilak|Dhivya] throw KeyInvalidatedException if key i…
tilak-puli Aug 14, 2023
da8b19d
feat(inji-214): [Tilak|Dhivya] fix keyinfo throwing exception when ru…
tilak-puli Aug 14, 2023
6dfb6ee
feat(INJI-214): add the exception block and remove all keys
dhivya0413 Aug 14, 2023
cfd3507
feat(inji-214): add api for biometric is enabled
dhivya0413 Aug 16, 2023
80ab687
feat(inji-214): [Tilak|Dhivya] move remove all keys into keystore
tilak-puli Aug 16, 2023
62586f1
feat(inji-214): [Tilak|Dhivya] Disable Invalidation of keys on biomet…
tilak-puli Aug 17, 2023
1d26b1f
feat(inji-214): [Tilak|Dhivya] Handle user not auth exception for sig…
tilak-puli Aug 17, 2023
610df42
feat(inji-214): [Tilak|Dhivya] Add a api to update biometric popup text
tilak-puli Aug 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 87 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# react-native-secure-keystore
A React native library helps to securelys store keys
A React native library helps to securely store keys in Hardware Key Store and use them to create signatures.

`note: This library only supported for android.`

## Installation

```sh
Expand All @@ -8,14 +11,95 @@ npm install react-native-secure-keystore

## Usage

1. for RSA based Key Pair

```js
import SecureKeyStore from "react-native-secure-keystore";

// ...

if(!SecureKeyStore.deviceSupportsHardware) {
return
}

const alias = "1234ab";
const data = "any data";

const publicKey = await SecureKeyStore.generateKeyPair(alias);

const signature = await SecureKeyStore.sign(alias, data)

```


2. for symmetric key

```js
import { multiply } from "react-native-secure-keystore";
import SecureKeyStore from "react-native-secure-keystore";

// ...

const result = await multiply(3, 7);
if(!SecureKeyStore.deviceSupportsHardware) {
return
}

const alias = "1234ab";
const data = "any data";

await SecureKeyStore.generateKey(alias);

const encryptedData = await SecureKeyStore.encryptData(alias, data)
const decryptedData = await SecureKeyStore.decryptData(alias, encryptedData)

```


## API documentation

### deviceSupportsHardware

`deviceSupportsHardware() => boolean`

Check if the device supports hardware key store


### generateKey

`generateKey(alias: string) => void`

generates a symmetric key for encryption and decryption

### generateKeyPair

`generateKey(alias: string) => string`

generates a asymmetric RSA key Pair for signing

### encryptData

`encryptData(alias: string, data: string) => string`

Encrypts the given data using the key that is assigned to the alias. Returns back encrypted data as a string

### decryptData

`decryptData(alias: string, encryptionText: string) => string`

Decrypts the given encryptionText using the key that is assigned to the alias. Returns back the data as a string

### sign

`sign(alias: string, data: string) => string`

Create a signature for the given data using the key that is assigned to the alias. Returns back the signature as a string

### hasAlias

`hasAlias(alias: string) => boolean`

Check if the given alias is present in the key store


## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
6 changes: 5 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// From node_modules
implementation 'com.madgag.spongycastle:core:1.56.0.0'
implementation 'com.madgag.spongycastle:prov:1.56.0.0'
implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.56.0.0'
implementation "androidx.biometric:biometric:1.1.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

if (isNewArchitectureEnabled()) {
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SecureKeystore_kotlinVersion=1.7.0
SecureKeystore_minSdkVersion=21
SecureKeystore_minSdkVersion=24
SecureKeystore_targetSdkVersion=31
SecureKeystore_compileSdkVersion=31
SecureKeystore_ndkversion=21.4.7075529
1 change: 1 addition & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativesecurekeystore">

<uses-permission android:name="android.permission.USE_BIOMETRIC" />
</manifest>
21 changes: 21 additions & 0 deletions android/src/main/java/com/reactnativesecurekeystore/CipherBox.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.reactnativesecurekeystore

import com.reactnativesecurekeystore.dto.EncryptedOutput
import java.security.Key
import java.security.PrivateKey
import java.security.Signature
import javax.crypto.Cipher
import javax.crypto.SecretKey

interface CipherBox {
fun initEncryptCipher(key: Key): Cipher
fun encryptData(cipher: Cipher, data: String): EncryptedOutput

fun initDecryptCipher(key: Key, encryptedOutput: EncryptedOutput): Cipher
fun decryptData(cipher: Cipher, encryptedOutput: EncryptedOutput): ByteArray

fun createSignature(key: PrivateKey, data: String): Signature
fun sign(signature: Signature): String

fun generateHmacSha(key: SecretKey, data: String): ByteArray
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.reactnativesecurekeystore

import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import com.reactnativesecurekeystore.dto.EncryptedOutput
import java.security.Key
import java.security.PrivateKey
import java.security.Signature
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec


const val CIPHER_ALGORITHM =
"${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}"
const val GCM_TAG_LEN = 128
const val SIGN_ALGORITHM = "SHA256with${KeyProperties.KEY_ALGORITHM_RSA}"
const val HMAC_ALGORITHM = "HmacSHA256"

class CipherBoxImpl : CipherBox {

override fun initEncryptCipher(key: Key): Cipher {
val cipher = Cipher.getInstance(CIPHER_ALGORITHM)
cipher.init(Cipher.ENCRYPT_MODE, key)

return cipher
}

override fun encryptData(cipher: Cipher, data: String): EncryptedOutput {
Log.d("CipherBox", "iv ${cipher.iv} data: $data" )
val encryptedData = cipher.doFinal(data.toByteArray());

return EncryptedOutput(encryptedData, cipher.iv)
}

override fun initDecryptCipher(key: Key, encryptedOutput: EncryptedOutput): Cipher {
val cipher = Cipher.getInstance(CIPHER_ALGORITHM)

val spec = GCMParameterSpec(GCM_TAG_LEN, encryptedOutput.iv)
cipher.init(Cipher.DECRYPT_MODE, key, spec)

return cipher
}

override fun decryptData(cipher: Cipher, encryptedOutput: EncryptedOutput): ByteArray {
try {
return cipher.doFinal(encryptedOutput.encryptedData);
} catch (e: Exception) {
Log.e("Secure","Exception in Decrypti",e)
throw e
}
}

override fun createSignature(key: PrivateKey, data: String): Signature {
val bytes = data.toByteArray(charset("UTF8"))
val signature = Signature.getInstance(SIGN_ALGORITHM).run {
initSign(key)
update(bytes)
this
}

return signature
}

override fun sign(signature: Signature): String {
val sign = signature.sign()
return Base64.encodeToString(sign, Base64.DEFAULT)
}

override fun generateHmacSha(key: SecretKey, data: String): ByteArray {
val mac = Mac.getInstance(HMAC_ALGORITHM);

try {
mac.init(key)
} catch (e: Exception) {
Log.e("debugging", "Exception in generatehmac", e)
throw e
}

return mac.doFinal(data.toByteArray())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.reactnativesecurekeystore

import android.os.Build
import android.security.keystore.KeyInfo
import android.util.Log
import com.reactnativesecurekeystore.biometrics.Biometrics
import java.security.GeneralSecurityException
import java.util.concurrent.atomic.AtomicBoolean
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory


class DeviceCapability(private val secureKeystore: SecureKeystore, private val KeyGenerator: KeyGeneratorImpl, private val biometrics: Biometrics) {
private val mutex = Object()

@Transient
var isSupportsSecureHardware: AtomicBoolean? = null

fun supportsHardwareKeyStore(): Boolean {
if (isSupportsSecureHardware != null) return isSupportsSecureHardware!!.get()

synchronized(mutex) {
/** double check if it has value in sync block */
if (isSupportsSecureHardware != null) return isSupportsSecureHardware!!.get()
// Check the key stored in secure hardware using temporary key alias, and removed after checked
val key = KeyGenerator.generateKey(CHECK_HARDWARE_SUPPORT_KEY_ALIAS,false, null)
isSupportsSecureHardware =
AtomicBoolean(getSecurityLevel(key) == DeviceSecurityLevel.SECURE_HARDWARE)
Log.i("SecureStorage", "Device Supports Hardware $isSupportsSecureHardware")
secureKeystore.removeKey(CHECK_HARDWARE_SUPPORT_KEY_ALIAS)
return isSupportsSecureHardware!!.get()
}
}

/** Get the supported level of security for provided Key instance. */
@Throws(GeneralSecurityException::class)
fun getSecurityLevel(key: SecretKey): DeviceSecurityLevel {
val keyInfo: KeyInfo = getKeyInfo(key)

val insideSecureHardware: Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
/** SecurityLevel 1 indicates SECURITY_LEVEL_TRUSTED_ENVIRONMENT
* SecurityLevel 2 indicates SECURITY_LEVEL_STRONGBOX */
(keyInfo.securityLevel == 1) or (keyInfo.securityLevel == 2)
} else {
keyInfo.isInsideSecureHardware()
}
Log.i("keystore", "Device has secure Hardware $insideSecureHardware")
if (insideSecureHardware) {
return DeviceSecurityLevel.SECURE_HARDWARE
}
return DeviceSecurityLevel.SECURE_SOFTWARE
}

/** Get information about provided key. */
@Throws(GeneralSecurityException::class)
fun getKeyInfo(key: SecretKey): KeyInfo {
Log.i("keystore", "KeyInfo Details$key -> ${key.algorithm}")
val secretKeyFactory = SecretKeyFactory.getInstance(key.algorithm, KEYSTORE_TYPE)
return secretKeyFactory.getKeySpec(
key as SecretKey?,
KeyInfo::class.java
) as KeyInfo
}

/** Get information about Device biometrics enrollment. */
fun hasBiometricsEnabled(): Boolean {
return biometrics.isBiometricEnabled()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.reactnativesecurekeystore

/** Minimal required level of the security implementation. */
enum class DeviceSecurityLevel {
/** Requires for the key to be stored in the Android Keystore, separate from the encrypted data. */
SECURE_SOFTWARE,

/** Requires for the key to be stored on a secure hardware (Trusted Execution Environment or Secure Environment). */
SECURE_HARDWARE;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.reactnativesecurekeystore

import java.security.KeyPair
import javax.crypto.SecretKey

interface KeyGenerator {
fun generateKey(alias: String, isAuthRequired: Boolean, authTimeout: Int?): SecretKey
fun generateHmacKey(hmacKeyAlias: String): SecretKey
fun generateKeyPair(alias: String, isAuthRequired: Boolean, authTimeout: Int?): KeyPair
}
Loading