11/**
22 * AES-GCM Encryption/Decryption
3- * Using node-forge for compatibility with web app
3+ * Using react-native-quick-crypto when available (fast native implementation)
4+ * Falls back to node-forge for compatibility
45 */
56
67import forge from 'node-forge' ;
78
89import { ENCRYPTION_CONFIG } from '../config' ;
910
11+ // Try to import native crypto, but don't fail if not available (Expo Go)
12+ let createCipheriv : any = null ;
13+ let createDecipheriv : any = null ;
14+ let QuickCryptoBuffer : any = null ;
15+
16+ // Try to get Buffer from global scope (polyfilled by react-native-quick-crypto)
17+ try {
18+ // @ts -ignore - Buffer should be global after react-native-quick-crypto is loaded
19+ QuickCryptoBuffer = global . Buffer || Buffer ;
20+ } catch ( e ) {
21+ // Buffer not available globally
22+ }
23+
24+ try {
25+ const quickCrypto = require ( 'react-native-quick-crypto' ) ;
26+
27+ // Get the cipher functions
28+ createCipheriv = quickCrypto . createCipheriv ;
29+ createDecipheriv = quickCrypto . createDecipheriv ;
30+
31+ if ( createCipheriv && createDecipheriv && QuickCryptoBuffer ) {
32+ console . log ( '[Encryption] Native AES-GCM available - will use fast implementation' ) ;
33+ } else {
34+ console . log ( '[Encryption] Native AES-GCM partially available but missing functions' ) ;
35+ if ( __DEV__ ) {
36+ console . log ( '[Encryption] Available:' , {
37+ createCipheriv : ! ! createCipheriv ,
38+ createDecipheriv : ! ! createDecipheriv ,
39+ Buffer : ! ! QuickCryptoBuffer
40+ } ) ;
41+ }
42+ }
43+ } catch ( error ) {
44+ console . log ( '[Encryption] Native AES-GCM not available - using node-forge' ) ;
45+ }
46+
1047/**
1148 * Encrypt plaintext using AES-GCM
1249 */
@@ -15,30 +52,39 @@ export async function encryptWithAESGCM(
1552 keyBase64 : string ,
1653 ivBase64 : string
1754) : Promise < string > {
18- // Convert base64 to forge-compatible format
55+ // Try native implementation first (if available)
56+ if ( createCipheriv && QuickCryptoBuffer ) {
57+ try {
58+ const key = QuickCryptoBuffer . from ( keyBase64 , 'base64' ) ;
59+ const iv = QuickCryptoBuffer . from ( ivBase64 , 'base64' ) ;
60+
61+ const cipher = createCipheriv ( 'aes-256-gcm' , key , iv ) ;
62+
63+ let encrypted = cipher . update ( plaintext , 'utf8' ) ;
64+ encrypted = QuickCryptoBuffer . concat ( [ encrypted , cipher . final ( ) ] ) ;
65+
66+ const authTag = cipher . getAuthTag ( ) ;
67+ const encryptedWithTag = QuickCryptoBuffer . concat ( [ encrypted , authTag ] ) ;
68+
69+ return encryptedWithTag . toString ( 'base64' ) ;
70+ } catch ( error ) {
71+ console . warn ( '[Encryption] Native AES-GCM encryption failed, falling back to node-forge:' , error ) ;
72+ }
73+ }
74+
75+ // Fallback to node-forge
1976 const key = forge . util . decode64 ( keyBase64 ) ;
2077 const iv = forge . util . decode64 ( ivBase64 ) ;
2178
22- // Create AES-GCM cipher
2379 const cipher = forge . cipher . createCipher ( 'AES-GCM' , key ) ;
24-
25- // Start encryption with IV
2680 cipher . start ( { iv : forge . util . createBuffer ( iv ) } ) ;
27-
28- // Update with plaintext
2981 cipher . update ( forge . util . createBuffer ( plaintext , 'utf8' ) ) ;
30-
31- // Finish encryption
3282 cipher . finish ( ) ;
3383
34- // Get ciphertext and auth tag
3584 const ciphertext = cipher . output . getBytes ( ) ;
3685 const authTag = cipher . mode . tag . getBytes ( ) ;
37-
38- // Combine ciphertext + auth tag (Web Crypto API format)
3986 const encryptedWithTag = ciphertext + authTag ;
4087
41- // Convert to base64
4288 return forge . util . encode64 ( encryptedWithTag ) ;
4389}
4490
@@ -50,13 +96,45 @@ export async function decryptWithAESGCM(
5096 keyBase64 : string ,
5197 ivBase64 : string
5298) : Promise < string > {
53- // Convert base64 to forge-compatible format
99+ // Try native implementation first (if available)
100+ if ( createDecipheriv && QuickCryptoBuffer ) {
101+ try {
102+ const key = QuickCryptoBuffer . from ( keyBase64 , 'base64' ) ;
103+ const iv = QuickCryptoBuffer . from ( ivBase64 , 'base64' ) ;
104+ const encryptedDataWithTag = QuickCryptoBuffer . from ( encryptedBase64 , 'base64' ) ;
105+
106+ const tagLength = ENCRYPTION_CONFIG . GCM_TAG_LENGTH ;
107+
108+ if ( encryptedDataWithTag . length < tagLength ) {
109+ throw new Error (
110+ `Encrypted data too short for GCM (${ encryptedDataWithTag . length } bytes, need at least ${ tagLength } )`
111+ ) ;
112+ }
113+
114+ // Split the data: ciphertext + auth tag (last 16 bytes)
115+ const ciphertext = encryptedDataWithTag . subarray ( 0 , - tagLength ) ;
116+ const authTag = encryptedDataWithTag . subarray ( - tagLength ) ;
117+
118+ const decipher = createDecipheriv ( 'aes-256-gcm' , key , iv ) ;
119+ decipher . setAuthTag ( authTag ) ;
120+
121+ let decrypted = decipher . update ( ciphertext ) ;
122+ decrypted = QuickCryptoBuffer . concat ( [ decrypted , decipher . final ( ) ] ) ;
123+
124+ return decrypted . toString ( 'utf8' ) ;
125+ } catch ( error ) {
126+ if ( __DEV__ ) {
127+ console . error ( '[Encryption] ❌ Native AES-GCM decryption failed:' , error ) ;
128+ console . error ( '[Encryption] Error details:' , JSON . stringify ( error , Object . getOwnPropertyNames ( error ) ) ) ;
129+ }
130+ }
131+ }
132+
133+ // Fallback to node-forge
54134 const key = forge . util . decode64 ( keyBase64 ) ;
55135 const iv = forge . util . decode64 ( ivBase64 ) ;
56136 const encryptedDataWithTag = forge . util . decode64 ( encryptedBase64 ) ;
57137
58- // For node-forge GCM, we need to manually handle the auth tag
59- // Web Crypto API embeds the auth tag at the end of the encrypted data
60138 const tagLength = ENCRYPTION_CONFIG . GCM_TAG_LENGTH ;
61139
62140 if ( encryptedDataWithTag . length < tagLength ) {
@@ -65,23 +143,17 @@ export async function decryptWithAESGCM(
65143 ) ;
66144 }
67145
68- // Split the data: ciphertext + auth tag (last 16 bytes)
69146 const ciphertext = encryptedDataWithTag . slice ( 0 , - tagLength ) ;
70147 const authTag = encryptedDataWithTag . slice ( - tagLength ) ;
71148
72- // Create AES-GCM decipher
73149 const decipher = forge . cipher . createDecipher ( 'AES-GCM' , key ) ;
74-
75- // Start decryption with IV and auth tag
76150 decipher . start ( {
77151 iv : forge . util . createBuffer ( iv ) ,
78152 tag : forge . util . createBuffer ( authTag ) ,
79153 } ) ;
80154
81- // Update with ciphertext
82155 decipher . update ( forge . util . createBuffer ( ciphertext ) ) ;
83156
84- // Finish and verify auth tag
85157 const success = decipher . finish ( ) ;
86158
87159 if ( ! success ) {
0 commit comments