πŸ”’ CryptoKit 완전정볡

μ•”ν˜Έν™”λΆ€ν„° μ„œλͺ…κΉŒμ§€! Apple의 ν˜„λŒ€μ  μ•”ν˜Έν™” ν”„λ ˆμž„μ›Œν¬λ‘œ 데이터λ₯Ό μ™„λ²½ν•˜κ²Œ λ³΄ν˜Έν•˜μ„Έμš”.

✨ CryptoKitμ΄λž€?

CryptoKit은 Apple이 μ œκ³΅ν•˜λŠ” Swift λ„€μ΄ν‹°λΈŒ μ•”ν˜Έν™” ν”„λ ˆμž„μ›Œν¬μž…λ‹ˆλ‹€. ν•΄μ‹±, λŒ€μΉ­ν‚€ μ•”ν˜Έν™”, κ³΅κ°œν‚€ μ•”ν˜Έν™”, λ””μ§€ν„Έ μ„œλͺ…을 κ°„κ²°ν•˜κ³  μ•ˆμ „ν•˜κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. iOS 13+μ—μ„œ μ‚¬μš© κ°€λŠ₯ν•˜λ©° λͺ¨λ“  μ•”ν˜Έν™” μž‘μ—…μ΄ Secure Enclaveμ—μ„œ μ²˜λ¦¬λ©λ‹ˆλ‹€.

πŸ“¦ μ£Όμš” κΈ°λŠ₯

κΈ°λŠ₯ κ°œμš”
βœ… ν•΄μ‹± (SHA-256, SHA-384, SHA-512)
βœ… λŒ€μΉ­ν‚€ μ•”ν˜Έν™” (AES-GCM, ChaCha20-Poly1305)
βœ… κ³΅κ°œν‚€ μ•”ν˜Έν™” (Curve25519, P-256, P-384, P-521)
βœ… λ””μ§€ν„Έ μ„œλͺ… (ECDSA, EdDSA)
βœ… ν‚€ κ΅ν™˜ (ECDH)
βœ… HKDF (ν‚€ μœ λ„)
βœ… HMAC (λ©”μ‹œμ§€ 인증 μ½”λ“œ)

πŸ”‘ ν•΄μ‹± (Hashing)

Hashing.swift
import CryptoKit
import Foundation

// SHA-256 ν•΄μ‹±
func hashData(_ input: String) -> String {
    let data = Data(input.utf8)
    let hash = SHA256.hash(data: data)

    // Hex λ¬Έμžμ—΄λ‘œ λ³€ν™˜
    return hash.compactMap { String(format: "%02x", $0) }.joined()
}

// μ‚¬μš© μ˜ˆμ‹œ
let password = "mySecurePassword123"
let hashed = hashData(password)
print(hashed)
// "b3d2f8c5..." (64자 hex λ¬Έμžμ—΄)

// SHA-512 ν•΄μ‹±
func hashSHA512(_ input: String) -> String {
    let data = Data(input.utf8)
    let hash = SHA512.hash(data: data)
    return hash.description  // λ˜λŠ” hex λ³€ν™˜
}

// 파일 ν•΄μ‹±
func hashFile(at url: URL) throws -> String {
    let data = try Data(contentsOf: url)
    let hash = SHA256.hash(data: data)
    return hash.compactMap { String(format: "%02x", $0) }.joined()
}

πŸ” λŒ€μΉ­ν‚€ μ•”ν˜Έν™” (AES-GCM)

SymmetricEncryption.swift
import CryptoKit

// AES-GCM μ•”ν˜Έν™”
func encryptData(_ plaintext: String, key: SymmetricKey) throws -> Data {
    let data = Data(plaintext.utf8)

    // AES-GCM μ•”ν˜Έν™” (인증 νƒœκ·Έ 포함)
    let sealedBox = try AES.GCM.seal(data, using: key)

    // combined: nonce + ciphertext + tag
    return sealedBox.combined!
}

func decryptData(_ encrypted: Data, key: SymmetricKey) throws -> String {
    // SealedBox 볡원
    let sealedBox = try AES.GCM.SealedBox(combined: encrypted)

    // λ³΅ν˜Έν™”
    let decryptedData = try AES.GCM.open(sealedBox, using: key)

    return String(data: decryptedData, encoding: .utf8)!
}

// μ‚¬μš© μ˜ˆμ‹œ
let key = SymmetricKey(size: .bits256)  // 랜덀 ν‚€ 생성
let message = "λΉ„λ°€ λ©”μ‹œμ§€"

let encrypted = try encryptData(message, key: key)
let decrypted = try decryptData(encrypted, key: key)

print(decrypted)  // "λΉ„λ°€ λ©”μ‹œμ§€"

πŸ”‘ λŒ€μΉ­ν‚€ μ €μž₯ 및 관리

KeyManagement.swift
import CryptoKit
import Foundation

// ν‚€λ₯Ό Keychain에 μ €μž₯
func saveKey(_ key: SymmetricKey, tag: String) throws {
    let keyData = key.withUnsafeBytes { Data($0) }

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: tag,
        kSecValueData as String: keyData
    ]

    let status = SecItemAdd(query as CFDictionary, nil)

    guard status == errSecSuccess else {
        throw NSError(domain: "KeychainError", code: Int(status))
    }
}

func loadKey(tag: String) throws -> SymmetricKey {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: tag,
        kSecReturnData as String: true
    ]

    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)

    guard status == errSecSuccess,
          let keyData = item as? Data else {
        throw NSError(domain: "KeychainError", code: Int(status))
    }

    return SymmetricKey(data: keyData)
}

// μ‚¬μš© μ˜ˆμ‹œ
let key = SymmetricKey(size: .bits256)
try saveKey(key, tag: "MyEncryptionKey")

// λ‚˜μ€‘μ— ν‚€ 뢈러였기
let loadedKey = try loadKey(tag: "MyEncryptionKey")

🌐 κ³΅κ°œν‚€ μ•”ν˜Έν™” (Curve25519)

PublicKeyEncryption.swift
import CryptoKit

// ν‚€ 쌍 생성
let alicePrivateKey = Curve25519.KeyAgreement.PrivateKey()
let alicePublicKey = alicePrivateKey.publicKey

let bobPrivateKey = Curve25519.KeyAgreement.PrivateKey()
let bobPublicKey = bobPrivateKey.publicKey

// ECDH ν‚€ κ΅ν™˜
let aliceSharedSecret = try alicePrivateKey.sharedSecretFromKeyAgreement(with: bobPublicKey)
let bobSharedSecret = try bobPrivateKey.sharedSecretFromKeyAgreement(with: alicePublicKey)

// 곡유 λΉ„λ°€μ—μ„œ λŒ€μΉ­ν‚€ 생성
let aliceSymmetricKey = aliceSharedSecret.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: Data(),
    sharedInfo: Data("Encryption Key".utf8),
    outputByteCount: 32
)

let bobSymmetricKey = bobSharedSecret.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: Data(),
    sharedInfo: Data("Encryption Key".utf8),
    outputByteCount: 32
)

// 이제 Alice와 Bob은 같은 λŒ€μΉ­ν‚€λ₯Ό 가짐
let message = "λΉ„λ°€ λ©”μ‹œμ§€"
let encrypted = try encryptData(message, key: aliceSymmetricKey)
let decrypted = try decryptData(encrypted, key: bobSymmetricKey)

print(decrypted)  // "λΉ„λ°€ λ©”μ‹œμ§€"

✍️ λ””μ§€ν„Έ μ„œλͺ… (EdDSA)

DigitalSignature.swift
import CryptoKit

// μ„œλͺ… ν‚€ 생성
let signingKey = Curve25519.Signing.PrivateKey()
let publicKey = signingKey.publicKey

// 데이터 μ„œλͺ…
func signData(_ data: Data, with key: Curve25519.Signing.PrivateKey) throws -> Data {
    let signature = try key.signature(for: data)
    return Data(signature)
}

// μ„œλͺ… 검증
func verifySignature(
    _ signature: Data,
    for data: Data,
    publicKey: Curve25519.Signing.PublicKey
) -> Bool {
    do {
        return try publicKey.isValidSignature(signature, for: data)
    } catch {
        return false
    }
}

// μ‚¬μš© μ˜ˆμ‹œ
let document = Data("μ€‘μš”ν•œ λ¬Έμ„œ".utf8)

// μ„œλͺ… 생성
let signature = try signData(document, with: signingKey)

// μ„œλͺ… 검증
let isValid = verifySignature(signature, for: document, publicKey: publicKey)
print("μ„œλͺ… 유효: \(isValid)")  // true

// λ¬Έμ„œ λ³€μ‘° μ‹œ
let tamperedDoc = Data("λ³€μ‘°λœ λ¬Έμ„œ".utf8)
let isValid2 = verifySignature(signature, for: tamperedDoc, publicKey: publicKey)
print("μ„œλͺ… 유효: \(isValid2)")  // false

πŸ“± SwiftUI 톡합

EncryptionView.swift
import SwiftUI
import CryptoKit

struct EncryptionDemoView: View {
    @State private var plaintext = ""
    @State private var encrypted = ""
    @State private var decrypted = ""

    let key = SymmetricKey(size: .bits256)

    var body: some View {
        VStack(spacing: 20) {
            Text("AES-GCM μ•”ν˜Έν™”")
                .font(.title)
                .bold()

            TextField("μ•”ν˜Έν™”ν•  ν…μŠ€νŠΈ", text: $plaintext)
                .textFieldStyle(.roundedBorder)

            Button("μ•”ν˜Έν™”") {
                encrypt()
            }
            .buttonStyle(.borderedProminent)

            if !encrypted.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    Text("μ•”ν˜Έν™”λœ 데이터:")
                        .font(.headline)
                    Text(encrypted)
                        .font(.system(.caption, design: .monospaced))
                        .foregroundStyle(.secondary)
                        .textSelection(.enabled)
                }
                .padding()
                .background(.gray.opacity(0.1))
                .cornerRadius(8)

                Button("λ³΅ν˜Έν™”") {
                    decrypt()
                }
                .buttonStyle(.bordered)

                if !decrypted.isEmpty {
                    Text("λ³΅ν˜Έν™” κ²°κ³Ό: \(decrypted)")
                        .font(.headline)
                        .foregroundStyle(.green)
                }
            }
        }
        .padding()
    }

    func encrypt() {
        guard !plaintext.isEmpty else { return }

        do {
            let data = Data(plaintext.utf8)
            let sealedBox = try AES.GCM.seal(data, using: key)
            encrypted = sealedBox.combined!.base64EncodedString()
        } catch {
            encrypted = "μ•”ν˜Έν™” μ‹€νŒ¨: \(error)"
        }
    }

    func decrypt() {
        guard let data = Data(base64Encoded: encrypted) else { return }

        do {
            let sealedBox = try AES.GCM.SealedBox(combined: data)
            let decryptedData = try AES.GCM.open(sealedBox, using: key)
            decrypted = String(data: decryptedData, encoding: .utf8) ?? "였λ₯˜"
        } catch {
            decrypted = "λ³΅ν˜Έν™” μ‹€νŒ¨"
        }
    }
}

πŸ” μ‹€μ „ 예제: μ•ˆμ „ν•œ λ…ΈνŠΈ μ•±

SecureNote.swift
import SwiftUI
import CryptoKit

// μ•”ν˜Έν™”λœ λ…ΈνŠΈ λͺ¨λΈ
struct SecureNote: Identifiable, Codable {
    let id: UUID
    let encryptedContent: Data
    let createdAt: Date
}

class SecureNoteManager {
    private let key: SymmetricKey

    init() {
        // μ•± μ‹œμž‘ μ‹œ ν‚€ λ‘œλ“œ λ˜λŠ” 생성
        if let savedKey = try? loadKey(tag: "SecureNoteKey") {
            key = savedKey
        } else {
            key = SymmetricKey(size: .bits256)
            try? saveKey(key, tag: "SecureNoteKey")
        }
    }

    func createNote(_ content: String) throws -> SecureNote {
        let data = Data(content.utf8)
        let sealedBox = try AES.GCM.seal(data, using: key)

        return SecureNote(
            id: UUID(),
            encryptedContent: sealedBox.combined!,
            createdAt: Date()
        )
    }

    func decryptNote(_ note: SecureNote) throws -> String {
        let sealedBox = try AES.GCM.SealedBox(combined: note.encryptedContent)
        let decryptedData = try AES.GCM.open(sealedBox, using: key)
        return String(data: decryptedData, encoding: .utf8) ?? ""
    }
}

// SwiftUI View
struct SecureNoteView: View {
    @State private var notes: [SecureNote] = []
    @State private var newNoteContent = ""
    @State private var selectedNote: SecureNote?

    let manager = SecureNoteManager()

    var body: some View {
        NavigationStack {
            List {
                Section {
                    TextField("μƒˆ λ…ΈνŠΈ λ‚΄μš©", text: $newNoteContent)
                    Button("μ €μž₯") {
                        saveNote()
                    }
                    .disabled(newNoteContent.isEmpty)
                }

                Section("μ•”ν˜Έν™”λœ λ…ΈνŠΈ") {
                    ForEach(notes) { note in
                        Button("λ…ΈνŠΈ \(note.id.uuidString.prefix(8))...") {
                            selectedNote = note
                        }
                    }
                }
            }
            .navigationTitle("μ•ˆμ „ν•œ λ…ΈνŠΈ")
            .sheet(item: $selectedNote) { note in
                NoteDetailView(note: note, manager: manager)
            }
        }
    }

    func saveNote() {
        do {
            let note = try manager.createNote(newNoteContent)
            notes.append(note)
            newNoteContent = ""
        } catch {
            print("μ•”ν˜Έν™” μ‹€νŒ¨: \(error)")
        }
    }
}

struct NoteDetailView: View {
    let note: SecureNote
    let manager: SecureNoteManager

    var body: some View {
        VStack {
            if let content = try? manager.decryptNote(note) {
                Text(content)
                    .padding()
            } else {
                Text("λ³΅ν˜Έν™” μ‹€νŒ¨")
                    .foregroundStyle(.red)
            }
        }
    }
}

πŸ”‘ P-256 (NIST ν‘œμ€€ 곑선)

P256Example.swift
import CryptoKit

// P-256 ECDSA μ„œλͺ…
let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey

let data = Data("μ€‘μš”ν•œ 데이터".utf8)

// μ„œλͺ… 생성
let signature = try privateKey.signature(for: data)

// μ„œλͺ… 검증
let isValid = publicKey.isValidSignature(signature, for: data)
print("P-256 μ„œλͺ… 유효: \(isValid)")

// P-256 ν‚€ κ΅ν™˜
let aliceKey = P256.KeyAgreement.PrivateKey()
let bobKey = P256.KeyAgreement.PrivateKey()

let sharedSecret = try aliceKey.sharedSecretFromKeyAgreement(with: bobKey.publicKey)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
    using: SHA256.self,
    salt: Data(),
    sharedInfo: Data(),
    outputByteCount: 32
)

πŸ”’ ChaCha20-Poly1305 μ•”ν˜Έν™”

ChaCha20.swift
import CryptoKit

// ChaCha20-Poly1305 (AES λŒ€μ•ˆ, λͺ¨λ°”μΌμ—μ„œ 더 빠름)
func encryptWithChaCha(_ plaintext: String, key: SymmetricKey) throws -> Data {
    let data = Data(plaintext.utf8)
    let sealedBox = try ChaChaPoly.seal(data, using: key)
    return sealedBox.combined
}

func decryptWithChaCha(_ encrypted: Data, key: SymmetricKey) throws -> String {
    let sealedBox = try ChaChaPoly.SealedBox(combined: encrypted)
    let decryptedData = try ChaChaPoly.open(sealedBox, using: key)
    return String(data: decryptedData, encoding: .utf8)!
}

// μ‚¬μš© μ˜ˆμ‹œ
let key = SymmetricKey(size: .bits256)
let encrypted = try encryptWithChaCha("λΉ„λ°€ λ©”μ‹œμ§€", key: key)
let decrypted = try decryptWithChaCha(encrypted, key: key)

πŸ’‘ HIG κ°€μ΄λ“œλΌμΈ

λ³΄μ•ˆ ꢌμž₯사항
βœ… DO
1. μ•”ν˜Έν™” ν‚€λŠ” Keychain에 μ €μž₯
2. λŒ€μΉ­ν‚€λŠ” 256λΉ„νŠΈ μ‚¬μš© (.bits256)
3. μ„œλͺ… 검증 ν•„μˆ˜ (무결성 확인)
4. λ―Όκ°ν•œ λ°μ΄ν„°λŠ” λ©”λͺ¨λ¦¬μ—μ„œ μ¦‰μ‹œ 제거
5. SHA-256 이상 μ‚¬μš© (SHA-1 κΈˆμ§€)

❌ DON'T
1. ν•˜λ“œμ½”λ”©λœ μ•”ν˜Έν™” ν‚€
2. UserDefaults에 ν‚€ μ €μž₯
3. μ»€μŠ€ν…€ μ•”ν˜Έν™” μ•Œκ³ λ¦¬μ¦˜ κ΅¬ν˜„
4. ν‰λ¬ΈμœΌλ‘œ λ„€νŠΈμ›Œν¬ 전솑
5. λ‘œκ·Έμ— λ―Όκ°ν•œ 데이터 좜λ ₯

πŸ”§ 싀무 ν™œμš© 팁

μ‹€μ „ νŒ¨ν„΄
import CryptoKit

// 1. HMAC으둜 λ©”μ‹œμ§€ 인증
func authenticateMessage(_ message: String, key: SymmetricKey) -> Data {
    let data = Data(message.utf8)
    let authCode = HMAC<SHA256>.authenticationCode(for: data, using: key)
    return Data(authCode)
}

// 2. λΉ„λ°€λ²ˆν˜Έμ—μ„œ ν‚€ 생성 (PBKDF2 λŒ€μ‹  Argon2 ꢌμž₯)
func deriveKey(from password: String, salt: Data) -> SymmetricKey {
    let passwordData = Data(password.utf8)
    let hash = SHA256.hash(data: passwordData + salt)
    return SymmetricKey(data: hash)
}

// 3. 파일 μ•”ν˜Έν™”
func encryptFile(at url: URL, key: SymmetricKey) throws {
    let data = try Data(contentsOf: url)
    let sealedBox = try AES.GCM.seal(data, using: key)
    try sealedBox.combined!.write(to: url.appendingPathExtension("encrypted"))
}

// 4. κ³΅κ°œν‚€λ₯Ό Data둜 λ³€ν™˜ (μ „μ†‘μš©)
let privateKey = Curve25519.Signing.PrivateKey()
let publicKeyData = privateKey.publicKey.rawRepresentation

// Dataμ—μ„œ κ³΅κ°œν‚€ 볡원
let restoredPublicKey = try Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData)

πŸ’‘ CryptoKit 핡심
βœ… Swift λ„€μ΄ν‹°λΈŒ μ•”ν˜Έν™” ν”„λ ˆμž„μ›Œν¬
βœ… Secure Enclave ν™œμš©
βœ… AES-GCM, ChaCha20-Poly1305 지원
βœ… Curve25519, P-256 κ³΅κ°œν‚€ μ•”ν˜Έν™”
βœ… λ””μ§€ν„Έ μ„œλͺ… 및 ν‚€ κ΅ν™˜

πŸ“¦ ν•™μŠ΅ 자료

πŸ’»
GitHub ν”„λ‘œμ νŠΈ
πŸ“–
Apple 곡식 λ¬Έμ„œ
πŸŽ₯
WWDC 2019