diff --git a/mobile-app/context/DbContext.tsx b/mobile-app/context/DbContext.tsx index 6e0384aea..e4f0d042c 100644 --- a/mobile-app/context/DbContext.tsx +++ b/mobile-app/context/DbContext.tsx @@ -53,11 +53,14 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children } const initializeDatabase = useCallback(async (vaultResponse: VaultResponse, derivedKey: string) => { // Attempt to decrypt the blob. + console.log('attempt to decrypt vault'); const decryptedBlob = await EncryptionUtility.symmetricDecrypt( vaultResponse.vault.blob, derivedKey ); + console.log('decrypted blob', decryptedBlob); + // Initialize the SQLite client. const client = new SqliteClient(); await client.initializeFromBase64(decryptedBlob); diff --git a/mobile-app/ios/Podfile.lock b/mobile-app/ios/Podfile.lock index 5d7603d06..b694e16ca 100644 --- a/mobile-app/ios/Podfile.lock +++ b/mobile-app/ios/Podfile.lock @@ -1553,6 +1553,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-aes-gcm-crypto (0.2.2): + - React-Core - react-native-get-random-values (1.11.0): - React-Core - react-native-safe-area-context (4.12.0): @@ -2157,6 +2159,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-aes-gcm-crypto (from `../node_modules/react-native-aes-gcm-crypto`) - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-webview (from `../node_modules/react-native-webview`) @@ -2320,6 +2323,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-aes-gcm-crypto: + :path: "../node_modules/react-native-aes-gcm-crypto" react-native-get-random-values: :path: "../node_modules/react-native-get-random-values" react-native-safe-area-context: @@ -2455,6 +2460,7 @@ SPEC CHECKSUMS: React-logger: c4052eb941cca9a097ef01b59543a656dc088559 React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead + react-native-aes-gcm-crypto: d572dd7a69f31c539bb8309b3a829bfa3bfad244 react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba react-native-safe-area-context: cd916088cac5300c3266876218377518987b995e react-native-webview: 6b9fc65c1951203a3e958ff3cc0a858d4b6be901 diff --git a/mobile-app/package-lock.json b/mobile-app/package-lock.json index ff1da85d7..e4eefc340 100644 --- a/mobile-app/package-lock.json +++ b/mobile-app/package-lock.json @@ -29,6 +29,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.9", + "react-native-aes-gcm-crypto": "^0.2.2", "react-native-argon2": "^2.0.1", "react-native-gesture-handler": "~2.20.2", "react-native-get-random-values": "^1.11.0", @@ -11883,6 +11884,16 @@ } } }, + "node_modules/react-native-aes-gcm-crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/react-native-aes-gcm-crypto/-/react-native-aes-gcm-crypto-0.2.2.tgz", + "integrity": "sha512-vUwkh2zBiIQMRY191IfZhDmhHVT+nV4sxet/A0V8J35lVShCA4kuFzBL+QVB06RM2EF8oZSnNMt/uvFkKAx6QQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-argon2": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-native-argon2/-/react-native-argon2-2.0.1.tgz", diff --git a/mobile-app/package.json b/mobile-app/package.json index cd39af3f1..066ea6355 100644 --- a/mobile-app/package.json +++ b/mobile-app/package.json @@ -36,6 +36,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.9", + "react-native-aes-gcm-crypto": "^0.2.2", "react-native-argon2": "^2.0.1", "react-native-gesture-handler": "~2.20.2", "react-native-get-random-values": "^1.11.0", diff --git a/mobile-app/utils/EncryptionUtility.tsx b/mobile-app/utils/EncryptionUtility.tsx index f729d607d..33a7080cf 100644 --- a/mobile-app/utils/EncryptionUtility.tsx +++ b/mobile-app/utils/EncryptionUtility.tsx @@ -1,4 +1,5 @@ import argon2 from 'react-native-argon2'; +import AesGcmCrypto from 'react-native-aes-gcm-crypto'; import { Email } from './types/webapi/Email'; import { EncryptionKey } from './types/EncryptionKey'; import { MailboxEmail } from './types/webapi/MailboxEmail'; @@ -61,36 +62,18 @@ class EncryptionUtility { return plaintext; } - const key = await crypto.subtle.importKey( - "raw", - Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)), - { - name: "AES-GCM", - length: 256, - }, - false, - ["encrypt"] - ); - - const iv = crypto.getRandomValues(new Uint8Array(12)); - const encoder = new TextEncoder(); - const encoded = encoder.encode(plaintext); - - const ciphertext = await crypto.subtle.encrypt( - { name: "AES-GCM", iv: iv }, - key, - encoded - ); - - const combined = new Uint8Array(iv.length + ciphertext.byteLength); - combined.set(iv, 0); - combined.set(new Uint8Array(ciphertext), iv.length); - - return btoa( - Array.from(combined) - .map(byte => String.fromCharCode(byte)) - .join('') - ); + try { + const result = await AesGcmCrypto.encrypt(plaintext, false, base64Key); + // Combine IV, tag, and content into a single string for storage + return JSON.stringify({ + iv: result.iv, + tag: result.tag, + content: result.content + }); + } catch (error) { + console.error('AES-GCM encryption failed:', error); + throw error; + } } /** @@ -101,29 +84,28 @@ class EncryptionUtility { return base64Ciphertext; } - const key = await crypto.subtle.importKey( - "raw", - Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)), - { - name: "AES-GCM", - length: 256, - }, - false, - ["decrypt"] - ); + try { + const ciphertext = Uint8Array.from(atob(base64Ciphertext), c => c.charCodeAt(0)); + const iv = ciphertext.slice(0, 12); + const tag = ciphertext.slice(-16); + const content = ciphertext.slice(12, -16); - const ivAndCiphertext = Uint8Array.from(atob(base64Ciphertext), c => c.charCodeAt(0)); - const iv = ivAndCiphertext.slice(0, 12); - const ciphertext = ivAndCiphertext.slice(12); + const contentBase64 = Buffer.from(content).toString('base64'); + const ivHex = Buffer.from(iv).toString('hex'); + const tagHex = Buffer.from(tag).toString('hex'); - const decrypted = await crypto.subtle.decrypt( - { name: "AES-GCM", iv: iv }, - key, - ciphertext - ); - - const decoder = new TextDecoder(); - return decoder.decode(decrypted); + const decryptedData = await AesGcmCrypto.decrypt( + contentBase64, + base64Key, + ivHex, + tagHex, + false + ); + return decryptedData; + } catch (error) { + console.error('AES-GCM decryption failed:', error); + throw error; + } } /**