@capacitor-community/sqlite 5.0.5 → 5.0.7-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +3 -0
  2. package/android/src/main/java/com/getcapacitor/community/database/sqlite/CapacitorSQLite.java +9 -0
  3. package/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/Database.java +9 -0
  4. package/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/ImportExportJson/UtilsEncryption.java +111 -0
  5. package/android/src/main/java/com/getcapacitor/community/database/sqlite/SQLite/UtilsSecret.java +3 -3
  6. package/dist/esm/definitions.d.ts +66 -0
  7. package/dist/esm/definitions.js +37 -0
  8. package/dist/esm/definitions.js.map +1 -1
  9. package/dist/esm/web.d.ts +3 -1
  10. package/dist/esm/web.js +8 -0
  11. package/dist/esm/web.js.map +1 -1
  12. package/dist/plugin.cjs.js +45 -0
  13. package/dist/plugin.cjs.js.map +1 -1
  14. package/dist/plugin.js +45 -0
  15. package/dist/plugin.js.map +1 -1
  16. package/electron/dist/plugin.js +138 -52
  17. package/electron/dist/plugin.js.map +1 -1
  18. package/electron/rollup.config.js +2 -0
  19. package/ios/Plugin/CapacitorSQLite.swift +117 -88
  20. package/ios/Plugin/Database.swift +17 -5
  21. package/ios/Plugin/ImportExportJson/ImportData.swift +434 -0
  22. package/ios/Plugin/ImportExportJson/ImportFromJson.swift +43 -57
  23. package/ios/Plugin/ImportExportJson/JsonSQLite.swift +7 -0
  24. package/ios/Plugin/Utils/UtilsDelete.swift +506 -0
  25. package/ios/Plugin/Utils/UtilsJson.swift +123 -1
  26. package/ios/Plugin/Utils/UtilsSQLCipher.swift +173 -477
  27. package/ios/Plugin/Utils/UtilsSQLStatement.swift +450 -0
  28. package/package.json +2 -2
  29. package/src/definitions.ts +104 -0
  30. package/src/web.ts +10 -0
@@ -7,6 +7,13 @@
7
7
  //
8
8
 
9
9
  import Foundation
10
+
11
+ public struct EncryptJson: Codable {
12
+ let expData: String
13
+ public func show() {
14
+ print("expData: \(expData) ")
15
+ }
16
+ }
10
17
  public struct JsonSQLite: Codable {
11
18
  let database: String
12
19
  let version: Int
@@ -0,0 +1,506 @@
1
+ //
2
+ // UtilsDelete.swift
3
+ // CapacitorCommunitySqlite
4
+ //
5
+ // Created by Quéau Jean Pierre on 30/07/2023.
6
+ //
7
+
8
+ import Foundation
9
+ enum UtilsDeleteError: Error {
10
+ case findReferencesAndUpdate(message: String)
11
+ case getReferences(message: String)
12
+ case getRefs(message: String)
13
+ case getUpdDelReturnedValues(message: String)
14
+ case getFirstPK(message: String)
15
+ case extractColumnNames(message: String)
16
+ case executeUpdateForDelete(message: String)
17
+ case extractForeignKeyInfo(message: String)
18
+ case searchForRelatedItems(message: String)
19
+ case upDateWhereForRestrict(message: String)
20
+ case upDateWhereForCascade(message: String)
21
+ }
22
+
23
+ // swiftlint:disable type_body_length
24
+ class UtilsDelete {
25
+
26
+ // MARK: - findReferencesAndUpdate
27
+
28
+ // swiftlint:disable function_body_length
29
+ // swiftlint:disable cyclomatic_complexity
30
+ class func findReferencesAndUpdate(mDB: Database, tableName: String,
31
+ whereStmt: String,
32
+ initColNames: [String],
33
+ values: [Any]) throws -> Bool {
34
+ do {
35
+ var retBool: Bool = true
36
+ let result = try getReferences(mDB: mDB,
37
+ tableName: tableName)
38
+ let references = result.retRefs
39
+ let tableNameWithRefs = result.tableWithRefs
40
+ if references.count <= 0 {
41
+ return retBool
42
+ }
43
+ if tableName == tableNameWithRefs {
44
+ return retBool
45
+ }
46
+ // Loop through references
47
+ for ref in references {
48
+
49
+ // Extract the FOREIGN KEY constraint info
50
+ // from the ref statement
51
+ let foreignKeyInfo = try UtilsSQLStatement
52
+ .extractForeignKeyInfo(from: ref)
53
+ // get the tableName of the references
54
+ guard let refTable = foreignKeyInfo["tableName"]
55
+ as? String else {
56
+ let msg = "findReferencesAndUpdate: no foreignKeyInfo " +
57
+ "tableName"
58
+ throw UtilsDeleteError
59
+ .findReferencesAndUpdate(message: msg)
60
+ }
61
+ if refTable.isEmpty || refTable != tableName {
62
+ continue
63
+ }
64
+ // get the with ref columnName
65
+ guard let withRefsNames = foreignKeyInfo["forKeys"]
66
+ as? [String] else {
67
+
68
+ let msg = "findReferencesAndUpdate: no foreignKeyInfo " +
69
+ "forKeys"
70
+ throw UtilsDeleteError
71
+ .findReferencesAndUpdate(message: msg)
72
+ }
73
+ guard let colNames = foreignKeyInfo["refKeys"]
74
+ as? [String] else {
75
+ let msg = "findReferencesAndUpdate: no foreignKeyInfo " +
76
+ "refKeys"
77
+ throw UtilsDeleteError
78
+ .findReferencesAndUpdate(message: msg)
79
+ }
80
+ if colNames.count != withRefsNames.count {
81
+ let msg = "findReferencesAndUpdate: no foreignKeyInfo " +
82
+ "colNames"
83
+ throw UtilsDeleteError
84
+ .findReferencesAndUpdate(message: msg)
85
+
86
+ }
87
+ guard let action = foreignKeyInfo["action"]
88
+ as? String else {
89
+ let msg = "findReferencesAndUpdate: no action"
90
+ throw UtilsDeleteError
91
+ .findReferencesAndUpdate(message: msg)
92
+ }
93
+ if action == "NO_ACTION" {
94
+ continue
95
+ }
96
+
97
+ var updTableName: String = tableNameWithRefs
98
+ var updColNames: [String] = withRefsNames
99
+ var results: (setStmt: String, uWhereStmt: String)
100
+ results.uWhereStmt = ""
101
+ results.setStmt = ""
102
+ if !checkValuesMatch(withRefsNames,
103
+ against: initColNames) {
104
+ // Search for related items in tableName
105
+ let result: (String,[[String: Any]]) = try UtilsDelete
106
+ .searchForRelatedItems(mDB: mDB,
107
+ updTableName: updTableName,
108
+ tableName: tableName,
109
+ whStmt: whereStmt,
110
+ withRefsNames: withRefsNames,
111
+ colNames: colNames,
112
+ values: values);
113
+ let key: String = result.0
114
+ let relatedItems: [Any] = result.1
115
+ if relatedItems.count == 0 && key.count <= 0 {
116
+ continue
117
+ }
118
+
119
+ // case no match
120
+ if updTableName != tableName {
121
+ switch action {
122
+ case "CASCADE":
123
+ // updTableName
124
+ // update all related element
125
+ // set sql_deleted = 1 and last_modified
126
+ // tableName
127
+ //update all by sending return true
128
+ results = try upDateWhereForCascade(
129
+ results: result)
130
+
131
+ case "RESTRICT":
132
+ // find for elements related in updTableName
133
+ // if some elements
134
+ // send a message
135
+ // do not update tableName
136
+ // return false
137
+ // If no related elements in updTableName
138
+ // return true to update tableName
139
+ results = try upDateWhereForRestrict(
140
+ results: result)
141
+
142
+ default:
143
+ // updTableName
144
+ // update the result_id result_slug to Null
145
+ // update the last_modified
146
+ // keep sql_deleted to 0
147
+ // return true to update tableName
148
+ results = try upDateWhereForDefault(
149
+ withRefsNames: withRefsNames,
150
+ results: result)
151
+ }
152
+ }
153
+
154
+ } else {
155
+ let msg = "Not implemented. Please transfer your " +
156
+ "example to the maintener"
157
+ throw UtilsDeleteError
158
+ .findReferencesAndUpdate(message: msg)
159
+ }
160
+ if results.setStmt.count > 0 &&
161
+ results.uWhereStmt.count > 0 {
162
+
163
+ try executeUpdateForDelete(
164
+ mDB: mDB,
165
+ tableName: updTableName,
166
+ whereStmt: results.uWhereStmt,
167
+ setStmt: results.setStmt,
168
+ colNames: updColNames,
169
+ values: values)
170
+ }
171
+ }
172
+ return retBool
173
+ } catch UtilsDeleteError.upDateWhereForRestrict(let message) {
174
+ throw UtilsDeleteError
175
+ .findReferencesAndUpdate(message: message)
176
+ } catch UtilsSQLStatementError
177
+ .extractForeignKeyInfo(let message) {
178
+ throw UtilsDeleteError
179
+ .findReferencesAndUpdate(message: message)
180
+ } catch UtilsDeleteError.executeUpdateForDelete(let message) {
181
+ throw UtilsDeleteError
182
+ .findReferencesAndUpdate(message: message)
183
+ } catch UtilsSQLCipherError.prepareSQL(let message) {
184
+ throw UtilsDeleteError
185
+ .findReferencesAndUpdate(message: message)
186
+ } catch UtilsSQLCipherError.querySQL(let message) {
187
+ throw UtilsDeleteError
188
+ .findReferencesAndUpdate(message: message)
189
+ }
190
+ }
191
+ // swiftlint:enable cyclomatic_complexity
192
+ // swiftlint:enable function_body_length
193
+
194
+ // MARK: - upDateWhereForDefault
195
+
196
+ class func upDateWhereForDefault(withRefsNames: [String],
197
+ results: (String,[[String:Any]]))
198
+ throws -> ((setStmt: String, uWhereStmt: String)) {
199
+
200
+ var setStmt = ""
201
+ var uWhereStmt = ""
202
+ let key: String = results.0
203
+ let relatedItems = results.1
204
+
205
+ var cols: [Any] = []
206
+ for relItem in relatedItems {
207
+ if let mVal = relItem[key] {
208
+ cols.append(mVal)
209
+ }
210
+ }
211
+ // create the set statement
212
+ for name in withRefsNames {
213
+ setStmt += "\(name) = NULL, "
214
+ }
215
+ setStmt += "sql_deleted = 0"
216
+
217
+ // create the where statement
218
+ uWhereStmt = "WHERE \(key) IN ("
219
+ for col in cols {
220
+ uWhereStmt += "\(col),"
221
+ }
222
+ if uWhereStmt.hasSuffix(",") {
223
+ uWhereStmt = String(uWhereStmt.dropLast())
224
+ }
225
+
226
+ uWhereStmt += ");"
227
+
228
+ return(setStmt, uWhereStmt)
229
+ }
230
+
231
+ // MARK: - upDateWhereForRestrict
232
+
233
+ class func upDateWhereForRestrict(results: (String,[[String: Any]]))
234
+ throws -> ((setStmt: String, uWhereStmt: String)) {
235
+
236
+ // Search for related items in tableName
237
+ let setStmt = ""
238
+ let uWhereStmt = ""
239
+ let relatedItems = results.1
240
+
241
+ if !relatedItems.isEmpty {
242
+ let msg = "Restrict mode related items exist" +
243
+ " please delete them first"
244
+ throw UtilsDeleteError
245
+ .upDateWhereForRestrict(message: msg)
246
+ }
247
+ return(setStmt, uWhereStmt)
248
+ }
249
+
250
+ // MARK: - upDateWhereForCascade
251
+
252
+ class func upDateWhereForCascade(results: (String,[[String:Any]]))
253
+ throws -> ((setStmt: String, uWhereStmt: String)) {
254
+
255
+ // Search for related items in tableName
256
+ var setStmt = ""
257
+ var uWhereStmt = ""
258
+ let key: String = results.0
259
+ let relatedItems = results.1
260
+ var cols: [Any] = []
261
+ for relItem in relatedItems {
262
+ if let mVal = relItem[key] {
263
+ cols.append(mVal)
264
+ }
265
+ }
266
+ setStmt += "sql_deleted = 1"
267
+ // create the where statement
268
+ uWhereStmt = "WHERE \(key) IN ("
269
+ for col in cols {
270
+ uWhereStmt += "\(col),"
271
+ }
272
+ if uWhereStmt.hasSuffix(",") {
273
+ uWhereStmt = String(uWhereStmt.dropLast())
274
+ }
275
+
276
+ uWhereStmt += ");"
277
+ return (setStmt, uWhereStmt)
278
+ }
279
+
280
+ // MARK: - searchForRelatedItems
281
+
282
+ // swiftlint:disable function_parameter_count
283
+ class func searchForRelatedItems(mDB: Database,
284
+ updTableName: String,
285
+ tableName: String, whStmt: String,
286
+ withRefsNames: [String],
287
+ colNames: [String], values: [Any])
288
+ throws -> (String,[[String: Any]]) {
289
+ var relatedItems: [[String: Any]] = []
290
+ var key: String = ""
291
+ let t1Names = withRefsNames.map { "t1.\($0)" }
292
+ let t2Names = colNames.map { "t2.\($0)" }
293
+
294
+ do {
295
+ var whereClause = try UtilsSQLStatement
296
+ .addPrefixToWhereClause(whStmt, from: colNames,
297
+ to: withRefsNames,
298
+ prefix: "t2.")
299
+ if whereClause.hasSuffix(";") {
300
+ whereClause = String(whereClause.dropLast())
301
+ }
302
+ let resultString = zip(t1Names, t2Names)
303
+ .map {"\($0) = \($1)" }
304
+ .joined(separator: " AND ")
305
+
306
+ let sql = "SELECT t1.rowid FROM \(updTableName) t1 " +
307
+ "JOIN \(tableName) t2 ON \(resultString) " +
308
+ "WHERE \(whereClause) AND t1.sql_deleted = 0;"
309
+ var vals = try UtilsSQLCipher.querySQL(mDB: mDB, sql: sql,
310
+ values: values)
311
+ if vals.count > 1 {
312
+ if let mVals = vals[0]["ios_columns"] as? [String] {
313
+ key = mVals[0]
314
+ let keyToRemove = "ios_columns"
315
+ vals.removeAll { dict in
316
+ return dict.keys.contains(keyToRemove)
317
+ }
318
+ for val in vals {
319
+ relatedItems.append(val)
320
+ }
321
+ }
322
+ }
323
+ return (key, relatedItems)
324
+ } catch UtilsSQLStatementError.addPrefixToWhereClause(let message) {
325
+ throw UtilsDeleteError
326
+ .searchForRelatedItems(message: message)
327
+ } catch UtilsSQLCipherError.querySQL(let message) {
328
+ throw UtilsDeleteError
329
+ .searchForRelatedItems(message: message)
330
+ }
331
+ }
332
+ // swiftlint:enable function_parameter_count
333
+
334
+ // MARK: - executeUpdateForDelete
335
+
336
+ // swiftlint:disable function_parameter_count
337
+ class func executeUpdateForDelete(mDB: Database, tableName: String,
338
+ whereStmt: String, setStmt: String,
339
+ colNames: [String], values: [Any])
340
+ throws {
341
+ var lastId: Int64 = -1
342
+ //update sql_deleted for this references
343
+ let stmt = "UPDATE \(tableName) SET \(setStmt) \(whereStmt)"
344
+ var selValues: [Any] = []
345
+ if !values.isEmpty {
346
+ var arrVal: [String] = whereStmt
347
+ .components(separatedBy: "?")
348
+ if arrVal[arrVal.count - 1] == ";" {
349
+ arrVal.removeLast()
350
+ }
351
+ for (jdx, val) in arrVal.enumerated() {
352
+ for updVal in colNames {
353
+ let indices: [Int] = val.indicesOf(string: updVal)
354
+ if indices.count > 0 {
355
+ selValues.append(values[jdx])
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ let resp = try UtilsSQLCipher.prepareSQL(mDB: mDB, sql: stmt,
362
+ values: selValues,
363
+ fromJson: false, returnMode: "no")
364
+ lastId = resp.0
365
+ if lastId == -1 {
366
+ let msg = "UPDATE sql_deleted failed for " +
367
+ "table: \(tableName) "
368
+ throw UtilsDeleteError.executeUpdateForDelete(message: msg)
369
+ }
370
+
371
+ }
372
+
373
+ // MARK: - getCurrentTimeAsInteger
374
+
375
+ class func getCurrentTimeAsInteger() -> Int {
376
+ let currentTime = Date().timeIntervalSince1970
377
+ print(">>>> in getCurrentTimeAsInteger currentTime: \(currentTime)")
378
+ return Int(currentTime)
379
+ }
380
+
381
+ // MARK: - checkValuesMatch
382
+
383
+ class func checkValuesMatch(_ array1: [String],
384
+ against array2: [String]) -> Bool {
385
+ for value in array1 {
386
+ if !array2.contains(value) {
387
+ return false
388
+ }
389
+ }
390
+ return true
391
+ }
392
+
393
+ // MARK: - getReferences
394
+
395
+ class func getReferences(mDB: Database, tableName: String)
396
+ throws -> (tableWithRefs: String, retRefs: [String]) {
397
+ // find the REFERENCES
398
+ var sqlStmt = "SELECT sql FROM sqlite_master "
399
+ sqlStmt += "WHERE sql LIKE('%FOREIGN KEY%') AND "
400
+ sqlStmt += "sql LIKE('%REFERENCES%') AND "
401
+ sqlStmt += "sql LIKE('%\(tableName)%') AND "
402
+ sqlStmt += "sql LIKE('%ON DELETE%');"
403
+ do {
404
+ var references: [[String: Any]] = try UtilsSQLCipher
405
+ .querySQL(mDB: mDB, sql: sqlStmt,values: [])
406
+ var retRefs = [String]()
407
+ var tableWithRefs: String = ""
408
+ if references.count > 1 {
409
+ references.removeFirst()
410
+ if let refValue = references[0]["sql"] as? String {
411
+ let result = try getRefs(sqlStatement: refValue)
412
+ retRefs = result.foreignKeys
413
+ tableWithRefs = result.tableName
414
+ }
415
+ }
416
+ return (tableWithRefs, retRefs)
417
+ } catch UtilsDeleteError.getRefs(let message) {
418
+ throw UtilsDeleteError
419
+ .getReferences(message: message)
420
+ } catch UtilsSQLCipherError.querySQL(let message) {
421
+ throw UtilsDeleteError
422
+ .getReferences(message: message)
423
+ }
424
+ }
425
+
426
+ // MARK: - getRefs
427
+
428
+ class func getRefs(sqlStatement: String)
429
+ throws -> (tableName: String, foreignKeys: [String]) {
430
+ var tableName = ""
431
+ var foreignKeys = [String]()
432
+ let statement = UtilsSQLStatement
433
+ .flattenMultilineString(sqlStatement)
434
+
435
+ do {
436
+ // Regular expression pattern to match the table name
437
+ let tableNamePattern = #"CREATE\s+TABLE\s+(\w+)\s+\("#
438
+ let tableNameRegex = try NSRegularExpression(
439
+ pattern: tableNamePattern, options: [])
440
+ if let tableNameMatch = tableNameRegex
441
+ .firstMatch(in: statement, options: [],
442
+ range: NSRange(location: 0,
443
+ length: statement.utf16.count)) {
444
+ let tableNameRange = Range(tableNameMatch.range(
445
+ at: 1), in: statement)!
446
+ tableName = String(statement[tableNameRange])
447
+ }
448
+
449
+ // Regular expression pattern to match the FOREIGN KEY
450
+ // constraints
451
+ // swiftlint:disable line_length
452
+ let foreignKeyPattern = #"FOREIGN\s+KEY\s+\([^)]+\)\s+REFERENCES\s+(\w+)\s*\([^)]+\)\s+ON\s+DELETE\s+(CASCADE|RESTRICT|SET\s+DEFAULT|SET\s+NULL|NO\s+ACTION)"#
453
+ // swiftlint:enable line_length
454
+
455
+ let foreignKeyRegex = try NSRegularExpression(
456
+ pattern: foreignKeyPattern, options: [])
457
+ let foreignKeyMatches = foreignKeyRegex
458
+ .matches(in: statement, options: [],
459
+ range: NSRange(location: 0,
460
+ length: statement.utf16.count))
461
+ for foreignKeyMatch in foreignKeyMatches {
462
+ let foreignKeyRange = Range(
463
+ foreignKeyMatch.range(at: 0), in: statement)!
464
+ let foreignKey = String(statement[foreignKeyRange])
465
+ foreignKeys.append(foreignKey)
466
+ }
467
+ } catch {
468
+ let msg = "getRefs: Error creating regular expression: " +
469
+ "\(error)"
470
+ throw UtilsDeleteError.getRefs(message: msg)
471
+ }
472
+ return (tableName, foreignKeys)
473
+ }
474
+
475
+ // MARK: - getUpdDelReturnedValues
476
+
477
+ class func getUpdDelReturnedValues(mDB: Database,
478
+ sqlStmt: String,
479
+ names: String )
480
+ throws -> [[String: Any]] {
481
+ var result: [[String: Any]] = []
482
+ let tableName = UtilsSQLStatement
483
+ .extractTableName(from: sqlStmt)
484
+ let whereClause = UtilsSQLStatement
485
+ .extractWhereClause(from: sqlStmt)
486
+ if let tblName = tableName {
487
+ if var wClause = whereClause {
488
+ if wClause.suffix(1) == ";" {
489
+ wClause = String(wClause.dropLast())
490
+ }
491
+ do {
492
+ var query: String = "SELECT \(names) FROM " +
493
+ "\(tblName) WHERE "
494
+ query += "\(wClause);"
495
+ result = try UtilsSQLCipher
496
+ .querySQL(mDB: mDB, sql: query, values: [])
497
+ } catch UtilsSQLCipherError.querySQL(let message) {
498
+ throw UtilsDeleteError
499
+ .getUpdDelReturnedValues(message: message)
500
+ }
501
+ }
502
+ }
503
+ return result
504
+ }
505
+ }
506
+ // swiftlint:enable type_body_length
@@ -6,6 +6,8 @@
6
6
  //
7
7
 
8
8
  import Foundation
9
+ import CryptoKit
10
+ import CommonCrypto
9
11
 
10
12
  enum UtilsJsonError: Error {
11
13
  case tableNotExists(message: String)
@@ -20,7 +22,12 @@ enum UtilsJsonError: Error {
20
22
  case isLastModified(message: String)
21
23
  case isSqlDeleted(message: String)
22
24
  case checkUpdate(message: String)
23
- case checkValues(message: String)}
25
+ case checkValues(message: String)
26
+ case encryptDictionaryToBase64(message: String)
27
+ case decryptBase64ToDictionary(message: String)
28
+ }
29
+
30
+ var mSalt = "jeep_capacitor_sqlite"
24
31
 
25
32
  // swiftlint:disable file_length
26
33
  // swiftlint:disable type_body_length
@@ -466,6 +473,121 @@ class UtilsJson {
466
473
  return isViews
467
474
  }
468
475
 
476
+ // MARK: encryptDictionaryToBase64
477
+
478
+ class func encryptDictionaryToBase64(_ dictionary: [String: Any], forAccount account: String) throws -> String {
479
+ // Encryption using Swift Crypto and Keychain-stored passphrase
480
+
481
+ let jsonData = try JSONSerialization.data(withJSONObject: dictionary)
482
+
483
+ let isPassPhrase = try UtilsSecret.isPassphrase(account: account)
484
+ if !isPassPhrase {
485
+ throw UtilsJsonError.encryptDictionaryToBase64(
486
+ message: "No passphrase stored")
487
+ }
488
+ let passphrase = UtilsSecret.getPassphrase(account: account)
489
+ // Generate a constant salt
490
+ let salt = Data(mSalt.utf8)
491
+
492
+ // Derive the encryption key using the passphrase and salt
493
+ guard let key = deriveSymmetricKeyFromPassphrase(passphrase, salt: salt) else {
494
+ throw UtilsJsonError.encryptDictionaryToBase64(
495
+ message: "No Encryption key returned")
496
+ }
497
+ let symKey = SymmetricKey(data: key)
498
+
499
+ // Use Swift Crypto to perform AES encryption with GCM mode
500
+ let sealedBox = try AES.GCM.seal(jsonData, using: symKey, nonce: AES.GCM.Nonce())
501
+
502
+ if let combined = sealedBox.combined {
503
+ // combined the salt and the encrypted data
504
+ var saltAndEncryptedData = salt
505
+ saltAndEncryptedData.append(combined)
506
+ // Convert to base64 string
507
+
508
+ let base64String = saltAndEncryptedData.base64EncodedString()
509
+ return base64String
510
+ } else {
511
+ throw UtilsJsonError.encryptDictionaryToBase64(
512
+ message: "Conversion to base64String failed")
513
+
514
+ }
515
+
516
+ }
517
+
518
+ // MARK: decrypt DictionaryToBase64
519
+
520
+ class func decryptBase64ToDictionary(_ base64String: String,
521
+ forAccount account: String)
522
+ throws -> [String: Any] {
523
+ guard let data = Data(base64Encoded: base64String) else {
524
+ throw UtilsJsonError.decryptBase64ToDictionary(
525
+ message: "Conversion from Base64 to Dictionary failed")
526
+
527
+ }
528
+ let isPassPhrase = try UtilsSecret.isPassphrase(account: account)
529
+ if !isPassPhrase {
530
+ throw UtilsJsonError.decryptBase64ToDictionary(
531
+ message: "No passphrase stored")
532
+ }
533
+ let passphrase = UtilsSecret.getPassphrase(account: account)
534
+ // Generate a constant salt
535
+ let salt = Data(mSalt.utf8)
536
+
537
+ // Derive the encryption key using the passphrase and salt
538
+ guard let key = deriveSymmetricKeyFromPassphrase(passphrase,
539
+ salt: salt) else {
540
+ throw UtilsJsonError.encryptDictionaryToBase64(
541
+ message: "No Encryption key returned")
542
+ }
543
+ let symKey = SymmetricKey(data: key)
544
+
545
+ // Extract the encrypted data from the remaining bytes
546
+ let encryptedData = data.suffix(from: salt.count)
547
+
548
+ // Convert the base64 string to a sealed box
549
+ let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
550
+
551
+ // Decrypt the ciphertext using AES GCM open
552
+ let decryptedData = try AES.GCM.open(sealedBox, using: symKey)
553
+
554
+ // Convert decrypted data to dictionary
555
+ let dictionary = try JSONSerialization.jsonObject(
556
+ with: decryptedData) as? [String: Any] ?? [:]
557
+
558
+ return dictionary
559
+ }
560
+
561
+ // MARK: deriveSymmetricKeyFromPassphrase
562
+
563
+ class func deriveSymmetricKeyFromPassphrase(_ passphrase: String,
564
+ salt: Data)
565
+ -> Data? {
566
+
567
+ let passphraseData = Data(passphrase.utf8)
568
+ let keyLength = kCCKeySizeAES256
569
+ let iterations: UInt32 = 10000
570
+ var derivedKeyData = Data(count: keyLength)
571
+ let derivedCount = derivedKeyData.count
572
+ let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyUnsafeMutableRawBufferPointer in
573
+ passphraseData.withUnsafeBytes { passphraseUnsafeRawBufferPointer in
574
+ salt.withUnsafeBytes { saltUnsafeRawBufferPointer in
575
+ CCKeyDerivationPBKDF(
576
+ CCPBKDFAlgorithm(kCCPBKDF2),
577
+ passphraseUnsafeRawBufferPointer.baseAddress,
578
+ passphraseData.count,
579
+ saltUnsafeRawBufferPointer.baseAddress,
580
+ salt.count,
581
+ CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
582
+ iterations,
583
+ derivedKeyUnsafeMutableRawBufferPointer.baseAddress,
584
+ derivedCount
585
+ )
586
+ }
587
+ }
588
+ }
589
+ return derivationStatus == kCCSuccess ? derivedKeyData : nil
590
+ }
469
591
  }
470
592
  // swiftlint:enable type_body_length
471
593
  // swiftlint:enable file_length