🔒 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 공개키 암호화
✅ 디지털 서명 및 키 교환