Zero trust. Zero compromise.
A complete breakdown of how NeuroKey protects your data, what threats it defends against, and the cryptographic guarantees it provides.
Threat model
Security is only meaningful in the context of what you're protecting against. Here's every threat we've modeled and how NeuroKey addresses each one.
Server breach / data exfiltration
No server exists. There is nothing to breach.
Brute-force of master password
Master password is never stored in plaintext. It is aggressively hashed via PBKDF2 strictly for key derivation.
Device theft
Vault requires biometric unlock. Encrypted with AES-256 at rest.
Physical memory extraction
Key is zeroed from memory after use. Secure Enclave prevents extraction of the biometric handle.
Reverse engineering & app tampering
Production Android builds utilize ProGuard (R8) code obfuscation, shrinking resources and masking execution logic.
Encryption pipeline
A step-by-step walkthrough of how your vault goes from plaintext to ciphertext — and back.
Biometric trigger
User authenticates via Face ID, Touch ID, or Android Biometrics. The OS handles the scan and passes a success token to the Expo runtime to unlock the SecureStore.
import * as LocalAuth from 'expo-local-authentication';
const result = await LocalAuth.authenticateAsync({
promptMessage: 'Unlock NeuroKey Vault',
fallbackLabel: 'Use Master Password',
disableDeviceFallback: false,
});
// On success → release SecureStore key handlePBKDF2 key derivation
The biometric byte array is used as PBKDF2 key material. With 5,000 SHA-256 rounds and a random 256-bit salt, the resulting AES-256 key is computationally infeasible to brute-force.
const rawKey = await crypto.subtle.importKey(
"raw", biometricBytes, "PBKDF2",
false, ["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 5_000,
hash: "SHA-256" },
rawKey,
{ name: "AES", length: 256 },
false, ["encrypt", "decrypt"]
);AES-256 encryption
The vault JSON is encrypted with AES-256. A fresh random 96-bit IV is generated for every write. The authentication tag (128-bit) detects any byte-level tampering on the ciphertext.
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES", iv,
additionalData: vaultMetadata },
aesKey,
new TextEncoder().encode(vaultJSON)
);
// Ciphertext includes 128-bit auth tag
// Any tampering → decryption throwsEncrypted AsyncStorage & SecureStore
The encrypted JSON blob is written to AsyncStorage. The highly sensitive decryption keys are stored separately in the device's hardware-backed Keychain/Keystore via expo-secure-store.
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
// 1. Store the encrypted vault blob
await AsyncStorage.setItem('@neurokey_vault', ciphertext);
// 2. Lock the AES key in hardware Keystore
await SecureStore.setItemAsync(
'ENCRYPTION_KEY_HANDLE',
aesKey,
{ requireAuthentication: true }
);How k-anonymity keeps your password private.
To check if a password has been leaked, NeuroKey uses the k-anonymity model from the Have I Been Pwned API. Here's the exact protocol:
- 1
Hash the password
Compute SHA-1 of the candidate password locally. Example: SHA-1("hunter2") = AB87D2...
- 2
Truncate to 5 chars
Take only the first 5 hex characters (the "prefix"): AB87D. This is what's transmitted.
- 3
Query the API
Send GET /range/AB87D. The API returns all hash suffixes that share this prefix — typically 400–900 entries.
- 4
Match locally
Compare the full local hash against the returned suffixes. A match means the password is in a breach database. No full hash or plaintext ever leaves your device.
k-anonymity protocol
1. Local hash
SHA1("hunter2") = AB87D24BEA...2. Prefix sent to API
GET /range/AB87D → server never sees full hash3. API returns suffixes
24BEA...:34221
7F3AC...:891
C4D90...:12
...(600+ entries)4. Local match check
24BEA matched! Password found in 34,221 breaches.Don't trust. Verify.
NeuroKey is fully open source. Every cryptographic claim on this page can be verified in the source code. No black boxes.
0
Network requests per vault operation
5k
PBKDF2 iterations per key derivation
256-bit
AES key length
0
Bytes stored on any server