๐ŸŒ™

[Swift] Keychain

yeggrrr๐Ÿผ 2025. 9. 14. 16:09
728x90

iOS์—์„œ ํ”ํžˆ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋กœ๋Š” UserDefaults, CoreData, Keychain ๋“ฑ์ด ์žˆ๋‹ค.

ํ•œ์ฐฝ ๊ณต๋ถ€ํ•  ๋•Œ, ์ด๋Ÿฐ ์ด์•ผ๊ธฐ๋ฅผ ์ž์ฃผ ๋“ค์—ˆ๋‹ค.

"๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜น์€ ํ† ํฐ๊ณผ ๊ฐ™์ด ์ค‘์š”ํ•œ ์ •๋ณด๋“ค์€ ํ‚ค์ฒด์ธ์— ์ €์žฅํ•ด์•ผํ•ด. UserDefaults๋Š” ๋ณด์•ˆ์ƒ ์ข‹์ง€ ์•Š์•„."
"๊ฒฐ๊ตญ ๋”ฐ์ง€๊ณ  ๋ณด๋ฉด UserDefaults์— ์ €์žฅํ•˜๋‚˜ ํ‚ค์ฒด์ธ์— ์ €์žฅํ•˜๋‚˜ ๋น„์Šทํ•ด."

ํ•˜์ง€๋งŒ ์ •์ž‘ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ค ์ €์žฅ์†Œ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•œ ๊ธฐ์ค€์„ ์ฐพ์ง€ ๋ชปํ–ˆ๊ณ ,
๋‹จ์ˆœํžˆ ๊ตฌํ˜„์ด ๊ฐ„ํŽธํ•˜๋‹ค๋Š” ์ด์œ ๋กœ UserDefaults๋ฅผ ์ž์ฃผ ์‚ฌ์šฉํ•ด ์™”๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” iOS Keychain์„ ์ข€ ๋” ๋ฉด๋ฐ€ํžˆ ์‚ดํŽด๋ณด๊ณ ,
์•ž์œผ๋กœ๋Š” ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ๋ฐ์ดํ„ฐ์˜ ์„ฑ๊ฒฉ๊ณผ ๋ณด์•ˆ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋งž์ถ”์–ด ์ ์ ˆํ•œ ์ €์žฅ์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ธฐ์ค€์„ ์„ธ์›Œ๋ณด๋ ค๊ณ  ํ•œ๋‹ค :)


Keychain์ด๋ž€?

- iOS, macOS ๋“ฑ ์• ํ”Œ ํ”Œ๋žซํผ์—์„œ ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณตํ•˜๋Š” ์‹œ์Šคํ…œ ์ €์žฅ์†Œ์ž„
๋‹จ์ˆœํ•œ ํŒŒ์ผ ์•”ํ˜ธ๊ฐ€ ์•„๋‹ˆ๋ผ, OS ์ฐจ์›์˜ ๋ณด์•ˆ ๊ด€๋ฆฌ๊ฐ€ ์ ์šฉ๋˜๋ฉฐ, ๊ธฐ๊ธฐ ์ž ๊ธˆ์ด๋‚˜ ์ƒ์ฒด ์ธ์ฆ ๊ฐ™์€ ํ•˜๋“œ์›จ์–ด ๋ณด์•ˆ ์š”์†Œ์™€๋„ ์—ฐ๊ณ„ํ•  ์ˆ˜ ์žˆ์Œ
๋Œ€ํ‘œ์ ์œผ๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ, ์—‘์„ธ์Šค ํ† ํฐ, ์ธ์ฆ์„œ์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€ํ•  ๋•Œ ์‚ฌ์šฉํ•จ

 

UserDefaults, CoreData์™€์˜ ๋น„๊ต

๋งŽ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋•Œ UserDafaults๋‚˜ CoreData๋ฅผ ๋จผ์ € ๋– ์˜ฌ๋ฆผ
๊ทธ๋Ÿฌ๋‚˜ ๋ณด์•ˆ ์ธก๋ฉด์—์„œ Keychain์€ ๋ช…ํ™•ํ•œ ์žฅ์ ์ด ์žˆ์Œ

์ €์žฅ์†Œ ์šฉ๋„ ๋ณด์•ˆ์„ฑ
UserDefaults ๋‹จ์ˆœ ์„ค์ • ๊ฐ’, ํ”Œ๋ž˜๊ทธ ์ €์žฅ ์•”ํ˜ธํ™” ์—†์Œ
CoreData ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ, ๋ชจ๋ธ ์ €์žฅ ์ง์ ‘ ์•”ํ˜ธํ™” ํ•„์š”
Keychain ๋น„๋ฐ€๋ฒˆํ˜ธ, ํ† ํฐ, ์ธ์ฆ์„œ ๋“ฑ OS ๋ณด์•ˆ ๋‚ด์žฅ, ํ•ด๋“œ์›จ์–ด ์ธ์ฆ ์—ฐ๊ณ„

๐Ÿ’๐Ÿป‍โ™€๏ธ ๋ณด์•ˆ์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ Keychain์— ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด Apple์˜ ๊ฐ€์ด๋“œ๋ผ์ธ์— ์ ํ˜€์žˆ์Œ

 

Keychain์˜ ์ฃผ์š” ๊ธฐ๋Šฅ

- ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ ์ €์žฅ: ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ† ํฐ, ์ธ์ฆ์„œ ๋“ฑ
- ๋ฐ์ดํ„ฐ ์กฐํšŒ/์‚ญ์ œ/์—…๋ฐ์ดํŠธ: Keychain API๋ฅผ ํ†ตํ•ด CRUD ์ง€์›
- ์ƒ์ฒด ์ธ์ฆ ์—ฐ๊ณ„: FaceID, TouchID์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
- iCloud Keychian ๋™๊ธฐํ™”: ๋™์ผ Apple ID๋กœ ๋กœ๊ทธ์ธ๋œ ๊ธฐ๊ธฐ ๊ฐ„ ์ž๋™ ๋™๊ธฐํ™”
- ์•ฑ ๊ฐ„ ๊ณต์œ : Access Group์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์•ฑ์—์„œ Keychain ๋ฐ์ดํ„ฐ ๊ณต์œ  ๊ฐ€๋Šฅ


Keychain API?

๋”๋ณด๊ธฐ

Keychain Services API
- ์†Œ๋Ÿ‰์˜ ๋น„๋ฐ€ ๋ฐ์ดํ„ฐ(์•”ํ˜ธ, ํ† ํฐ, ํ‚ค ๋“ฑ)๋ฅผ OS ๋ณด์•ˆ/๋ฐ์ดํ„ฐ ๋ณดํ˜ธ(ํด๋ž˜์Šค ํ‚ค, ํ‚ค๋ฐฑ) ๊ธฐ๋ฐ˜ ์•”ํ˜ธํ™” ์ €์žฅ์†Œ์— ์•ˆ์ „ํ•˜๊ฒŒ CRUD๋กœ ๊ด€๋ฆฌํ•˜๋Š” ํ‘œ์ค€ ์ธํ„ฐํŽ˜์ด์Šค
- ํ•ต์‹ฌ ํ•จ์ˆ˜: SecItemAdd / SecItemCopyMatching / SecItemUpdate / SecItemDelete

ํ•ญ๋ชฉ(Items)
- Keychain์€ '์•„์ดํ…œ' ๋‹จ์œ„๋กœ ๋™์ž‘ํ•จ
   • kSecClassGenericPassword: ์ผ๋ฐ˜ ๋น„๋ฐ€๋ฒˆํ˜ธ/ํ† ํฐ ๋“ฑ
   • kSecClassInternetPassword: ์„œ๋ฒ„·๋„๋ฉ”์ธ·ํ”„๋กœํ† ์ฝœ ์ •๋ณด์™€ ํ•จ๊ป˜ ์“ฐ๋Š” ์ธํ„ฐ๋„ท ๋น„๋ฐ€๋ฒˆํ˜ธ
   • kSecClassKey: ๋Œ€์นญ/๋น„๋Œ€์นญ ํ‚ค
   • kSecClassCertificate: ์ธ์ฆ์„œ

ํŠน์ง•
- ๊ฐ•ํ•œ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ:
   • iOS ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ ์ฒด๊ณ„/ํด๋ž˜์Šค ํ‚ค์— ์˜ํ•ด ์ €์žฅ ์‹œ ์•”ํ˜ธํ™”๋˜๊ณ , ํ•˜๋“œ์›จ์–ด ๋ณด์•ˆ ์š”์†Œ์™€ ์—ฐ๊ณ„๋จ
- ์ ‘๊ทผ ์‹œ์  ์ œ์–ด: kSecAttrAcessible
   • ์–ธ์ œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€ ์ง€์ • ๊ฐ€๋Šฅ(ex. ์ž ๊ธˆ ํ•ด์ œ ์‹œ, ์ตœ์ดˆ ์ž ๊ธˆ ํ•ด์ œ ์ดํ›„, ...)
- ์ถ”๊ฐ€ ์ธ์ฆ ์š”๊ตฌ: SecAccessControl + LAContext
   • FaceID/TouchID·๋””๋ฐ”์ด์Šค ์•”ํ˜ธ ๋“ฑ์„ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Œ
- ์•ฑ/ํ™•์žฅ ๊ฐ„ ๊ณต์œ : Access Group
   • Xcode์˜ Keychain Sharing์„ ์ผœ๊ณ  ๊ณตํ†ต access group์„ ์„ค์ •ํ•œ ํ›„, kSecAttrAccessGroup์„ ์ง€์ •
- iCloud ๋™๊ธฐํ™”
   • kSecAttrSynchronizable: true๋กœ ์„ค์ •

 

Swfit์—์„œ์˜ Keychain ์ ‘๊ทผ ๋ฐฉ๋ฒ•

1) Security ํ”„๋ ˆ์ž„์›Œํฌ ์ง์ ‘ ์‚ฌ์šฉ

import Security

// ์ €์žฅ
func savePassword(_ password: String, for account: String) {
    let data = password.data(using: .utf8)!
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecValueData as String: data
    ]
    SecItemAdd(query as CFDictionary, nil)
}

// ์กฐํšŒ
func loadPassword(for account: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var item: AnyObject?
    SecItemCopyMatching(query as CFDictionary, &item)
    if let data = item as? Data {
        return String(data: data, encoding: .utf8)
    }
    return nil
}

2) Keychain ๋ž˜ํผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ(๋งํฌ)

import KeychainAccess

let keychain = Keychain(service: "com.example.yegr")
try? keychain.set("yegrPassword123", key: "yegrPassword")
let password = try? keychain.get("yegrPassword")

 

์ฃผ์˜์‚ฌํ•ญ ๋ฐ ์ ์šฉ ์‚ฌ๋ก€

- ๊ผญ ํ•„์š”ํ•œ ๋ฏผ๊ฐ ๋ฐ์ดํ„ฐ๋งŒ ์ €์žฅํ•  ๊ฒƒ(๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” UserDefaults)
- ๋น„๋ฐ€๋ฒˆํ˜ธ ๋Œ€์‹  ์„ธ์…˜ ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋จ
- ์ƒ์ฒด ์ธ์ฆ ์‹คํŒจ ์‹œ fallback ์ „๋žต ํ•„์š”ํ•จ(ex. ์•ฑ ์ž์ฒด ๋น„๋ฐ€๋ฒˆํ˜ธ)
- ์•ฑ ์‚ญ์ œ ํ›„์—๋„ Keychain ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋กœ๊ทธ์•„์›ƒ ์‹œ ๋ฐ˜๋“œ์‹œ ์‚ญ์ œ ์ฒ˜๋ฆฌ ํ•„์š”
- iCloud Keychain์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋™๊ธฐํ™” ์ •์ฑ…์„ ๋ช…ํ™•ํžˆ ์„ค๊ณ„ํ•  ๊ฒƒ

 

๊ฒฐ๋ก 

Keychain์€ iOS์—์„œ ๊ฐ€์žฅ ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์ž„
๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ณต๊ฐ„์ด ์•„๋‹ˆ๋ผ, ๋ณด์•ˆ ์ •์ฑ…๊ณผ ์ธ์ฆ ์ฒด๊ณ„๊ฐ€ ํ†ตํ•ฉ๋œ ์ธํ”„๋ผ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Œ
๋”ฐ๋ผ์„œ ํ† ํฐ, ์ธ์ฆ ์ •๋ณด ๋“ฑ ๋ณด์•ˆ์ด ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ Keychain์— ์ €์žฅํ•˜๊ณ ,
์ผ๋ฐ˜ ์„ค์ • ๊ฐ’์€ UserDefaults ํ˜น์€ CoreData๋กœ ๋‚˜๋ˆ„์–ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ


 

๊ฐœ์ธ์ ์ธ ์˜๊ฒฌ

Keychain์€ ์ €์žฅ/์กฐํšŒ ์ธก๋ฉด์—์„œ๋Š” UserDefaults์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ณ„๋„์˜ ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ ์—†์ด ๋ฐ”๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
๋‹ค๋งŒ UserDefaults๋ณด๋‹ค ๊ตฌํ˜„์ด ๋ณต์žกํ•œ ๋Œ€์‹ , ๋ณด์•ˆ ๋‹จ๊ณ„๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์žฅ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด Access Token์ฒ˜๋Ÿผ ์œ ํšจ ๊ธฐ๊ฐ„์ด ์งง์€ ๋ฐ์ดํ„ฐ๋ผ๋ฉด, ๊ตณ์ด FaceID์™€ ๊ฐ™์€ ์ถ”๊ฐ€ ์ธ์ฆ์„ ๋ถ™์ผ ํ•„์š” ์—†์ด
'kSecAttrAccessibleWhenUnlocked' ์ •๋„๋กœ '๊ธฐ๊ธฐ ์ž ๊ธˆ ํ•ด์ œ ์ƒํƒœ์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ'ํ•˜๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ UserDefaults๋Š” ์•ฑ์„ ์‚ญ์ œํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ์‚ฌ๋ผ์ง€์ง€๋งŒ, Keychain์€ ์•ฑ์„ ์ง€์›Œ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์„ ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ ํ•„์š”์— ๋”ฐ๋ผ ์•ฑ ์‚ญ์ œ๋‚˜ ๋กœ๊ทธ์•„์›ƒ ์‹œ์ ์— Keychain ๋ฐ์ดํ„ฐ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ •๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด๋ณด์ธ๋‹ค.

์ด ํŠน์„ฑ์ด ์–‘๋‚ ์˜ ๊ฒ€์ด ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
๋ณด์•ˆ ์ธก๋ฉด์—์„œ๋Š” ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์˜๋„์™€ ์ƒ๊ด€์—†์ด ๊ธฐ๊ธฐ์— ๋‚จ์•„ ์žˆ๋‹ค๋Š” ์ ์ด ๋‹จ์ ์ด ๋  ์ˆ˜ ์žˆ๋‹ค.
๋ฐ˜๋ฉด์— ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ธก๋ฉด์—์„œ๋Š” ์žฅ์ ์œผ๋กœ ์ž‘์šฉ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ๊ณผ๊ฑฐ์— ์‚ฌ์šฉ์ž๊ฐ€ ์นด์นด์˜ค/๋„ค์ด๋ฒ„์™€ ๊ฐ™์€ ํŠน์ • ์†Œ์…œ ๋กœ๊ทธ์ธ์œผ๋กœ ๊ฐ€์ž…ํ•œ ์ ์ด ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด, ์žฌ๊ฐ€์ž… ํ˜น์€ ์žฌ๋กœ๊ทธ์ธ ๊ณผ์ •์—์„œ ํ•ด๋‹น ์˜ต์…˜์„ ์šฐ์„  ์ถ”์ฒœํ•˜๋Š” UI๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. 


๋‹ค์Œ ๊ณ„ํš

๊ธฐ์กด์— ์ž์ฃผ ์‚ฌ์šฉํ•˜๋˜ UserDefaultsManager์ฒ˜๋Ÿผ KeychainManager๋ฅผ ๊ตฌ์„ฑํ•ด๋ณผ ์˜ˆ์ •

728x90