티스토리 뷰

UITextField에는 isSecureTextEntry라는 옵션이 존재한다.

 

 

이녀석을 true로 set해주면 아래와 같이 텍스트 필드에 값이 입력될 때 값을 가려준다. 

 

 

그렇다면 실제 값이 암호화 되서 입력되는지 확인해보면

메모리의 해당 텍스트필드의 텍스트 주소값을 조회해보면 값은 plain text상태 그대로 보여진다.

 

거의 그럴일은 없을지 모르겠지만.. 비밀번호등을 입력하는 텍스트필드를 메모리 덤프를 뜨게된다면 비밀번호가 보이게 된다.

 

이 값을 가려보자.

 

값을 가리기 위해 원래의 값을 암호화해서 저장해놓고, 텍스트필드의 값을 대치해 놓으려하는데

 

암호화를 위해 Secure Enclave를 사용해주었다. 

https://support.apple.com/ko-kr/guide/security/sec59b0b31ff/web

 

Secure Enclave

Secure Enclave는 최신 버전의 iPhone, iPod, Mac, Apple TV, Apple Watch, HomePod 모델의 전용 보안 하위 시스템입니다.

support.apple.com

해당 하드웨어를 사용하면 '암호화에 필요한 키를 어떻게 관리해야 하는가'에 대한 고민을 할 필요가 없고, 하드웨어 레벨의 높은 보안성을 지원해준다. 

 

암복호화 메서드를 만들어주자.

private func encString(_ input: SafeString) -> Data? {
    guard let key = self.key else { return nil }
    guard let publicKey = SecKeyCopyPublicKey(key) else { return nil }
    let encAlgorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
    guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, encAlgorithm) else { return nil }
    var error: Unmanaged<CFError>?
    let encoding = String.Encoding.utf8.rawValue
    guard let clearTextData = input.data(using: encoding) else { return nil }
    guard let cipherTextData = SecKeyCreateEncryptedData(publicKey, encAlgorithm, clearTextData as CFData, &error) as? Data else { return nil }
    if error != nil { error = nil ; return nil }
    return cipherTextData
}
    
private func decString(_ encData: Data) -> SafeString? {
    guard let key = key else { return nil }
    let decAlgorithm: SecKeyAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
    guard SecKeyIsAlgorithmSupported(key, .decrypt, decAlgorithm) else {
        return nil
    }
    var error: Unmanaged<CFError>?
    guard let decTextData = SecKeyCreateDecryptedData(key, decAlgorithm, encData as CFData, &error) as? Data else { return nil }
    if error != nil { error = nil ; return nil }
    if String(data: decTextData, encoding: .utf8) == nil { return nil }
    let returnString: SafeString = SafeString.makeSafeString(NSMutableString.init(string: String(data: decTextData, encoding: .utf8) ?? ""))
    return returnString
}

 

그 다음은 텍스트필드의 텍스트가 변경될 때마다 암/복호화를 해주기 위해 텍스트필드의 텍스트 변경을 옵저빙 해주자.

textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)

 

그리고 텍스트필드의 텍스트가 추가되거나 지워질 때 아래와 같이 암호화된 데이터를 복호화 시켜주고 값을 변경 한 다음 다시 암호화 해주자. (추가 될 경우 텍스트필드의 마지막 문자를 '-'로 치환해준다.)

private func whenAddedText(_ inputedText: String) {
    let addedText: SafeString = SafeString.makeSafeString(NSMutableString.init(string: String(inputedText.last ?? Character.init(""))))
    let decStr: SafeString = decString(self.encryptedTextData) ?? SafeString.makeSafeString("")
    guard let encData = encString(decStr + addedText) else { return }
    self.encryptedTextData = encData
    self.textField.text?.removeLast()
    self.textField.text = (self.textField.text ?? "") + "-"
}
    
private func whenRemovedText() {
    var decStr: SafeString = decString(self.encryptedTextData) ?? ""
    decStr = decStr.removeLast()
    guard let encData = encString(decStr) else { return }
    self.encryptedTextData = encData
}

 

그리고 입력한 값을 확인하고 싶다면?

아래와 같이 복호화를 해서 반환해주자.

func value() -> SafeString? {
    return decString(self.encryptedTextData)
}

 

테스트를 해보면

위와 같이 메모리덤프를 떠보았을때 값이 치환되어있고, 

 

 

값 조회 메서드를 사용해보면, 암호화 되있었던 값이 복호화 되서 보여지는것을 확인할 수 있다.

 

 

한 가지 더 체크해보아야 할 상황이 있는데, 

private func whenRemovedText() {
    var decStr: SafeString = decString(self.encryptedTextData) ?? "" // 이 부분
    decStr = decStr.removeLast()
    guard let encData = encString(decStr) else { return }
    self.encryptedTextData = encData
}

위의 코드의 주석이 달린 부분처럼 

해당 메서드를 실행시킬 때 복호화 된 String이 지역변수로 할당되면서 메모리에 남을 수 있다.

 

이 문제를 해결하기 위해 일반적인 String이 아닌 SafeString이라는 커스텀 된 String을 사용해주었다.

SafeString은 객체의 라이프사이클이 종료될 때 메모리에 남아있는 데이터를 초기화 시켜주는 기능을 가진 String이다.

 

해당 Class는 NSMutableString의 소멸자를 후킹해 객체의 소멸자가 호출되는 시점에 객체가 사용한 메모리를 초기화 시켜주고 있다. 

fileprivate final class DeallocHoocker {
    typealias Handler = () -> Void
    private struct AssociatedKey {
        static var deallocHoocker = "deallocHoocker"
    }
    private let handler: Handler
    private init(_ handler: @escaping Handler) {
        self.handler = handler
    }
    deinit { handler() }
    static func install(to object: AnyObject, _ handler: @escaping Handler) {
        objc_setAssociatedObject(
            object,
            &AssociatedKey.deallocHoocker,
            DeallocHoocker(handler),
            .OBJC_ASSOCIATION_RETAIN_NONATOMIC
        )
    }
}

extension NSMutableString {
    static func makeSafeString(_ inputed: NSMutableString) -> SafeString {
        let encoding = String.Encoding.utf8.rawValue
        
        let bufferSize = inputed.maximumLengthOfBytes(using: encoding) + 1
        let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: bufferSize)
        inputed.getCString(buffer, maxLength: bufferSize, encoding: encoding)
        let newString = NSMutableString(
            bytesNoCopy: buffer,
            length: strlen(buffer),
            encoding: encoding,
            freeWhenDone: false
        ) ?? NSMutableString()
        
        DeallocHoocker.install(to: newString, {
            memset(buffer, 0, bufferSize)
            buffer.deallocate()
        })
        
        return newString
    }
}

 

 

 

 

 

 

참조자료: 

https://toss.im/slash-21/sessions/3-6

 

메모리에 남지 않는 문자열

메모리에 남지 않는 문자열을 통해 민감한 정보를 안전하게 사용하는 방법을 공유합니다.

toss.im

https://medium.com/@jungkim/%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%ED%83%80%EC%9E%85%EB%B3%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%B6%84%EC%84%9D-%EC%8B%A4%ED%97%98-4d89e1436fee

 

스위프트 타입별 메모리 분석 실험

struct와 class 가 메모리 영역을 어디를 사용하는지 분석한 실험 결과

medium.com

 

 

소스: https://github.com/MyiOSPlayground/SecureTextField

 

 

 

 

 

 

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함