Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] keychain에 암호화/복호화하여 저장 방법 (SecureEnclave) 본문

iOS 응용 (swift)

[iOS - swift] keychain에 암호화/복호화하여 저장 방법 (SecureEnclave)

jake-kim 2021. 3. 12. 23:38

* keychain사용방법: KeychainAccess 프레임워크 사용 방법 참고 - ios-development.tistory.com/216

암호화 모듈

  • keychain에 저장된 문자열은 모두 외부에서 디바이스만 있다면 조회가 가능하기 때문에 암호화/복호화가 필수
  • SecureEnclave: prvate key를 이용하여 layer를 만들어 암호화하는 방법
  • 앱에서는 BundleID를 가지고 암호화/복호화
  • Crypto 클래스 정의
import Foundation
import Security

class Crypto {

    static let privTag = Bundle.main.bundleIdentifier!
    static let SecureEnclaveAccess = SecAccessControlCreateWithFlags(
        kCFAllocatorDefault,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        .privateKeyUsage,
        nil
    )

    static var EnclaveAttribute: [String: Any] = [
        kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
        kSecAttrKeySizeInBits as String: 256,
        kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
        kSecPrivateKeyAttrs as String: [
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: privTag,
            kSecAttrAccessControl as String: SecureEnclaveAccess!,
        ],
    ]

    // kSecAttrAccessControl as String:    access
    static var NonEnclaveAttribute: [String: Any] = [
        kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
        kSecAttrKeySizeInBits as String: 256,
        kSecPrivateKeyAttrs as String: [
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: privTag,
        ],
    ]

    public class func getPubKey() -> SecKey {
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrApplicationTag as String: privTag,
            kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
            kSecReturnRef as String: true,
        ]

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

        if status != errSecSuccess {
            print("priv key get failed.. generate new key")
            generateKey()
            SecItemCopyMatching(query as CFDictionary, &result)
        }
        let privKey: SecKey = result as! SecKey
        let pubKey = SecKeyCopyPublicKey(privKey)!
        return pubKey
    }

    public class func getPrivKey() -> SecKey {
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrApplicationTag as String: privTag,
            kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
            kSecReturnRef as String: true,
        ]

        var result: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        if status != errSecSuccess {
            print("priv key get failed.. generate new key")
            SecItemDelete(query as CFDictionary)
            generateKey()
            let new = SecItemCopyMatching(query as CFDictionary, &result)
            print(new)
        }
        let privKey: SecKey = result as! SecKey
        return privKey
    }

    public class func deleteKey() {
        let Privquery: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrApplicationTag as String: privTag,
            kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
            kSecReturnRef as String: true,
        ]
        let code = SecItemDelete(Privquery as CFDictionary)
        if code == errSecSuccess {
            print("Key Delete Complete!")
        } else {
            print("Delete Failed!!")
            print(code)
        }
    }
    public class func generateKeyNoEnclave() {
        var error: Unmanaged<CFError>?
        // 기기가 SecureEnclave를 지원하지 않는 경우, 일반 암호화 키 생성 후 키체인에 저장
        _ = SecKeyCreateRandomKey(NonEnclaveAttribute as CFDictionary, &error)
    }

    public class func generateKey() {
        var error: Unmanaged<CFError>?
        var privKey = SecKeyCreateRandomKey(EnclaveAttribute as CFDictionary, &error) // Secure Enclave 키 생성
        if privKey == nil { // 단말기가 Secure Enclave를 지원하지 않는 경우
            print("Secure Enclave Not Supported.")
            privKey = SecKeyCreateRandomKey(NonEnclaveAttribute as CFDictionary, &error)
        }
    }

    public class func encrypt(input: String) -> String? {
        let pubKey: SecKey = getPubKey()
        print(pubKey)
        var error: Unmanaged<CFError>?

        let plain: CFData = input.data(using: .utf8)! as CFData
        let encData = SecKeyCreateEncryptedData(pubKey, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, plain, &error)
        var tdata: Data
        if encData == nil {
            print("encrypt error!!!")
            return nil
        } else {
            tdata = encData! as Data
        }

        let b64result = tdata.base64EncodedString()
        return b64result
    }

    public class func decrypt(input: String) -> String? {
        let privKey: SecKey = getPrivKey()
        print(privKey)
        guard let encData = Data(base64Encoded: input) else {
            print("decrypt error!!!")
            return nil
        }
        var error: Unmanaged<CFError>?
        let decData = SecKeyCreateDecryptedData(privKey, SecKeyAlgorithm.eciesEncryptionStandardX963SHA256AESGCM, encData as CFData, &error)
        var tdata: Data

        if decData == nil {
            print("decrypt error!!!")
            return nil
        } else {
            tdata = decData! as Data
        }

        let decResult = String(data: tdata, encoding: .utf8)!
        return decResult
    }
}

Crypto 클래스 사용

  • 암호화 할 땐, Crypto.encrypt(input:)를 사용하고 복호화 할 땐 Crypto.decrypt(input:)을 사용
  • keychain을 사용하여 set, get할 때 각각 적용 (KeychainAccess를 사용하여 get, set 구현은 이곳 참고)
  • get, set에 사용코드
// KeychainService.swift

...

    // MARK: Save
    public func save(key: String, value: String) {
        do {
            if let encryptValue = Crypto.encrypt(input: value) {
                print("CRPYTO", "SAVE", key, value, "->", encryptValue)
                try lockCredentials.set(encryptValue, key: key)
            } else {
                print("CRPYTO", "SAVE FAILED", key, value)
            }
        } catch let error {
            print("KEYCHAIN SAVE :: Failed to save this value with this key \(key) => \(error)")
        }
        
    }

...

    // MARK: Get
    public func get(key: String) -> String? {
        var decrypt: String?
        var encrypt: String?
        if let value = lockCredentials[string: key] {
            encrypt = value
            decrypt = Crypto.decrypt(input: value)
        }
        print("CRPYTO", "GET", key, String(describing: encrypt), "->", String(describing: decrypt))
        return decrypt
    }

 

* 참고

- developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave

Comments