react-native-secure-sign
v0.0.1
Published
react-native-secure-sign lets you generates and use cryptographic signatures backed by hardware security on iOS and Android
Readme
react-native-secure-sign
react-native-secure-sign provides a cross-platform API to generate and use non-exportable signing keys stored in Android Keystore(hardware-backed via TEE or StrongBox) and iOS Secure Enclave, with optional biometric authentication.
Why use hardware-backed signing keys?
Keys generated by react-native-secure-sign are stored inside the device’s secure hardware and can never be extracted, even by the application itself.
Hardware-backed means that cryptographic keys are generated, stored, and used inside dedicated secure hardware, not in normal app memory or software.
This provides strong security guarantees:
Private keys are protected from malware and memory dumps
The key material never enters application memory and cannot be read or copied.Signatures can only be created on the physical device
A valid signature proves that the operation was performed on a specific device.Biometric authentication can be enforced for every signing operation
Each signature can require Face ID, Touch ID, or Android biometric authentication.Compromising the app does not compromise the signing key
Even with full app-level compromise, the private key remains inaccessible.
Hardware-backed keys provide a higher level of security than software-based key storage and are recommended for authentication, device binding, and high-trust cryptographic operations.
Installation
Important: This library requires React Native's New Architecture (Fabric + TurboModules) to be enabled. The legacy architecture is not supported.
npm install react-native-secure-sign
# or
yarn add react-native-secure-signReact Native CLI
iOS
- If you generate key with
requireUserAuthenticationset to true you need to add Face ID usage description to yourios/YourApp/Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID / Touch ID to securely authenticate key operations in the Secure Enclave.</string>This permission is required for biometric authentication when using the Secure Enclave on iOS.
Expo (Bare Workflow)
This library supports Expo bare workflow (projects created with expo prebuild) and builds created with expo-dev-client. However, The app won't work with Expo Go.
iOS
- If you generate key with
requireUserAuthenticationthen add Face ID usage description in yourapp.jsonorapp.config.js:
{
"expo": {
"ios": {
"infoPlist": {
"NSFaceIDUsageDescription": "This app uses Face ID / Touch ID to securely authenticate key operations in the Secure Enclave."
}
}
}
}Device Requirements
Note: The library requires devices with hardware-backed keystore support (most modern Android devices) and Secure Enclave support (iOS devices with Touch ID/Face ID). Use checkHardwareSupport() to verify device compatibility.
Usage
Methods
generate(keyId: string, options?: { requireUserAuthentication?: boolean }): Promise<string>
Generates a new key pair in the Secure Enclave (iOS) or Hardware Security Module (Android).
Parameters:
keyId(string): Unique identifier for the keyoptions(object, optional):requireUserAuthentication(boolean, default: true): Require biometric authentication to use the key
Returns: Promise - Base64url-encoded SPKI DER public key
Example:
const publicKey = await generate('my-key', { requireUserAuthentication: true });getPublicKey(keyId: string): Promise<string>
Retrieves the public key for an existing key pair.
Parameters:
keyId(string): Unique identifier for the key
Returns: Promise - Base64url-encoded SPKI DER public key
Example:
const publicKey = await getPublicKey('my-key');removeKey(keyId: string): Promise<void>
Removes a key pair from the secure storage.
Parameters:
keyId(string): Unique identifier for the key
Example:
await removeKey('my-key');checkHardwareSupport(): Promise<boolean>
Checks if the device supports hardware-backed secure storage.
Returns: Promise - Whether hardware-backed security is supported
Example:
const supported = await checkHardwareSupport();sign(keyId: string, information: string): Promise<string>
Signs information using a private key stored in the hardware-backed secure storage.
Parameters:
keyId(string): Unique identifier for the keyinformation(Base64url string): Information to sign
Returns: Promise - Base64url-encoded P1363 signature
Error Codes
This library returns only error codes, not error messages. All error handling should be based on the numeric codes.
Error Code Structure
Error codes are organized by category:
- 1001-1012: Key generation and management errors
- 2001-2004: Biometric authentication errors
- 3001: Decode error
- 4001-4002: Signature conversion errors
- 9999: Unknown/unexpected errors
Error Codes
Key Generation and Management (1001-1012)
| Code | Description | Possible Causes |
| ------ | ------------------------------------ | -------------------------------------------------------------- |
| 1001 | Key generation failed | Secure Enclave error, insufficient permissions, hardware issue |
| 1002 | Public key extraction failed | Key exists but public key cannot be extracted |
| 1003 | Access control creation failed | Invalid biometric settings, system error |
| 1004 | Key deletion failed | Authentication error, invalid parameters |
| 1005 | Key not found | The specified key was not found in the Keychain |
| 1006 | Invalid key ID | The provided key identifier is invalid or cannot be processed |
| 1007 | Invalid key reference | The retrieved key reference is not of the expected type |
| 1008 | Authentication failed | Biometric or passcode authentication failed for key access |
| 1009 | Keychain query failed | A general error occurred during a Keychain query operation |
| 1010 | Public key format conversion failed | SEC1 to SPKI DER conversion failed (Rust FFI error) |
| 1011 | Key already exists | Attempting to generate key with existing alias |
| 1012 | Key info extraction failed (Android) | Cannot retrieve key properties from KeyStore |
Biometric Authentication (2001-2004)
| Code | Description | Possible Causes |
| ------ | -------------------------------------- | ------------------------------------------------------- |
| 2001 | Biometric authentication not available | Device doesn't support biometrics, disabled in settings |
| 2002 | No biometric data enrolled | User hasn't set up Touch ID/Face ID |
| 2003 | Biometric authentication locked out | Too many failed biometric attempts |
| 2004 | Authentication cancelled | User cancelled the biometric authentication prompt |
Decode Error
| Code | Description | Possible Causes |
| ------ | ------------ | ---------------------- |
| 3001 | Decode error | Decode base64Url error |
Signature Conversion Errors (4001-4002)
| Code | Description | Possible Causes |
| ------ | --------------------------- | ----------------------------------------------- |
| 4001 | Invalid DER format | DER signature format is invalid or corrupted |
| 4002 | Signature conversion failed | Failed to convert DER signature to P1363 format |
Unknown Errors (9999)
| Code | Description | Possible Causes |
| ------ | ------------- | -------------------------------------------- |
| 9999 | Unknown error | Unexpected system error, unhandled exception |
Usage in JavaScript
Import
import {
generate,
sign,
getPublicKey,
removeKey,
checkHardwareSupport,
} from 'react-native-secure-sign';checkHardwareSupport()
Check if the device supports hardware-backed secure storage before using other methods.
try {
const supported = await checkHardwareSupport();
if (supported) {
console.log('Device supports hardware signing');
} else {
console.log('Device does not support hardware signing');
}
} catch (error) {
console.error('Error checking hardware support:', error);
}generate()
Generate a new key pair in the Secure Enclave (iOS) or Hardware Security Module (Android).
try {
const publicKey = await generate('my-unique-key-id', {
requireUserAuthentication: true,
});
console.log('Public Key (Base64url):', publicKey);
} catch (error) {
console.error('Error Code:', error.code);
switch (error.code) {
case '1011':
console.log('Key already exists. Remove it first or use a different keyId');
break;
case '2001':
console.log('Biometric authentication not available on this device');
break;
case '2002':
console.log('Please set up Touch ID/Face ID in device settings');
break;
case '2003':
console.log('Too many failed biometric attempts');
break;
case '1001':
console.log('Failed to generate key');
break;
case '1003':
console.log('Failed to create access control');
break;
case '1006':
console.log('Invalid key ID provided');
break;
default:
console.log('Unknown error:', error.code);
break;
}
}getPublicKey()
Retrieve the public key for an existing key pair.
try {
const publicKey = await getPublicKey('my-unique-key-id');
console.log('Public Key (Base64url):', publicKey);
} catch (error) {
console.error('Error Code:', error.code);
switch (error.code) {
case '1005':
console.log('Key not found. Generate a key first');
break;
case '1006':
console.log('Invalid key ID provided');
break;
case '1008':
console.log('Authentication failed or cancelled');
break;
case '1002':
console.log('Failed to extract public key');
break;
default:
console.log('Unknown error:', error.code);
break;
}
}sign()
Sign data using a private key stored in hardware-backed secure storage.
try {
const dataToSign = 'Hello, World!';
const dataBase64url = "SGVsbG8sIFdvcmxkIQ"
const signature = await sign('my-unique-key-id', dataBase64url);
console.log('Signature (Base64url P1363):', signature);
} catch (error) {
console.error('Error Code:', error.code);
switch (error.code) {
case '1005':
console.log('Key not found. Generate a key first');
break;
case '1008':
console.log('Authentication failed or cancelled');
break;
case '2001':
console.log('Biometric authentication not available');
break;
case '2002':
console.log('Please set up biometric authentication');
break;
console.log('Too many failed attempts. Unlock device first');
break;
case '2004':
console.log('User cancelled authentication');
break;
case '3001':
console.log('Invalid input format. Provide Base64url encoded data');
break;
case '4001':
console.log('Invalid signature format');
break;
case '4002':
console.log('Failed to convert signature format');
break;
case '5001':
console.log('Signing algorithm not supported');
break;
default:
console.log('Unknown error:', error.code);
break;
}
}removeKey()
Remove a key pair from hardware.
try {
await removeKey('my-unique-key-id');
console.log('Key removed successfully');
} catch (error) {
console.error('Error Code:', error.code);
switch (error.code) {
case '1005':
console.log('Key not found. It may already be removed');
break;
case '1004':
console.log('Failed to delete key');
break;
case '1006':
console.log('Invalid key ID provided');
break;
case '1008':
console.log('Authentication required to remove key');
break;
default:
console.log('Unknown error:', error.code);
break;
}
}Testing with Backend
To test the complete signing process, you can run the backend and connect it to your mobile application:
Clone the backend repository:
git clone https://github.com/react-native-secure-sign/react-native-secure-sign-be.git cd react-native-secure-sign-beRun the backend:
# Follow the instructions in the backend repository # The backend runs on port 8080 by defaultConfigure the mobile application:
- Ensure that the mobile app and backend are on the same network
- Update
API_BASEin your app code to point to your computer's IP address (e.g.,http://192.168.1.100:8080)
Test the signing process:
- Run the mobile application
- Execute the registration flow, which will generate a key, sign the challenge, and send the data to the backend
- The backend will verify the signature and confirm the correctness of the process
The backend implements a complete registration flow with signature verification, allowing you to comprehensively test the entire signing process using hardware-backed security keys.
Example of a complete registration flow using the library:
import {
generate,
sign,
getPublicKey,
removeKey,
checkHardwareSupport,
} from 'react-native-secure-sign';
import axios from 'axios';
const KEY_ID = 'com.example.app.key';
// react-native-secure-sign-be runs on port 8080
const API_BASE = 'http://X.X.X.X:8080'
async function registerAccount() {
try {
// 1. Initiate registration challenge with server
const challengeResponse = await axios.post(
`${API_BASE}/v1/register/initiate-challenge`
);
const { challengeId, informationToSign_b64u } = challengeResponse.data;
// 2. Generate key pair (if not exists)
let publicKey;
publicKey = await generate(KEY_ID, {
requireUserAuthentication: true,
});
// 3. Sign the challenge
const signature = await sign(KEY_ID, informationToSign_b64u);
// 4. Complete registration with server
const finishResponse = await axios.post(
`${API_BASE}/v1/register/finish-challenge`,
{
challengeId,
signature,
publicKey,
}
);
console.log('Registration successful:', finishResponse.data);
return finishResponse.data;
} catch (error) {
switch (error.code) {
// Key Management Errors (1001-1012)
case '1001':
console.error('Key generation failed. Check device hardware support.');
break;
case '1002':
console.error('Public key extraction failed.');
break;
case '1003':
console.error('Access control creation failed. Check biometric settings.');
break;
case '1004':
console.error('Key deletion failed.');
break;
case '1005':
console.error('Key not found. Generate a key first.');
break;
case '1006':
console.error('Invalid key ID provided.');
break;
case '1007':
console.error('Invalid key reference.');
break;
case '1008':
console.error('Authentication failed. Please try again.');
break;
case '1009':
console.error('Keychain query failed. System error.');
break;
case '1010':
console.error('Public key format conversion failed.');
break;
case '1011':
console.error('Key already exists. Use existing key or remove it first.');
break;
case '1012':
console.error('Key info extraction failed.');
break;
// Biometric Authentication Errors (2001-2004)
case '2001':
console.error('Biometric authentication not available. Enable biometrics in device settings.');
break;
case '2002':
console.error('No biometric data enrolled. Please set up Touch ID/Face ID.');
break;
case '2003':
console.error('Biometric authentication locked out. Please unlock device and try again.');
break;
case '2004':
console.error('Authentication cancelled by user.');
break;
// Decode Error (3001)
case '3001':
console.error('Invalid input. Check base64Url format.');
break;
// Signature Conversion Errors (4001-4002)
case '4001':
console.error('Invalid DER format.');
break;
case '4002':
console.error('Signature conversion failed.');
break;
// Unknown Error (9999)
case '9999':
console.error('Unknown error occurred.');
break;
default:
console.error('Unhandled error occurred:', error);
}
}
}