header-img
Info :

String νƒ€μž…μ˜ κ°’ 킀체인에 μ €μž₯ν•  λ•Œ μ‚¬μš©ν•˜λ €κ³  κ΅¬ν˜„ν•΄λ†“μ€ 킀체인 μœ ν‹Έ ν΄λž˜μŠ€μž…λ‹ˆλ‹€. μ‹±κΈ€ν†€μœΌλ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. store μ‹œ 이미 μ‘΄μž¬ν•˜λ˜ 값이면 μžλ™μœΌλ‘œ μ—…λ°μ΄νŠΈκ°€ λ˜λ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. λ°œμƒν•˜λŠ” 각쒅 였λ₯˜μ— λŒ€ν•œ μ„€λͺ…은 μ£Όμ„μœΌλ‘œ λ‹¬μ•˜μŠ΅λ‹ˆλ‹€. 

import Foundation
import Security

enum KeyChainType: String {
    case tokenKey = "Token_Key"
    // ...
}

enum KeyChainError: Error {
    case invalidBundleID
    case invalidData
    case recognizedKeyChainError(OSStatus, CFString?)
    case unrecognizedKeyChainError
    case dataCannotConvertString
    case dataNotFound
}

class KeyChainUtil {
    private let bundleID = Bundle.main.bundleIdentifier

    // singleton
    static let shared = KeyChainUtil()

    private init() { }

    // MARK: Create = Store
    /**
     킀체인에 데이터 μ €μž₯ν•˜λŠ” ν•¨μˆ˜. 기쑴에 μ‘΄μž¬ν•˜λ˜ 데이터여도 이 ν•¨μˆ˜λ‘œ μ €μž₯ν•΄μ£Όλ©΄ 됨.
     
     λ°œμƒν•  수 μžˆλŠ” μ—λŸ¬
     1. invalidBundleID -> μœ νš¨ν•˜μ§€ μ•Šμ€ bundle id의 경우
     2. invalidData -> μ €μž₯ λ°μ΄ν„°μ˜ μ €μž₯ ν˜•μ‹ λ³€ν™˜μ΄ λΆˆκ°€λŠ₯ν•œ 경우
     3. recognizedKeyChainError -> λ°œμƒ κ°€λŠ₯
     
     - Parameters:
     - key: enum KeyChainType에 μ‘΄μž¬ν•˜λŠ” μ €μž₯ν•  κ°’μ˜ νƒ€μž…
     - data: μ €μž₯ν•  κ°’
     */
    func storeStringTypeInKeyChain(
        type key: KeyChainType,
        data: String
    ) throws {

        guard let service = self.bundleID else {
            throw KeyChainError.invalidBundleID
        }

        guard let validData = data.data(using: .utf8, allowLossyConversion: false) else {
            throw KeyChainError.invalidData
        }

        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: key.rawValue,
            kSecValueData: validData
        ]

        let status: OSStatus = SecItemAdd(query as CFDictionary, nil)

        if status == errSecSuccess {
            print("key chain stored well")
        } else {
            if status == errSecDuplicateItem {
                do {
                    try updateStringTypeInKeyChain(type: key, data: data)
                } catch KeyChainError.recognizedKeyChainError(let status, let err) {
                    throw KeyChainError.recognizedKeyChainError(status, err)
                }
            }
            let err = SecCopyErrorMessageString(status, nil)
            throw KeyChainError.recognizedKeyChainError(status, err)
        }

    }

    // MARK: Update
    /**
     킀체인에 데이터 μ—…λ°μ΄νŠΈν•˜λŠ” ν•¨μˆ˜. μ™ΈλΆ€μ—μ„œ μ ‘κ·Ό λΆˆκ°€λŠ₯. Store μ‹œ μžλ™μœΌλ‘œ 뢈림
     
     λ°œμƒν•  수 μžˆλŠ” μ—λŸ¬
     1. invalidBundleID -> λ°œμƒν•˜μ§€ μ•ŠμŒ (storeμ—μ„œ ν•œ 번 검증)
     2. invalidData -> λ°œμƒν•˜μ§€ μ•ŠμŒ (storeμ—μ„œ ν•œ 번 검증)
     3. recognizedKeyChainError -> λ°œμƒ κ°€λŠ₯
     */
    fileprivate func updateStringTypeInKeyChain(
        type key: KeyChainType,
        data: String
    ) throws {

        guard let service = self.bundleID else {
            throw KeyChainError.invalidBundleID
        }

        guard let validData = data.data(using: .utf8, allowLossyConversion: false) else {
            throw KeyChainError.invalidData
        }

        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service
        ]

        let attributes: [CFString: Any] = [
            kSecAttrAccount: key.rawValue,
            kSecValueData: validData
        ]

        let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)

        if status == errSecSuccess {
            print("key chain updated well")
        } else {
            let err = SecCopyErrorMessageString(status, nil)
            throw KeyChainError.recognizedKeyChainError(status, err)
        }

    }

    // MARK: Read
    /**
     ν‚€μ²΄μΈμ—μ„œ 데이터 κ°’ λΆˆλŸ¬μ˜€λŠ” ν•¨μˆ˜
     
     λ°œμƒν•  수 μžˆλŠ” μ—λŸ¬
     1. invalidBundleID -> μœ νš¨ν•˜μ§€ μ•Šμ€ bundle id의 경우
     2. dataCannotConvertString -> μ €μž₯ λ°μ΄ν„°μ˜ String λ³€ν™˜μ΄ λΆˆκ°€λŠ₯ν•œ 경우
     3. dataNotFound -> key κ°’μœΌλ‘œ μ ‘κ·Όν•œ 데이터가 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 경우
     4. recognizedKeyChainError -> λ°œμƒ κ°€λŠ₯
     
     - Parameter key: enum KeyChainType에 μ‘΄μž¬ν•˜λŠ” μ €μž₯ν•œ κ°’μ˜ νƒ€μž…
     - Returns: KeyChain에 μ €μž₯된 κ°’
     */
    func getStringTypeFromKeyChain(type key: KeyChainType) throws -> String {

        guard let service = self.bundleID else {
            throw KeyChainError.invalidBundleID
        }

        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: key.rawValue,
            kSecReturnData: true,
            kSecReturnAttributes: true,
            kSecMatchLimit: kSecMatchLimitOne
        ]

        var dataTypeRef: CFTypeRef?

        let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

        if status == errSecSuccess {
            guard let item = dataTypeRef as? [String: Any] else {
                throw KeyChainError.dataCannotConvertString
            }

            guard let deviceData = item[kSecValueData as String] as? Data else {
                throw KeyChainError.dataCannotConvertString
            }

            guard let result = String(data: deviceData, encoding: .utf8) else {
                throw KeyChainError.dataCannotConvertString
            }

            return result
        } else if status == errSecItemNotFound || status == -25300 {
            throw KeyChainError.dataNotFound
        } else {
            let err = SecCopyErrorMessageString(status, nil)
            throw KeyChainError.recognizedKeyChainError(status, err)
        }

    }
}

fileprivate extension KeyChainUtil {

}

'CS > iOS, Swift' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

[iOS] μ„œλ²„ 톡신 Base Service  (0) 2023.04.07
[iOS] μ›Ήλ·° μΏ ν‚€ κ΄€λ ¨ μœ ν‹Έ  (0) 2023.04.07
[iOS] ν•΄μ‹±  (0) 2023.04.07
[iOS] Framework, Library RnD  (0) 2023.04.07
[iOS] static framework to xcframework, cocoapod 배포  (0) 2023.04.07
더보기
CS/iOS, Swift