π 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 곡κ°ν€ μνΈν
β
λμ§νΈ μλͺ
λ° ν€ κ΅ν