Skip to content

Instantly share code, notes, and snippets.

@luannguyenkhoa
Last active November 1, 2022 01:56
Show Gist options
  • Save luannguyenkhoa/d0620df0de7296ce4e8e6c3d0a187fc5 to your computer and use it in GitHub Desktop.
Save luannguyenkhoa/d0620df0de7296ce4e8e6c3d0a187fc5 to your computer and use it in GitHub Desktop.
A way to manage UserDefault in Swift by utilizing the combination of Enum and Protocol, combined with CryptoSwift to encrypt the sensitive info before storing.
import CryptoSwift
/*
Where controls all actions related to save and retrieve value from User Default
All strings value will be encrypted/decrypted before saving/retrieving from UD
Other types will be save/retrieve directly
*/
public protocol UD {
associatedtype T
var rawValue: String {get}
}
public extension UD {
// MARK: - Getter and Setter
var value: T? {
let val = UserDefaults.standard.value(forKey: key) as? T
return decrypt(val: val)
}
func set(ud: UserDefaults? = UserDefaults.standard, _ val: T?) {
let value = encrypt(val)
ud?.set(value, forKey: key)
}
func instance(_ name: String) -> UserDefaults? {
return UserDefaults(suiteName: name)
}
func archive<E: Codable>(val: E) {
let archiver = NSKeyedArchiver()
do {
try archiver.encodeEncodable(val, forKey: NSKeyedArchiveRootObjectKey)
UserDefaults.standard.set(archiver.encodedData, forKey: key)
} catch let err {
print("Archive: \(err)")
}
}
func unarchive<E: Codable>() -> E? {
if let data = UserDefaults.standard.value(forKey: key) as? Data {
do {
let val = try NSKeyedUnarchiver(forReadingWith: data).decodeTopLevelDecodable(E.self, forKey: NSKeyedArchiveRootObjectKey)
return val.map({ $0 })
} catch let err {
print("Unarchive: \(err)")
}
}
return nil
}
var bool: Bool {
return UserDefaults.standard.bool(forKey: key)
}
// MARK: - Private handlers
private var key: String {
return rawValue
}
private var secureKey: String {
return key.count > 16 ? String(key.prefix(16)) : key + String(repeating: "k", count: 16 - key.count)
}
// MARK: - Encrypt and Decrypt String using AES Method
private func encrypt(_ val: T?) -> T? {
/// Only encrypt the param that is in String type
guard let value = val as? String else { return val }
do {
/// Initialize AES with iv and securekey in 16 bytes
let iv = AES.randomIV(AES.blockSize)
let aes = try AES(key: secureKey.md5().bytes, blockMode: CBC(iv: iv))
/// Start encrypting the inputting value
let encrypted = try aes.encrypt(value.bytes)
/// Shift iv key to the result to use for decrypt later
let encryptedK = Data(bytes: encrypted)
var encryptedKIV = Data(bytes: iv)
encryptedKIV.append(encryptedK)
/// Encode based64 data to end string
return encryptedKIV.base64EncodedString() as? T
} catch let err {
print(err)
return val
}
}
private func decrypt(val: T?) -> T? {
/// Ignore if the param is not a string
guard let value = val as? String else { return val }
do {
/// Ignore if its not a base64Encoded
guard let data = Data(base64Encoded: value) else { return val }
let count = [UInt8](data).count
/// Assumption based on encrypt method, the data size must be always greater than iv key size
guard count > AES.blockSize else { return val }
/// Get iv key part from the decoded data
let iv = Array([UInt8](data)[0 ..< AES.blockSize])
/// Get target data
let bytes = Array([UInt8](data)[AES.blockSize ..< count])
/// Decrypt data by AES method
let aes = try AES(key: secureKey.md5().bytes, blockMode: CBC(iv: iv))
let decrypted = try aes.decrypt(bytes)
return String(bytes: decrypted, encoding: .utf8) as? T
} catch let err {
print(err)
return val
}
}
}
/// for dealling with UserDefault
public enum UDKey<T>: String, UD {
// MARK: Usecases
case example = "EXAMPLE"
/// Group cases into a nested Enum based on their sense
enum User: String, UD, CaseInterable {
case email = "USER_EMAIL"
case userId = "USER_ID"
case userName = "USER_NAME"
static func clean() {
User.allCases.forEach({ $0.set(nil) })
}
}
enum Item: String, UD {
case state = "ITEM_STATE"
case read = "ITEM_READ"
}
}
@luannguyenkhoa
Copy link
Author

luannguyenkhoa commented Sep 20, 2018

Attempts:
Retrieve value:

let val1 = UDKey.example.value ?? ""
let val2: String? = UDKey.example.value
let val3 = UDKey<String>.example.value

For first level Enum:

UDKey.example.set("Example")
let example = UDKey.example.value ?? ""

For nested Enum:

UDKey.User.email.set("nkluan91@gmail.com")
let email = UDKey.User.email.value ?? ""

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment