From 8679911c65c5ea2f7a7ae51592a2a935fe4acd3a Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 17 Nov 2024 21:40:16 +0200 Subject: [PATCH] move bcrypt operations to webworkers --- android/app/build.gradle | 3 + .../Qortal/qortalMobile/MainActivity.java | 12 +++- .../Qortal/qortalMobile/NativeBcrypt.java | 62 +++++++++++++++++++ package-lock.json | 4 +- src/ExtStates/NotAuthenticated.tsx | 2 +- src/deps/bcryptworker.worker.js | 12 ++++ src/deps/kdf.ts | 30 ++++++++- src/utils/nativebcrypt.ts | 9 +++ 8 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/java/com/github/Qortal/qortalMobile/NativeBcrypt.java create mode 100644 src/deps/bcryptworker.worker.js create mode 100644 src/utils/nativebcrypt.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 00f7b31..d12aa37 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,6 +36,9 @@ dependencies { implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" implementation project(':capacitor-android') + implementation "org.mindrot:jbcrypt:0.4" + implementation "at.favre.lib:bcrypt:0.10.2" + implementation 'com.password4j:password4j:1.8.2' testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java index f5ad5fd..1351888 100644 --- a/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/MainActivity.java @@ -1,5 +1,15 @@ package com.github.Qortal.qortalMobile; import com.getcapacitor.BridgeActivity; +import com.github.Qortal.qortalMobile.NativeBcrypt; +import android.os.Bundle; -public class MainActivity extends BridgeActivity {} +public class MainActivity extends BridgeActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + registerPlugin(NativeBcrypt.class); + super.onCreate(savedInstanceState); + + + } +} diff --git a/android/app/src/main/java/com/github/Qortal/qortalMobile/NativeBcrypt.java b/android/app/src/main/java/com/github/Qortal/qortalMobile/NativeBcrypt.java new file mode 100644 index 0000000..15db209 --- /dev/null +++ b/android/app/src/main/java/com/github/Qortal/qortalMobile/NativeBcrypt.java @@ -0,0 +1,62 @@ +package com.github.Qortal.qortalMobile; + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import android.os.Process; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.JSObject; +import com.getcapacitor.PluginMethod; +import com.getcapacitor.annotation.CapacitorPlugin; +import org.mindrot.jbcrypt.BCrypt; + +@CapacitorPlugin(name = "NativeBcrypt") +public class NativeBcrypt extends Plugin { + // Use a fixed thread pool with the number of CPU cores + private final ExecutorService executor = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setPriority(Thread.MAX_PRIORITY); // Set thread priority to high + return thread; + } + } + ); + + @PluginMethod + public void hashPassword(PluginCall call) { + String password = call.getString("password"); + String salt = call.getString("salt"); + + if (password == null || salt == null) { + call.reject("Password or salt is missing"); + return; + } + + executor.execute(() -> { + try { + // Perform bcrypt hashing + String hash = BCrypt.hashpw(password, salt); + + // Prepare the result + JSObject result = new JSObject(); + result.put("hash", hash); + + // Resolve the call on the main thread + getActivity().runOnUiThread(() -> call.resolve(result)); + } catch (Exception e) { + // Reject the call on the main thread in case of an error + getActivity().runOnUiThread(() -> call.reject("Hashing failed: " + e.getMessage())); + } + }); + } + + @Override + public void handleOnDestroy() { + super.handleOnDestroy(); + executor.shutdown(); // Shutdown the executor to release resources + } +} diff --git a/package-lock.json b/package-lock.json index 1f544fd..857d8d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "qortal-go", - "version": "0.3.1", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qortal-go", - "version": "0.3.1", + "version": "0.3.2", "dependencies": { "@capacitor/android": "^6.1.2", "@capacitor/app": "^6.0.1", diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index 025fac5..b846239 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -23,7 +23,7 @@ import { set } from "lodash"; import { cleanUrl, isUsingLocal } from "../background"; export const manifestData = { - version: '0.3.2' + version: '0.3.2.1' } export const NotAuthenticated = ({ diff --git a/src/deps/bcryptworker.worker.js b/src/deps/bcryptworker.worker.js new file mode 100644 index 0000000..d6d048a --- /dev/null +++ b/src/deps/bcryptworker.worker.js @@ -0,0 +1,12 @@ +import bcrypt from 'bcryptjs' + + +self.onmessage = function (e) { + const { hashBase64, salt } = e.data; + try { + const result = bcrypt.hashSync(hashBase64, salt); + self.postMessage({ result }); + } catch (error) { + self.postMessage({ error: error.message }); + } +}; \ No newline at end of file diff --git a/src/deps/kdf.ts b/src/deps/kdf.ts index 98bdc36..c9c5d72 100644 --- a/src/deps/kdf.ts +++ b/src/deps/kdf.ts @@ -3,7 +3,11 @@ import {bytes_to_base64 as bytesToBase64, Sha512} from 'asmcrypto.js' import bcrypt from 'bcryptjs' import utils from '../utils/utils' +// import NativeBcrypt from '../utils/nativebcrypt' +import BcryptWorker from './bcryptworker.worker.js?worker'; import { crypto } from '../constants/decryptWallet' + + const stringtoUTF8Array = (message)=> { if (typeof message === 'string') { var s = unescape(encodeURIComponent(message)) // UTF-8 @@ -15,6 +19,27 @@ const stringtoUTF8Array = (message)=> { return message } +const bcryptInWorker = (hashBase64, salt) => { + return new Promise((resolve, reject) => { + const worker = new BcryptWorker() + worker.onmessage = (e) => { + const { result, error } = e.data; + if (error) { + reject(error); + } else { + resolve(result); + } + worker.terminate(); + }; + worker.onerror = (err) => { + reject(err.message); + worker.terminate(); + }; + worker.postMessage({ hashBase64, salt }); + }); + }; + + const stringToUTF8Array=(message)=> { if (typeof message !== 'string') return message; // Assuming you still want to pass through non-string inputs unchanged const encoder = new TextEncoder(); // TextEncoder defaults to UTF-8 @@ -23,10 +48,11 @@ const stringToUTF8Array=(message)=> { const computekdf = async (req)=> { const { salt, key, nonce, staticSalt, staticBcryptSalt } = req const combinedBytes = utils.appendBuffer(new Uint8Array([]), stringToUTF8Array(`${staticSalt}${key}${nonce}`)) + const sha512Hash = new Sha512().process(combinedBytes).finish().result const sha512HashBase64 = bytesToBase64(sha512Hash) - const result = bcrypt.hashSync(sha512HashBase64.substring(0, 72), staticBcryptSalt) - + + const result = await bcryptInWorker(sha512HashBase64.substring(0, 72), staticBcryptSalt); return { key, nonce, result } } diff --git a/src/utils/nativebcrypt.ts b/src/utils/nativebcrypt.ts new file mode 100644 index 0000000..9b7a141 --- /dev/null +++ b/src/utils/nativebcrypt.ts @@ -0,0 +1,9 @@ +import { registerPlugin } from '@capacitor/core'; + +export interface NativeBcryptPlugin { + hashPassword(options: { password: string; salt: string }): Promise<{ hash: string }>; +} + +const NativeBcrypt = registerPlugin('NativeBcrypt'); + +export default NativeBcrypt; \ No newline at end of file