Skip to content

Commit

Permalink
use device salted PBKDF2 password hash
Browse files Browse the repository at this point in the history
  • Loading branch information
TheTrunk committed Feb 7, 2025
1 parent a5e331f commit ebd5dba
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 15 deletions.
33 changes: 20 additions & 13 deletions src/components/Authentication/Authentication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ const Authentication = (props: {
textForPrompt = t('home:auth_confirm_public_nonces');
}
console.log('Initiate Fingerprint');
// instead of this try to retrieve our keychain secret that has been stored with access control biometrics current set. Generate that secret on create/restore or here if it does not exist
// if we do not have it, we show fallback mechanism
// if success continue, if fail, show error message and only allow password authentication
rnBiometrics
.simplePrompt({
promptMessage: textForPrompt,
Expand Down Expand Up @@ -121,24 +124,28 @@ const Authentication = (props: {
try {
console.log('Grant Access');
// get from keychain
// encryption key
const encryptionKey = await Keychain.getGenericPassword({
service: 'enc_key',
const passwordHash = await Keychain.getGenericPassword({
service: 'sspkey_pw_hash',
});
const passwordData = await Keychain.getGenericPassword({
service: 'sspkey_pw',
// get salt
const saltData = await Keychain.getGenericPassword({
service: 'salt',
});
if (!passwordData || !encryptionKey) {
// from user password create hash
if (!passwordHash || !saltData) {
throw new Error('Unable to decrypt stored data');
}
const passwordDecrypted = CryptoJS.AES.decrypt(
passwordData.password,
encryptionKey.password,
// generate hash of our password
const key256Bits1000Iterations = CryptoJS.PBKDF2(
password,
saltData.password,
{
keySize: 256 / 32,
iterations: 100000, // more is too slow, favor performance
},
);
const passwordDecryptedString = passwordDecrypted.toString(
CryptoJS.enc.Utf8,
);
if (password !== passwordDecryptedString) {
const pwHash = key256Bits1000Iterations.toString();
if (passwordHash.password !== pwHash) {
displayMessage('error', t('home:err_auth_pw_incorrect'));
return;
}
Expand Down
24 changes: 23 additions & 1 deletion src/screens/Create/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,26 @@ function Create({ navigation }: Props) {
await Keychain.resetGenericPassword({
service: 'sspkey_pw',
});
await Keychain.resetGenericPassword({
service: 'sspkey_pw_hash',
});
await Keychain.resetGenericPassword({
service: 'fcm_key_token',
});
await Keychain.resetGenericPassword({
service: 'salt',
});
const rnd = crypto.getRandomValues(new Uint8Array(64));
const encKey = Buffer.from(rnd).toString('hex');
await Keychain.setGenericPassword('enc_key', encKey, {
service: 'enc_key',
});
// generate salt
const salt = CryptoJS.lib.WordArray.random(64).toString();
// store salt, used for hashing password
await Keychain.setGenericPassword('salt', salt, {
service: 'salt',
});
const pwForEncryption = encKey + password;
const mnemonicBlob = CryptoJS.AES.encrypt(
mnemonicPhrase,
Expand Down Expand Up @@ -206,7 +218,17 @@ function Create({ navigation }: Props) {
const xpubBlob = CryptoJS.AES.encrypt(xpub, pwForEncryption).toString();
setXprivKeyIdentity(xprivBlob);
setXpubKeyIdentity(xpubBlob);
// In keychain plain password is stored (only password not id)
// generate hash of our password
const key256Bits1000Iterations = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 100000, // more is too slow, favor performance
});
const pwHash = key256Bits1000Iterations.toString();
// store the pwHash
// this is used in case password is supplied and not biometrics
await Keychain.setGenericPassword('sspkey_pw_hash', pwHash, {
service: 'sspkey_pw_hash',
});
// encrypt password with enc_key
const encryptedPassword = CryptoJS.AES.encrypt(
password,
Expand Down
24 changes: 23 additions & 1 deletion src/screens/Restore/Restore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,26 @@ function Restore({ navigation }: Props) {
await Keychain.resetGenericPassword({
service: 'sspkey_pw',
});
await Keychain.resetGenericPassword({
service: 'sspkey_pw_hash',
});
await Keychain.resetGenericPassword({
service: 'fcm_key_token',
});
await Keychain.resetGenericPassword({
service: 'salt',
});
const rnd = crypto.getRandomValues(new Uint8Array(64));
const encKey = Buffer.from(rnd).toString('hex');

await Keychain.setGenericPassword('enc_key', encKey, {
service: 'enc_key',
});
// generate salt
const salt = CryptoJS.lib.WordArray.random(64).toString();
// store salt, used for hashing password
await Keychain.setGenericPassword('salt', salt, {
service: 'salt',
});
const pwForEncryption = encKey + password;
const mnemonicBlob = CryptoJS.AES.encrypt(
mnemonicPhrase,
Expand Down Expand Up @@ -240,6 +251,17 @@ function Restore({ navigation }: Props) {
const xpubBlob = CryptoJS.AES.encrypt(xpub, pwForEncryption).toString();
setXprivKeyIdentity(xprivBlob);
setXpubKeyIdentity(xpubBlob);
// generate hash of our password
const key256Bits1000Iterations = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 100000, // more is too slow, favor performance
});
const pwHash = key256Bits1000Iterations.toString();
// store the pwHash
// this is used in case password is supplied and not biometrics
await Keychain.setGenericPassword('sspkey_pw_hash', pwHash, {
service: 'sspkey_pw_hash',
});
// encrypt password with enc_key
const encryptedPassword = CryptoJS.AES.encrypt(
password,
Expand Down
18 changes: 18 additions & 0 deletions src/screens/Startup/Startup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ const Startup = ({ navigation }: ApplicationScreenProps) => {
await Keychain.setGenericPassword('enc_key', encKey, {
service: 'enc_key',
});
// generate salt
const salt = CryptoJS.lib.WordArray.random(64).toString();
// store salt, used for hashing password
await Keychain.setGenericPassword('salt', salt, {
service: 'salt',
});
// generate hash of our password
const key256Bits1000Iterations = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 100000, // more is too slow, favor performance
});
const pwHash = key256Bits1000Iterations.toString();
// store the pwHash
// this is used in case password is supplied and not biometrics
await Keychain.setGenericPassword('sspkey_pw_hash', pwHash, {
// this encrypted one should be for biometrics?
service: 'sspkey_pw_hash',
});
// encrypt our password with enc_key
const encryptedPassword = CryptoJS.AES.encrypt(
password,
Expand Down

0 comments on commit ebd5dba

Please sign in to comment.