Created
June 12, 2024 06:25
-
-
Save vikaskore/eea482b90b0f15afc6a77264eb82a916 to your computer and use it in GitHub Desktop.
This helps developers to save token in keychain in encrypted format and decrypt later to use, AES256 encryption is used in this content
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SecureToken.swift | |
// Samples | |
// | |
// Created by VikasK on 12/06/24. | |
// Copyright © 2024 Vikaskore Software. All rights reserved. | |
// | |
import CommonCrypto | |
import Foundation | |
// To store encyptionKey and IV | |
fileprivate class KeyManager { | |
// Shared object to access properties and functions of this class | |
internal static let shared = KeyManager() | |
// Required: Minimum 32 character long key string | |
let encryptionKey = "thi_sisenc_ry_ptionk_eyusedh_ere" | |
// Required: 16 bytes for AES128 | |
let iVector = "jun_12_security_" | |
init() { } | |
} | |
class Encrypter { | |
// Encrypt Item | |
func encrypt(string: String) { | |
KeychainWrapper().removeItemFromKeychain() | |
if let encryptedData = crypt(data: string.data(using: .utf8), option: CCOperation(kCCEncrypt)) { | |
// Store the encrypted data in Keychain | |
KeychainWrapper().saveJWTToKeychain(tokenData: encryptedData) | |
} | |
} | |
// decrypt item | |
func decrypt() -> String? { | |
guard let encryptedData = KeychainWrapper().retrieveJWTFromKeychain() else { | |
return nil | |
} | |
guard let decryptedData = crypt(data: encryptedData, option: CCOperation(kCCDecrypt)) else { return nil } | |
return String(bytes: decryptedData, encoding: .utf8) | |
} | |
private func crypt(data: Data?, option: CCOperation) -> Data? { | |
let key = KeyManager.shared.encryptionKey | |
let iVector = KeyManager.shared.iVector | |
// Convert the encryption key string to data | |
guard key.count == kCCKeySizeAES256, let keyData = key.data(using: .utf8) else { | |
debugPrint("Error: Failed to set a key.") | |
return nil | |
} | |
guard iVector.count == kCCBlockSizeAES128, let ivData = iVector.data(using: .utf8) else { | |
debugPrint("Error: Failed to set an initial vector.") | |
return nil | |
} | |
guard let data = data else { return nil } | |
let cryptLength = data.count + key.count | |
var cryptData = Data(count: cryptLength) | |
var bytesLength = Int(0) | |
let status = cryptData.withUnsafeMutableBytes { cryptBytes in | |
data.withUnsafeBytes { dataBytes in | |
ivData.withUnsafeBytes { ivBytes in | |
keyData.withUnsafeBytes { keyBytes in | |
CCCrypt(option, CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), keyBytes.baseAddress, keyData.count, ivBytes.baseAddress, dataBytes.baseAddress, data.count, cryptBytes.baseAddress, cryptLength, &bytesLength) | |
} | |
} | |
} | |
} | |
guard Int32(status) == Int32(kCCSuccess) else { | |
debugPrint("Error: Failed to crypt data. Status \(status)") | |
return nil | |
} | |
cryptData.removeSubrange(bytesLength..<cryptData.count) | |
return cryptData | |
} | |
// To remove saved token from keychain | |
func removeTokenFromKeychain() { | |
KeychainWrapper().removeItemFromKeychain() | |
} | |
// To check validity of JWT token | |
func checkValidityAndReturnJWTToken() -> String { | |
guard let jwtToken = Encrypter().decrypt() else { | |
debugPrint("Cant retrive JWTToken") | |
return "" | |
} | |
var payload64 = jwtToken.components(separatedBy: ".")[1] | |
// need to pad the string with = to make it divisible by 4, | |
// otherwise Data won't be able to decode it | |
while payload64.count % 4 != 0 { | |
payload64 += "=" | |
} | |
debugPrint("base64 encoded payload: \(payload64)") | |
let payloadData = Data(base64Encoded: payload64, | |
options: .ignoreUnknownCharacters)! | |
let json = try? JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any] | |
let exp = json?["exp"] as? Int | |
let expDate = Date(timeIntervalSince1970: TimeInterval(exp ?? 0)) | |
// Get the current date and time | |
let currentDate = Date() | |
// Subtract 5 minutes (300 seconds) from the current date(optional) | |
let newDate = currentDate.addingTimeInterval(-300) | |
let isValid = expDate.compare(newDate) == ComparisonResult.orderedDescending | |
debugPrint(isValid) | |
if isValid { | |
return jwtToken | |
} else { | |
return "" | |
} | |
} | |
} | |
private class KeychainWrapper { | |
fileprivate func saveJWTToKeychain(tokenData: Data) { | |
let query: [String: Any] = [ | |
kSecClass as String: kSecClassGenericPassword, | |
kSecAttrService as String: "SecureJWTToken", | |
kSecValueData as String: tokenData, | |
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly | |
] | |
let status = SecItemAdd(query as CFDictionary, nil) | |
if status == errSecSuccess { | |
debugPrint("JWT token saved to Keychain.") | |
} else { | |
debugPrint("Error saving JWT token to Keychain.") | |
} | |
} | |
fileprivate func retrieveJWTFromKeychain() -> Data? { | |
let query: [String: Any] = [ | |
kSecClass as String: kSecClassGenericPassword, | |
kSecAttrService as String: "SecureJWTToken", | |
kSecMatchLimit as String: kSecMatchLimitOne, | |
kSecReturnData as String: kCFBooleanTrue ?? true | |
] | |
var result: AnyObject? | |
let status = SecItemCopyMatching(query as CFDictionary, &result) | |
if status == errSecSuccess, let data = result as? Data { | |
return data | |
} else { | |
debugPrint("Error retrieving JWT token from Keychain: \(status)") | |
return nil | |
} | |
} | |
fileprivate func removeItemFromKeychain() { | |
let query: [String: Any] = [ | |
kSecClass as String: kSecClassGenericPassword, | |
kSecAttrService as String: "SecureJWTToken" | |
] | |
_ = SecItemDelete(query as CFDictionary) | |
} | |
} | |
/* | |
Usage: | |
/// Encrypt Token using encryption key | |
Encrypter().encrypt(string: "Your_JWT_Token") | |
/// Decrypt and return token | |
let value = Encrypter().decrypt() | |
/// Check Validity of token, if valid return token else empty string | |
let jwtToken = Encrypter().checkValidityAndReturnJWTToken() | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment