🇺🇸 EN

🔒 CryptoKit 완전정복

암호화부터 서명까지! Apple의 현대적 암호화 프레임워크로 데이터를 완벽하게 보호하세요.

⭐ 난이도: ⭐⭐ ⏱️ 예상 시간: 1-2h 📂 System & Network

✨ 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

📎 Apple 공식 자료

📘 공식 문서 🎬 WWDC 세션