네이티브 응용 개발 환경별로 안전한 저장소를 알아보자.

요약: 각 운영체제가 정의하는 Secure Storage API를 적극적으로 활용하자. iOS, macOS등 애플의 환경이면 Keychain, Android의 경우 KeyStore, Windows의 경우 CNG(Cryptography: Next Generation)가 있으나 구체적으로 파고들면 고민이 필요하다.

어떻게 정보를 보호할까

요약: 사용자 정보가 유출되지 않으려면 파일에 키를 저장하는 수준으로는 충분하지 않다

개발을 하다 보면 한번쯤 이런 생각을 해본 사람들이 있을 것이다.

“인증 토큰을 어떻게 저장해야 하지?” 라든가 “노출되면 안되는 사용자의 정보를 어디에다가 저장해야 하지?” 와 같은.

이런 개발 단 요구사항들의 공통점은 다음과 같이 요약할 수 있다:

  1. 인가되지 않은 읽기(혹은 더 광범위하게 수정을 포함해서 접근 자체)를 거부
  2. 인가되지 않은 수정이 발생했을 경우 이를 탐지

가장 쉽게 생각할 수 있는 것은 암호화이다. 실제로 암호화가 바로 이와 같은 목적을 달성하기 위해서 생긴 것이니. 키를 알 수 없는 외부 공격자는 정보를 읽어도 유의미한 정보를 읽어낼 수 없으며, 키를 모르는 상태에서 임의의 방법으로 정보를 수정했을 때는 복호화가 진행되지 않는다. 따라서 키를 안전하게 보관하는 경우 공격자로부터 중요한 정보를 안전하게 보호할 수 있다.

문제는 여기서 발생한다:

과연 어떻게 키를 안전하게 보관할 것인가?

메모리 단에 저장하는 경우에는 문제가 간단하다. 대부분의 운영체제는 메모리 Scope를 하드웨어의 도움을 받아 안전하게 보호한다. 물론 메모리 보호를 응용 단에서 추가로 해야 하는 많은 경우가 있지만, 이와 같은 개발을 직접 하는 것은 배보다 배꼽이 더 커지게 되므로(또 이를 보다 완전한 통제 하에 두기 위해서는 사실상 운영체제의 응용 동작 하위 레벨에서 작동하는 코드를 작성해야 하므로) 일반적인 경우에는 운영체제의 메모리 보호 자체를 신뢰하는 것 만으로 충분하다.

그런데 위에서 말한 인증 토큰과 같은 경우를 생각해 보자. 만약 매번 응용을 실행할 떄마다 사용자의 로그인 작업을 반복해야 한다면? 엔지니어의 고민은 손쉽게 해결되겠지만 사용자들은 불만을 터뜨릴 것이다. 이는 비즈니스를 운영하는 데 있어서는 치명적인 문제점이다.

그렇다면 메모리의 내용물이 유실되지 않도록 응용을 계속해서 켜 두는 것은 어떨까? 데스크톱 컴퓨터를 유저들이 지속적으로 켜 두도록 강제할 수도 없고, 더욱이 모바일 환경 운영체제들은 대개 이와 같은 작업을 허용하지 않는다. 운영체제 입장에서는 메모리를 좀먹는 암덩어리 같은 존재거니와, 메모리 내용물을 유지하기 위해서는 대개 전력 사용이 필수적이라는 것을 잊지 말자. 따라서 이는 현실성이 없는 안이다.

그렇다면 보다 현실적인 방법은, 이와 같은 정보들을 함부로 가져올 수 없는 장기 저장공간에 저장하는 것이다. 하지만 여기서 문제가 생긴다. 그런게 어디 있단 말이지?

Seruce Storage 만세

요약: 하드웨어의 기능을 사용해 돌아가는 보안 표준들은 충분히 안전하다. 쉬운 이해를 위해 애플의 구현을 가져왔다.

다행스럽게도, 구글의 안드로이드와 애플의 운영체제들은 이를 위한 표준들을 제공한다. 안드로이드의 경우 이를 Keystore라고 부르며, 애플의 경우 Keychain이라고 부른다.

안드로이드의 경우 운영체제는 표준을 제공할 뿐이지 실제 해당 표준을 하드웨어 벤더별로 구현하는 방법은 상이하므로, 하드웨어와 소프트웨어를 동시에 통제하는 애플의 경우를 먼저 보도록 하자.

우선 애플의 키 보안 모델은 다음과 같이 구현된다:

  1. 하드웨어 보안 무결성 검증: 프로세서의 실행 코드 검증 및 Secure Enclave로 대표
  2. 운영체제 자체의 메모리 보안
  3. 파일 권한 모델
  4. 어플리케이션 서명
  5. 암호화 가속

이 아래로는 귀찮으니 위에서 영어로 표기한 것들을 그냥 한글로 적도록 하겠다.

애플이 제공하는 키체인 API는 오로지 키를 저장하기 위해서 사용하는 데이터베이스를 운영한다. 해당 정보가 어디 저장되는지 궁금할 텐데, 애플도 세부적으로 공개하진 않았지만 기본적으로는 시큐어 인클레이브 안에도 비휘발성 저장소가 존재하며, 이 안에 모든 민감 정보를 저장할 수는 없기에 컴퓨터의 저장 공간도 활용한다. 하지만 해당 공간들에는 시큐어 인클레이브를 통하지 않고는 접근할 수가 없다. 또한 해당 데이터베이스는 응용과 사용자 단위로 접근할 수 있는 범위가 제한되어 있다. 따라서 기본적으로 정해진 앱과 사용자가 아니면 해당 정보들을 읽는 것도, 수정하는 것도 모두 불가능하다. 이 챈 사용자들이 자주 다루는 파일과는 아주 다른 계층에 저장되는 정보라는 뜻이다.

그렇다면 해당 하드웨어에 비인가 상태로 접근하려고 하면 어떤 일이 벌어질까? 당연하게도, 정해진 수를 넘어서 접근하면 해당 저장소가 모두 잠긴다. 시큐어 인클레이브에 브루트포싱을 시도하더라도 정해진 횟수 이상으로 키를 틀리면 킬스위치가 하드웨어 단에서 올라가니, 나노미터 단위의 정확도의 손을 가진게 아니라면 꿈꺠는 것이 좋다. 이는 삼성 Knox에서도 구현된 사항이다. 이를 해제하기 위해서는 시큐어 인클레이브를 초기화 해야 하는데, 그 과정에서 저장된 모든 정보는 증발한다. 정보를 털릴 바에야 모두가 함께 자살하겠다는 데이터 논개와 같은 태도다.

혹시라도 애플 디바이스의 다른 부품들로부터 분해하고 재조립해 다양한 공격 시도를 하는 것이 가능하지 않은가 싶은가? 이것도 꿈 꺠는 편이 좋다. 이럴 걸 예상하고 시큐어 인클레이브에는 하드웨어 무결성 검사가 있는데, 어려우니 간단히 말하면 애플이 인가한 하드웨어 식별자 조합이 아니면 동작을 거부한다는 소리다. 아이폰 사설 수리점에서 부품을 교체할 경우 경고가 뜨는 걸 본 적이 있을 텐데, 이 중에서도 보안 시스템에 핵심적인 부품들이 교체되면 디바이스 전체가 동작을 중단한다. 결국 시큐어 인클레이브가 잠겼다면 남은 방법은 오로지 전체 저장소를 초기화하는 것 뿐이다. 이때도 아이클라우드 분실 모드 정보 등은 증발하지 않기 때문에 디바이스가 탈취 되었을때 이를 재활성화 하는 것은 오로지 디바이스 단에서 분실 키를 입력해 시큐어 인클레이브 잠금모드를 해제하는 것 뿐이다.

운영체제의 추가타

요약: 운영체제와, 운영체제가 요구되는 하드웨어단 보안 피쳐는 충분히 안전하다. 잘 활용하자.

아예 상식을 넘어선 방법으로 해당 공간에 접근하는 것은 불가능하다는 것을 알았다. 그렇다면 공격자의 남은 시나리오에는 어떤 것이 있을까? 그것은 바로 운영체제 자체의 동작에 개입하거나, 원하는 키에 접근할 수 있는 앱의 로직을 수정하거나, 앱이 구동중인 경우 해당 앱의 메모리 공간에 침범하는 방법이 있겠다.

우선 메모리 공간 침범 문제는 간단히 해결된다. 프로세서와 운영체제가 기본적으로 인가되지 않은 메모리 수정을 방어해준다. 인가되지 않은 수정이 발생했을 때 앱은 꺼지거나 커널 패닉이 일어난다. 심각할 경우에는 마찬가지로 시큐어 인클레이브 락이 걸린다. 운영체제의 동작에 개입하는 것은 앱의 메모리 보호에 적용되는 소프트웨어-하드웨어 조치에 좀 더 많은 장치가 걸리게 되므로 물론 더 어렵다.

그렇다면 앱을 수정하는 것은 어떨까? 키를 키체인 API에서 가져온 다음 메모리 어딘가에 저장해서 사용할테니 말이다. 거기에 원격으로 키를 전송하거나 디바이스 내 어딘가에 저장하는 코드를 심을 수 있지 않을까? 안타깝게도 이는 앱 사이닝에 의해 막힌다. 인가되지 않은 앱 수정이 일어난다 해도, 앱스토어를 통해 출시된 모든 앱에는 해당 앱이 개발자에 의해 작성된 이후에 수정된 것이 아닌지 검증하는 값들이 달리는데, 이 키는 개발자와 애플이 소유하고 있기 때문에 해커가 해당 키를 알아내지 않는 한 마음대로 앱 로직을 수정한다면 운영체제가 해당 앱 전체의 실행 자체를 거부한다. 앱 스토어가 아닌 곳에서 앱을 다운받아 실행하기 위해서는 탈옥이 필요했던 대표적인 이유다. 탈옥한 iOS는 바로 이 부분이 비활성화되거나 완화되기 떄문이다.

다른 회사들의 구현

요약: 윈도우도, 안드로이드도 애플의 정보 보호에 대한 충분히 안전한 대체재들이 존재한다. 다만 윈도우의 경우 앱 변조에 유의하자.

안드로이드에서 구현된 키스토어도 기본적으로 위와 비슷한 동작들을 하는데, 단지 제조사별로 구체적인 펌웨어에서의 소프트웨어적인 구현, 해당 사항들을 구현하기 위한 하드웨어 구성요소들이 상이할 뿐이다. 대표적인 게 삼성의 녹스인데, Secure Enclave와 유사하게 하드웨어 무결성 검사, 킬스위치, 암복호화 가속 등이 달려 있다.

윈도우의 경우에는 조금 어렵다. 레거시를 위해서 기본적으로 이전부터 쓰던 방법에는 Protected storage(서버 2003, XP 이후) 나 DPAPI(Data Protection API, 윈도우 2000 이후) 등이 있는데 위 운영체제들과는 달리 정보들이 단순한 파일로 저장되고, 비밀번호로 유래되는 마스터 키 하나로 전체 정보를 해독할 수 있으며, 앱별로 접근을 제한하는 방법이 없기 때문에 위에 비해서는 안정성이 퇴색되는 감이 있다. 이건 마소가 멍청하거나 보안에 무신경해서 그런 게 아니라 윈도우의 레거시 역사가 너무 길기 때문에 현대화된 보안 정책이 수립되기 전에 만들어진 앱이나 하드웨어 환경들을 버릴 수가 없기 떄문이다.

일단 API 자체는 윈도우의 Cryptography Next Generation API를 사용할 수 있으며, 저장소로 다행히도 TPM이 설치된 경우에는 이를 해당 API 구동에 사용한다. 또한 앱 로직과 에셋을 보호하기 위해서는 윈도우 8 이후로 도입된 패키징을 사용해 배포하거나, 기존 win32 환경에서 개발된 로직에 Windows appcontainer를 사용하면 비슷한 수준의 보안 이점을 누릴 수 있다. 윈도우 스토어를 통해 설치된 앱들은 외부에서 접근이 차단된 C:\Program Files\WindowsApps 디렉터리 안에 저장되는데, 기본적으로 외부에서 접근할 수 없다. 또한 앱 간에도 상호간의 파일에 마음대로 접근할 수 없으며, 가상화된 샌드박스 안에서 동작한다(VM 수준은 아니지만 보안 관점에서는 충분한 환경 격리를 제공한다). 이것이 불가한 경우에는 차선책으로, 애플의 앱 환경과 마찬가지로 로직 변조를 막기 위해서 인증서로 앱을 사이닝하면 되는데… 이게 비용이 좀 들어갈 거다(운영체제별 앱 스토어는 이 과정을 개발자 등록을 하면 자체 인증서로 싸게 대신해준다). 아무튼 앱 변조를 방어해낸 환경에서 CNG나 이에 상응하는 다른 기능들을 사용하면 충분히 안전하게 정보를 저장하고 읽을 수 있다. 핵심은 가능한 한 보안 하드웨어 표준을 사용하는 방향(대표적으로 TPM과 연관된)의 기능들을 활용하는 것이다. 윈도우는 개발 환경별로 이런 게 너무 많으니 알아서 찾아 쓰면 될 것 같다.

그럼에도 불구하고 다른 운영체제들에 비해 윈도우의 보안 정책이 미흡한 것은 사실이라 마소가 보안 쪽에서 무능한 회사인 것처럼 느껴질 수 있는데, 그렇지 않다. TPM은 중요한 키들을 저장하고, 암복호화를 가속하기 위해서 사용되며 SecureBoot는 하드웨어 무결성을 검증하는 역할을 하는데 모두 윈도우 8부터 지원한다. 게다가 마이크로소프트는 애플의 시큐어 인클레이브나 삼성의 녹스에 대응되는 Pluton이라고 하는 하드웨어를 자체적으로 설계했는데, 대표적으로 이게 탑재된 하드웨어가 바로 엑스박스 원 이후의 엑스박스이다. 그리고 이건 어느정도 우회법이 찾아진 시큐어 인클레이브나 삼성 녹스와도 차원이 다른 물건이다. 그걸 어떻게 아냐고? 엑스박스 360이야 파훼되었지만(하드웨어 보안도 없는 상태에서 이 파훼조차도 부분적이다. 마소의 소프트웨어 보안 짬밥이 나오는 부분이다), 플루톤이 탑재된 원 이후로는 해적판 앱들을 깔아서 실행하거나 간단한 정보라도 탈취한 애들이 없기 때문이다. 쉽게 말해서, 위에 적은 놈들보다 더 지독한 녀석이다. 마소가 바보가 아니라 맘만 먹으면 레거시 좀 희생하고 본인들이 통제하는 현대화된 보안 모델로 갈아버리는 건 전혀 불가능한 게 아니란 이야기이다. 단지 아직까지는 레거시 지원떄문에 참고 있을 뿐이다.

관련 문서

네이티브

크로스플랫폼 네이티브 앱용 패키지

안타깝게도 Avalonia나 Tauri를 위한 패키지는 찾지 못했다. 찾거나 알려주면 글에 추가해 두겠다.

마무리

쓰다 보니 글이 꽤나 어렵게 써졌다. 모두들 사용자 민감정보 보안에 신경쓰면서 개발하자.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다