-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathEncryptionCryptokitIml.swift
More file actions
849 lines (733 loc) · 32.6 KB
/
EncryptionCryptokitIml.swift
File metadata and controls
849 lines (733 loc) · 32.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
//
// EncryptionCryptokitIml.swift
// react-native-encryption
//
// Created by Rishabh on 29.12.24.
//
import Foundation
import CryptoKit
import Security
// MARK: - EncryptionError
enum EncryptionError: LocalizedError {
case invalidData
case invalidKey
case invalidKeyLength
case encryptionFailed
case decryptionFailed
case invalidBase64
case keyGenerationFailed
case publicKeyExportFailed
case privateKeyExportFailed
case keyCreationFailed
var errorDescription: String? {
switch self {
case .invalidData:
return "The provided data is invalid or cannot be processed."
case .invalidKey:
return "The encryption key is invalid or cannot be converted."
case .invalidKeyLength:
return "The encryption key must be 16, 24, or 32 bytes long."
case .encryptionFailed:
return "AES encryption failed due to an internal error."
case .decryptionFailed:
return "AES decryption failed due to an internal error."
case .invalidBase64:
return "The provided Base64 string is invalid or cannot be decoded."
case .keyGenerationFailed:
return "Failed to generate a secure encryption key."
case .publicKeyExportFailed:
return "Failed to export the public key."
case .privateKeyExportFailed:
return "Failed to export the private key."
case .keyCreationFailed:
return "Failed to create an encryption key from the given data."
}
}
}
// MARK: - CryptoUtility
@objcMembers
public class CryptoUtility: NSObject {
/// Generate a AES base64 key as string
/// - Parameters:
/// - keySize: Size of the key i.e 128,192, or 256.
/// - Returns: Base64-encoded encrypted string or nil on failure.
@objc public func generateAESKey(_ keySize: Int,errorObj: NSErrorPointer) -> String? {
let key: SymmetricKey
do {
switch keySize {
case 128:
key = SymmetricKey(size: .bits128)
case 192:
key = SymmetricKey(size: .bits192)
case 256:
key = SymmetricKey(size: .bits256)
default:
throw EncryptionError.invalidKeyLength
}
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "encryptionError.localizedDescription"
]
)
return nil
}
let keyData = key.withUnsafeBytes { Data(Array($0)) }
return keyData.base64EncodedString()
}
// MARK: - AES Encryption Helper
/// Shared method for AES encryption logic.
/// - Parameters:
/// - data: Data to be encrypted.
/// - key: Base64-encoded AES key.
/// - Throws: EncryptionError
/// - Returns: Encrypted data as `Data`
private func performAESEncryption(data: Data, key: String) throws -> Data {
// Validate Key
guard let keyData = Data(base64Encoded: key) else {
throw EncryptionError.invalidKey
}
// Initialize Symmetric Key
let symmetricKey = SymmetricKey(data: keyData)
// Encrypt Data
let sealedBox = try AES.GCM.seal(data, using: symmetricKey)
guard let combinedData = sealedBox.combined else {
throw EncryptionError.encryptionFailed
}
return combinedData
}
// MARK: - AES Encryption for String
/// Encrypts a string using AES with a Base64 public key.
/// - Parameters:
/// - data: Plain text string to be encrypted.
/// - key: Base64-encoded AES key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Base64-encoded encrypted string or nil on failure.
@objc public func encryptAES(_ data: String, key: String, errorObj: NSErrorPointer) -> String? {
do {
// Convert string to data
guard let dataToEncrypt = data.data(using: .utf8) else {
throw EncryptionError.invalidData
}
// Perform encryption
let encryptedData = try performAESEncryption(data: dataToEncrypt, key: key)
return encryptedData.base64EncodedString()
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: encryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown encryption error occurred."]
)
return nil
}
}
// MARK: - AES Encryption for File
/// Encrypts a file using AES with a Base64 public key.
/// - Parameters:
/// - inputPath: Path to the input file.
/// - outputPath: Path to save the encrypted file.
/// - key: Base64-encoded AES key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Path to the encrypted file or nil on failure.
@objc public func encryptFile(_ inputPath: String, outputPath: String, key: String, errorObj: NSErrorPointer) -> String? {
do {
// Validate file paths
let inputURL = URL(fileURLWithPath: inputPath)
let outputURL = URL(fileURLWithPath: outputPath)
// Read file data
let fileData = try Data(contentsOf: inputURL)
// Perform encryption
let encryptedData = try performAESEncryption(data: fileData, key: key)
// Write encrypted data to output file
try encryptedData.write(to: outputURL)
return outputURL.path
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: encryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown encryption error occurred."]
)
return nil
}
}
private func performAESDecryption(data: Data, key: String) throws -> Data {
// Validate Key
guard let keyData = Data(base64Encoded: key) else {
throw EncryptionError.invalidKey
}
// Initialize Symmetric Key
let symmetricKey = SymmetricKey(data: keyData)
// Decrypt Data
let sealedBox = try AES.GCM.SealedBox(combined: data)
let decryptedData = try AES.GCM.open(sealedBox, using: symmetricKey)
return decryptedData
}
// MARK: - AES Decryption for String
/// Decrypts a Base64-encoded encrypted string using AES.
/// - Parameters:
/// - data: Base64-encoded encrypted string.
/// - key: Base64-encoded AES key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Decrypted plain text string or nil on failure.
@objc public func decryptAES(_ data: String, key: String, errorObj: NSErrorPointer) -> String? {
do {
// Decode Base64-encoded data
guard let encryptedData = Data(base64Encoded: data) else {
throw EncryptionError.invalidData
}
// Perform decryption
let decryptedData = try performAESDecryption(data: encryptedData, key: key)
// Convert to String
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw EncryptionError.decryptionFailed
}
return decryptedString
} catch let decryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: decryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown decryption error occurred."]
)
return nil
}
}
// MARK: - AES Decryption for File
/// Decrypts an AES-encrypted file using a Base64-encoded AES key.
/// - Parameters:
/// - inputPath: Path to the encrypted input file.
/// - key: Base64-encoded AES key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Decrypted data as a string or nil on failure.
@objc public func decryptFile(_ inputPath: String, key: String, errorObj: NSErrorPointer) -> String? {
do {
// Validate File Path
let inputURL = URL(fileURLWithPath: inputPath)
// Read Encrypted File Data
let encryptedData = try Data(contentsOf: inputURL)
// Perform decryption
let decryptedData = try performAESDecryption(data: encryptedData, key: key)
// Convert to String
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw EncryptionError.decryptionFailed
}
return decryptedString
} catch let decryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: decryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown decryption error occurred."]
)
return nil
}
}
private func constructSecKey(from keyBase64: String, isPublicKey: Bool) throws -> SecKey {
guard let keyData = Data(base64Encoded: keyBase64) else {
throw EncryptionError.invalidKey
}
let keyClass = isPublicKey ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate
let keyAttributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: keyClass,
kSecAttrKeySizeInBits as String: 2048
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(keyData as CFData,
keyAttributes as CFDictionary,
&error) else {
throw error?.takeRetainedValue() ?? EncryptionError.keyCreationFailed
}
return secKey
}
// MARK: - RSA Encryption
/// Encrypts a string using RSA with a Base64 public key.
/// - Parameters:
/// - data: Plain text string to be encrypted.
/// - publicKeyBase64: Base64-encoded RSA public key.
/// - padding: The padding scheme to use: "PKCS1" or "OAEP".
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Base64-encoded encrypted string or nil on failure.
@objc public func encryptRSA(_ data: String, publicKeyBase64: String, padding: String, errorObj: NSErrorPointer) -> String? {
do {
// Create Public Key from Base64 String
let publicKey = try constructSecKey(from: publicKeyBase64, isPublicKey: true)
// Validate Data
guard let dataToEncrypt = data.data(using: .utf8) else {
throw EncryptionError.invalidData
}
// Select algorithm based on padding
let algorithm: SecKeyAlgorithm = padding == "OAEP" ? .rsaEncryptionOAEPSHA256 : .rsaEncryptionPKCS1
// Encrypt Data using RSA
var error: Unmanaged<CFError>?
guard let encryptedData = SecKeyCreateEncryptedData(
publicKey,
algorithm,
dataToEncrypt as CFData,
&error
) as Data? else {
throw error?.takeRetainedValue() ?? EncryptionError.encryptionFailed
}
return encryptedData.base64EncodedString()
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: encryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown RSA encryption error occurred."]
)
return nil
}
}
/// Retrieve the Public Key from a given Private Key (Base64 Encoded)
/// - Parameters:
/// - privateKeyBase64: Base64-encoded RSA private key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Base64-encoded RSA public key or nil on failure.
@objc public func getPublicRSAkey(_ privateKeyBase64: String, errorObj: NSErrorPointer) -> String? {
do {
// Decode the Base64-encoded Private Key
guard let privateKeyData = Data(base64Encoded: privateKeyBase64) else {
throw EncryptionError.invalidKey
}
// Create SecKey from Private Key Data
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 2048
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateWithData(privateKeyData as CFData,
attributes as CFDictionary,
&error) else {
throw error?.takeRetainedValue() ?? EncryptionError.keyCreationFailed
}
// Extract Public Key from Private Key
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw EncryptionError.publicKeyExportFailed
}
// Export Public Key as Data
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
throw error?.takeRetainedValue() ?? EncryptionError.publicKeyExportFailed
}
// Return Base64-encoded Public Key
return publicKeyData.base64EncodedString()
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: encryptionError.localizedDescription]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown error occurred while extracting the public key."]
)
return nil
}
}
@objc public func generateRSAKeyPair(_ errorObj: NSErrorPointer) -> NSDictionary? {
do {
let keySize = 2048
// Define Key Attributes
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: keySize,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: false
],
kSecPublicKeyAttrs as String: [
kSecAttrIsPermanent as String: false
]
]
// Generate Private Key
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error?.takeRetainedValue() ?? EncryptionError.keyGenerationFailed
}
// Extract Public Key
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw EncryptionError.publicKeyExportFailed
}
// Export Public Key
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
throw error?.takeRetainedValue() ?? EncryptionError.publicKeyExportFailed
}
// Export Private Key
guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else {
throw error?.takeRetainedValue() ?? EncryptionError.privateKeyExportFailed
}
// Encode Keys as Base64
let publicKeyBase64 = publicKeyData.base64EncodedString()
let privateKeyBase64 = privateKeyData.base64EncodedString()
return [
"publicKey": publicKeyBase64,
"privateKey": privateKeyBase64
]
} catch let err as NSError {
if let errorPointer = errorObj {
errorPointer.pointee = err
}
return nil
}
catch {
if let errorPointer = errorObj {
errorPointer.pointee = NSError(domain: "com.example.encryption",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown error occurred: \(error.localizedDescription)"])
}
return nil
}
}
// MARK: - RSA Decryption
/// Decrypts a Base64-encoded string using RSA with a Base64 private key.
/// - Parameters:
/// - data: Base64-encoded encrypted string.
/// - privateKeyBase64: Base64-encoded RSA private key.
/// - padding: The padding scheme to use: "PKCS1" or "OAEP".
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Decrypted plain text string or nil on failure.
@objc public func decryptRSA(_ data: String, privateKeyBase64: String, padding: String, errorObj: NSErrorPointer) -> String? {
do {
// Create Private Key from Base64 String
let privateKey = try constructSecKey(from: privateKeyBase64, isPublicKey: false)
// Validate Base64 Data
guard let encryptedData = Data(base64Encoded: data) else {
throw EncryptionError.invalidBase64
}
// Select algorithm based on padding
let algorithm: SecKeyAlgorithm = padding == "OAEP" ? .rsaEncryptionOAEPSHA256 : .rsaEncryptionPKCS1
// Decrypt Data using RSA
var error: Unmanaged<CFError>?
guard let decryptedData = SecKeyCreateDecryptedData(
privateKey,
algorithm,
encryptedData as CFData,
&error
) as Data? else {
throw error?.takeRetainedValue() ?? EncryptionError.decryptionFailed
}
// Convert Decrypted Data to String
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw EncryptionError.decryptionFailed
}
return decryptedString
} catch let decryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: decryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown RSA decryption error occurred."]
)
return nil
}
}
// MARK: - Hashing (SHA256 & SHA512)
/// Generate a hashSHA256 base64 key as string
/// - Parameters:
/// - input: string value.
/// - Returns: Hashed value as string.
@objc public func hashSHA256(_ input: String) -> String {
let data = Data(input.utf8)
let digest = SHA256.hash(data: data)
return digest.map { String(format: "%02x", $0) }.joined()
}
/// Generate a hashSHA512 base64 key as string
/// - Parameters:
/// - input: string value.
/// - Returns: Hashed value as string.
@objc public func hashSHA512(_ input: String) -> String {
let data = Data(input.utf8)
let digest = SHA512.hash(data: data)
return digest.map { String(format: "%02x", $0) }.joined()
}
/// Generate an HMAC key for SHA-256 or SHA-512.
/// - Parameters:
/// - keySize: Desired key size in bits (256 or 512).
/// - Returns: Base64-encoded key string.
/// - Throws: EncryptionError if key size is invalid.
@objc public func generateHMACKey(_ keySize: Int) -> String {
let key = SymmetricKey(size: .bits256) // Default 256 bits
let keyData = key.withUnsafeBytes { Data(Array($0)) }
return keyData.base64EncodedString()
}
// MARK: - HMAC (SHA256/SHA512)
/// Generate HMAC hash using specified hash algorithm.
/// - Parameters:
/// - data: Plain text string to be hashed.
/// - key: Base64-encoded key.
/// - algorithm: Specify either "SHA256" or "SHA512".
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: HMAC hash string or nil on failure.
@objc public func hmac(_ data: String, key: String, algorithm: String, errorObj: NSErrorPointer) -> String? {
do {
// Decode the Base64 key
guard let keyData = key.data(using: .utf8) else {
throw EncryptionError.invalidKey
}
// Create SymmetricKey
let symmetricKey = SymmetricKey(data: keyData)
// Convert the input data to bytes
let dataBytes = Data(data.utf8)
let hmacString: String
switch algorithm.uppercased() {
case "SHA256":
let authenticationCode = HMAC<SHA256>.authenticationCode(for: dataBytes, using: symmetricKey)
hmacString = authenticationCode.map { String(format: "%02x", $0) }.joined()
case "SHA512":
let authenticationCode = HMAC<SHA512>.authenticationCode(for: dataBytes, using: symmetricKey)
hmacString = authenticationCode.map { String(format: "%02x", $0) }.joined()
default:
throw EncryptionError.invalidKey
}
return hmacString
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: encryptionError.localizedDescription
]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "An unknown error occurred."
]
)
return nil
}
}
// MARK: - Random String Generation
/// Generate random string .
/// - Parameters:
/// - length: Plain text string to be encrypted.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: random string or nil on failure.
@objc public func generateRandomString(_ length: Int, errorObj: NSErrorPointer) -> String? {
// Validate Input
guard length > 0 else {
errorObj?.pointee = NSError(
domain: "RandomStringError",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Length must be greater than zero"]
)
return nil
}
let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let charsetArray = Array(charset)
let charsetCount = charsetArray.count
var randomString = ""
var buffer = [UInt8](repeating: 0, count: length)
let result = SecRandomCopyBytes(kSecRandomDefault, length, &buffer)
if result != errSecSuccess {
errorObj?.pointee = NSError(
domain: "RandomStringError",
code: -2,
userInfo: [NSLocalizedDescriptionKey: "Failed to generate secure random bytes"]
)
return nil
}
for byte in buffer {
randomString.append(charsetArray[Int(byte) % charsetCount])
}
return randomString
}
// MARK: - Base64 Encoding & Decoding
/// Encode a plain text to base64
/// - Parameters:
/// - input: string value.
/// - Returns: Encoded value as string.
@objc public func base64Encode(_ input: String) -> String {
return Data(input.utf8).base64EncodedString()
}
/// Decode a plain text to base64
/// - Parameters:
/// - input: string value.
/// - Returns: decoded value as plain text string.
@objc public func base64Decode(_ input: String,errorObj:NSErrorPointer) -> String? {
do {
guard let data = Data(base64Encoded: input),
let decodedString = String(data: data, encoding: .utf8) else {
throw EncryptionError.invalidBase64
}
return decodedString
} catch let err as NSError {
if let errorPointer = errorObj {
errorPointer.pointee = err
}
return nil
}
catch {
if let errorPointer = errorObj {
errorPointer.pointee = NSError(domain: "com.example.encryption",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown error occurred: \(error.localizedDescription)"])
}
return nil
}
}
/// Generate a public/private key pair for ECDSA
/// - Returns: public/private key pair for ECDSA as dictionary
@objc public func generateECDSAKeyPair() -> NSDictionary {
let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey
let privateKeyData = privateKey.rawRepresentation.base64EncodedString()
let publicKeyData = publicKey.rawRepresentation.base64EncodedString()
return [
"publicKey": publicKeyData,
"privateKey": privateKeyData
]
}
@objc public func getPublicECDSAKey(_ privateKeyBase64: String, errorObj: NSErrorPointer) -> String? {
do {
// Decode the Base64-encoded private key
guard let privateKeyData = Data(base64Encoded: privateKeyBase64) else {
throw EncryptionError.invalidKey
}
// Reconstruct the private key
let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData)
// Extract the public key
let publicKey = privateKey.publicKey
// Return the Base64-encoded public key
return publicKey.rawRepresentation.base64EncodedString()
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: encryptionError.localizedDescription]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Failed to extract public key from private key."]
)
return nil
}
}
// MARK: - Sign Data using ECDSA
/// Sign data using ECDSA private key
/// - Parameters:
/// - data: The plain text data to sign.
/// - privateKeyBase64: Base64-encoded private key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Base64-encoded signature string.
@objc public func signDataECDSA(_ data: String, privateKeyBase64: String, errorObj: NSErrorPointer) -> String? {
do {
guard let privateKeyData = Data(base64Encoded: privateKeyBase64) else {
throw EncryptionError.invalidKey
}
let privateKey = try P256.Signing.PrivateKey(rawRepresentation: privateKeyData)
let dataToSign = Data(data.utf8)
let signature = try privateKey.signature(for: dataToSign)
return signature.derRepresentation.base64EncodedString()
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: encryptionError.localizedDescription]
)
return nil
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown signing error occurred."]
)
return nil
}
}
// MARK: - Verify Signature using ECDSA
/// Verify ECDSA signature
/// - Parameters:
/// - data: The plain text data.
/// - signatureBase64: Base64-encoded signature.
/// - publicKeyBase64: Base64-encoded public key.
/// - errorObj: NSErrorPointer for capturing errors.
/// - Returns: Boolean indicating signature validity.
@objc public func verifySignatureECDSA(_ data: String, signatureBase64: String, publicKeyBase64: String, errorObj: NSErrorPointer) -> Bool {
do {
guard let publicKeyData = Data(base64Encoded: publicKeyBase64),
let signatureData = Data(base64Encoded: signatureBase64) else {
throw EncryptionError.invalidKey
}
let publicKey = try P256.Signing.PublicKey(rawRepresentation: publicKeyData)
let signature = try P256.Signing.ECDSASignature(derRepresentation: signatureData)
let dataToVerify = Data(data.utf8)
return publicKey.isValidSignature(signature, for: dataToVerify)
} catch let encryptionError as EncryptionError {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: encryptionError.localizedDescription]
)
return false
} catch {
errorObj?.pointee = NSError(
domain: "CryptoUtility",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "An unknown verification error occurred."]
)
return false
}
}
}