콘텐츠로 이동

[iOS] Swift로 홈키 길게 눌렀을 때 앱 화면 보호하는 방법

서론

모바일 앱 보안 관련해서 백그라운드 화면에 중요정보가 노출되면 취약하다고 한다.

중요정보

  • 고유식별정보(주민등록번호, 운전면허번호, 여권번호, 외국인 등록번호)
  • 비밀번호(로그인 비밀번호, 계좌 비밀번호, 공인인증서 비밀번호 등)
  • 신용정보(보안카드번호, OTP번호, 신용/체크 카드번호 등)
  • 기타 중요 정보

Multitasking Switcher

앱 실행 중 홈버튼을 한번 누르면 실행중이던 앱이 들어가고 홈 화면이 나온다.
홈 화면이던 앱 실행중이던 홈버튼을 연속으로 두 번 누르면
현재 백그라운드에 있는 앱 목록이 나오며 해당 앱의 마지막 화면이 나타나게 된다.
이 기능을 Multitasking Switcher라고 한다.

보안 설정

앱 생명 주기는 애플 개발자 페이지에 설명이 나와있다.
아이폰은 앱이 백그라운드에 갈 때마다 Screenshot을 저장하여
해당 이미지를 Multitasking Switcher에서 보여준다.
직접적으로 해당 스냅샷을 지정하거나 보안 옵션이 있는지 찾아보았는데 마땅히 없었다.

이미지 교체

직접적으로 이미지 지정이 안된다면 다른 방법을 사용해야 한다.
앱이 백그라운드로 갈 때 원하는 이미지 파일로 스냅샷이 찍히도록 꼼수를 썼다.

  1. 지정한 이미지파일로 UIImageView를 하나 생성한다.
  2. 앱이 백그라운드에 진입 직전 현재 화면 위에 이미지뷰를 덮어버린다.
  3. 다시 앱이 실행되면 해당 이미지뷰를 지운다.

이러면 백그라운드 진입과 동시에 찍히는 스냅샷은 화면 위에 덮어 쓴 이미지뷰가 찍힐 것이다.

AppDelegate

인터넷을 검색해보니 애플 개발자 페이지에 Responding to App Life-Cycle Events 항목이 있었다.
AppDelegate에 applicationDidEnterBackground와 applicationWillEnterForeground를 사용하면 될 것 같다.

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: [
        UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        print("Background!")
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        print("Foreground!")
    }

}

앱에서 홈버튼을 누르면 Background!라고 콘솔에 찍힐 것이고 다시 실행하면 Foreground!라고 찍힐 것이라 기대하며 위의 코드를 넣고 실행해봤다.
하지만 콘솔창엔 아무것도 찍히지 않았다.

SceneDelegate

한참을 삽질하다가 결국 알아냈다.
애플 개발자 페이지에 써있기를 iOS 13 이상부터는 SceneDelegate를 사용한댄다.
이런... 개발중인 내 폰의 버전은 iOS 13.3.1이었다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
        options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        print("Foreground!")
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        print("Background!")
    }

}

다시 한 번 앱에서 홈버튼을 누르면 Background!라고 콘솔에 찍힐 것이고 다시 실행하면 Foreground!라고 찍힐 것이라 기대하며 위의 코드를 넣고 실행해봤다. 드디어 콘솔창에 찍혔다!

하지만 뭔가 애매했다.

  • 앱 실행 중 홈버튼을 한 번 눌러서 완전히 백그라운드로 돌아갔다가 다시 실행(O)
  • 앱 실행 중 홈버튼을 따닥 두 번 눌러서 Multitasking Switcher에서 진입 시(X)

또 뭐가 문제인지 찾다가 모든 이벤트를 다 테스트 해본 결과 sceneWillResignActive, sceneDidBecomeActive 이 두 함수가 홈버튼을 한 번 눌러서 백그라운드로 갔다가 복귀하던, 홈버튼을 두 번 눌러서 바로 멀티태스킹을 하던 실행이 되는 것을 확인했다.

이제 스냅샷으로 찍혀 줄 이미지 파일을 asset에 SecureBackground로 등록한 뒤 해당 이미지를 표시해 줄 이미지뷰를 백그라운드 갈때는 덮어 쓰고 다시 실행될때는 없애는 코드를 넣고 실행했다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var imageView: UIImageView?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
        options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneWillResignActive(_ scene: UIScene) {
        imageView = UIImageView(frame: window!.frame)
        imageView?.image = UIImage(named: "SecureBackground")
        window?.addSubview(imageView!)
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        if imageView != nil {
            imageView?.removeFromSuperview()
            imageView = nil
        }
    }

}

성공이다.

SceneDelegate 제거

그런데 위 코드는 iOS 13 미만 버전대 기기에서는 또 작동을 안했다.
다시 AppDelegate에서 코드를 추가했더니 작동했다.
iOS 13보다 낮은 버전에서는 AppDelegate로 이벤트를 핸들링하기 때문에 똑같은 코드를 두번 넣어야 되게 생겼다.
어차피 나는 Scene을 사용하지 않아도 무방하기 때문에 아예 SceneDelegate를 삭제하고 설정하면 AppDelegate에서 해결되는게 아닌가 싶었다.

  • info.plist에서 Application Scene Manifest 제거
  • AppDelegate.swift에서 아래의 UISceneSession Lifecycle 관련 코드들 주석처리나 삭제
// AppDelegate.swift

    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting 
        connectingSceneSession: UISceneSession, options: 
        UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration",
         sessionRole: connectingSceneSession.role)
    }
    func application(_ application: UIApplication, didDiscardSceneSessions 
        sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after  application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }
  • SceneDelegate에 삽입했던 코드를 AppDelegate로 이동
  • 어차피 이제는 안쓸 SceneDelegate.swift 제거

이렇게 하고 실행해보니 똑같은 코드 두 번 안쓰고 잘됐다.

결론

Swift에서 백그라운드 스냅샷에 중요 정보 노출 방지하는 방법

임의의 이미지 뷰를 생성하여 앱이 백그라운드 진입할 때 위에 덮어쓰고 다시 돌아올 때 제거한다.

즉 App Delegate의 경우 AppDelegate.swift에 아래 코드 추가

var imageView: UIImageView?

func applicationWillResignActive(_ application: UIApplication) {
    imageView = UIImageView(frame: window!.frame)
    imageView?.image = UIImage(named: [넣을 이미지])
    window?.addSubview(imageView!)
}

func applicationDidBecomeActive(_ application: UIApplication) {
    if imageView != nil {
        imageView?.removeFromSuperview()
        imageView = nil
    }
}

주의

iOS 13부터 window의 개념이 scene으로 대체되고 하나의 앱에서 여러개의 scene을 가질 수 있게 된다. SceneDelegate을 통해 UILifeCycle을 관리하게 되므로 개발 시 고려해야 한다.

끝.


마지막 업데이트: 2022-09-20

댓글