Skip to content

Instantly share code, notes, and snippets.

@vikaskore
Created June 12, 2024 06:25
Show Gist options
  • Save vikaskore/eea482b90b0f15afc6a77264eb82a916 to your computer and use it in GitHub Desktop.
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
//
// 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