모바일 앱에서의 Secure Storage: 정의, 방법 및 구현

저자: IT Sectr 게시일: 2026-04-04 읽는 시간: 9 분

Secure Storage는 기기에서 기밀 데이터(토큰, 암호화 키, 결제 정보, 개인정보)를 보호하기 위한 방법과 기술의 집합입니다. OWASP Mobile Top 10 (2024)에 따르면, 안전하지 않은 데이터 저장은 가장 위험한 세 가지 위험 중 하나입니다. 안전한 저장의 적절한 구현은 기기에 물리적으로 접근하더라도 데이터 유출을 방지합니다.

주요 포인트

  • Secure Storage — 다른 애플리케이션과 공격자의 접근을 방지하기 위해 기기에서 암호화와 데이터 격리 방법의 집합입니다.
  • Android Keystore — 하드웨어 수준(TEE)에서 키를 생성하고 보호하는 암호화 저장소입니다.
  • iOS Keychain — Security Framework를 통해 OS 수준에서 암호화되며 접근할 수 있는 비밀 정보 저장용 안전 데이터베이스입니다.
  • EncryptedSharedPreferences — AES-256을 사용하여 키-값 쌍을 암호화하는 Android Jetpack 라이브러리입니다.
  • Data Protection API — 기기 잠금 상태에 연결된 보호 클래스에 기반하여 파일을 암호화하는 iOS 메커니즘입니다.

Secure Storage란 무엇인가요?

Secure Storage는 모바일 애플리케이션의 기밀 데이터를 다른 애플리케이션, 맬웨어, 그리고 기기에 물리적으로 접근할 수 있는 공격자로부터 접근할 수 없도록 저장하는 실무입니다. 일반적인 저장과 달리, Secure Storage는 암호화, 격리 그리고 하드웨어 보호를 사용합니다.

모든 데이터가 Secure Storage를 필요로 하는 것은 아닙니다: 프로필 이미지나 뉴스 캐시는 일반 파일 시스템에 저장할 수 있습니다. 그러나 암호화 키, 인증 토큰, 결제 데이터, 개인 키 그리고 생체 인식 템플릿은 반드시 보호되어야 합니다. Google Security Blog (2025)에 따르면, 모바일 애플리케이션의 67% 취약점이 비밀 정보를 평문으로 저장하는 것과 관련됩니다.

각 모바일 플랫폼은 자체 Secure Storage 메커니즘을 제공합니다: Android — Keystore와 EncryptedSharedPreferences, iOS — Keychain과 Data Protection API. 이 메커니즘은 하드웨어 보안 모듈(TEE, Secure Enclave)과 통합되어 있으며, 탈옥이나 루팅 후에도 데이터를 읽을 수 없다는 것을 보장합니다.

올바른 Secure Storage 방법 선택은 데이터 유형, 사용 시나리오 그리고 성능 요구사항에 따라 달라집니다. 각 메커니즘의 아키텍처를 이해하면 개발자가 올바른 아키텍처적 결정을 내릴 수 있습니다.

Android에서 Secure Storage

Android 플랫폼은 하드웨어 키 저장부터 암호화된 SharedPreferences까지 여러 수준의 데이터 보호를 제공합니다. 선택은 데이터의 민감도와 성능 요구사항에 따라 달라집니다.

Android Keystore — 하드웨어 키 저장

Android Keystore는 하드웨어 보호를 지원하는 기기에서 격리된 실행 환경(TEE — Trusted Execution Environment)에서 키를 생성하고 저장하는 암호화 제공자입니다. 키는 결코 TEE를 벗어나지 않습니다: 암호화 작업은 운영체제조차 접근할 수 없는 보호된 영역 내에서 수행됩니다.

Android 9 (API 28)부터, Keystore는 StrongBox Keymaster를 지원합니다 — 자체 CPU, 진수 난수 생성기(TRNG) 그리고 보호된 메모리를 갖춘 전용 보안 칩입니다. StrongBox는 Common Criteria EAL 4+를 충족하도록 인증되었으며, Android에서 가장 높은 수준의 키 저장 보안입니다. StrongBox를 사용하려면 키 생성 시 inStrongBox() 플래그를 명시적으로 지정해야 합니다.

Keystore는 다음 알고리즘을 지원합니다: AES/GCM/NoPadding (256비트), EC (secp256r1, secp384r1), RSA (2048–4096비트), HMAC-SHA256. 모든 키는 setUserAuthenticationRequired(true)를 통해 생체 인증에 연결할 수 있습니다.

EncryptedSharedPreferences

EncryptedSharedPreferences는 AndroidX Security 패키지의 라이브러리로, SharedPreferences API를 통해 저장된 모든 데이터를 자동으로 암호화합니다. 값은 AES-256 GCM 키로 암호화되고, 키는 AES-256 SIV (합성 IV)로 암호화되어 키 이름에 대한 사전 공격을 방지합니다.

주 암호화 키는 Android Keystore에 저장되어 이중 보호를 제공합니다: Keystore는 마스터 키를 보호하고, EncryptedSharedPreferences는 데이터를 보호합니다. 일반적인 데이터(토큰, 설정)에 대한 읽기/쓰기 작업 당 암호화 성능은 5ms 미만으로, 이 라이브러리는 사용자 시나리오에 적합합니다.

EncryptedSharedPreferences는 대용량 데이터(5MB 이상)에 대해서는 설계되지 않았습니다 — 필요한 경우 SQLCipher나 Room을 암호화와 함께 사용하세요.

SQLCipher — 암호화된 데이터베이스

SQLCipher는 SQLite의 확장으로, AES-256-CBC를 사용하여 전체 데이터베이스를 페이지별로 암호화합니다. 데이터베이스의 각 페이지는 PBKDF2를 통해 마스터 비밀번호에서 파생된 별도의 키로 암호화됩니다. SQLCipher는 데이터 크기에 따라 약 5–15%의 성능 오버헤드를 추가합니다.

Android와의 통합은 net.zetetic:android-database-sqlcipher 라이브러리를 통해 수행되며, 표준 SQLiteOpenHelper와 호환되는 API를 제공합니다. SQLCipher의 비밀번호는 코드나 SharedPreferences가 아닌 Keystore에 보관할 것을 권장합니다.

iOS에서 Secure Storage

iOS 플랫폼은 주 안전 저장소로 Keychain Services를, 그리고 OS 수준의 파일 암호화를 위해 Data Protection API를 제공합니다.

Keychain Services

Keychain은 iOS가 비밀번호, 암호화 키, 인증서 그리고 노트를 저장하는 암호화된 SQLite 데이터베이스입니다. 각 Keychain 항목(SecItem)은 기기 고유의 하드웨어 키를 사용하여 암호화된 형태로 저장됩니다. 항목에 대한 접근은 ACL(접근 제어 목록)을 통해 제어되며, 생체 인증(Face ID, Touch ID) 또는 패스코드가 필요할 수 있습니다.

Keychain은 데이터에 접근할 수 있는 시점을 결정하는 보호 클래스를 지원합니다: kSecAttrAccessibleWhenUnlockedThisDeviceOnly — 기기가 잠금 해제된 경우에만 데이터에 접근할 수 있으며 백업 중 전송되지 않습니다. 이 클래스는 대부분의 인증 토큰 저장 시나리오에 권장됩니다.

iOS 15+에서는 Security Framework가 Secure Enclave를 통한 하드웨어 키 지원을 제공합니다 — 암호화 작업을 처리하고 개인 키를 격리된 메모리에 보관하는 전용 Apple 프로세서입니다. Secure Enclave는 칩에서 추출할 수 없는 키를 생성하기 위해 ECDSA (secp256r1) 그리고 ECDH 알고리즘을 지원합니다.

Data Protection API

Data Protection은 기기 패스코드에 연결된 키를 사용하여 파일 시스템 수준(APFS)에서 각 파일을 암호화하는 iOS 메커니즘입니다. 개발자는 파일 생성 시 NSFileProtectionType 속성을 통해 보호 수준을 지정합니다: NSFileProtectionComplete — 기기가 잠금 해제된 경우에만 파일에 접근할 수 있습니다.

Data Protection은 패스코드가 설정된 경우 iOS 5+를 실행하는 모든 기기에서 자동으로 작동합니다. 암호화는 Apple의 Dedicated AES Engine을 통해 하드웨어 수준에서 수행되어 높은 성능을 보장합니다 — 암호화 지연은 사용자가 거의 인식하지 못합니다. 애플리케이션에서 보호를 사용하려면 FileManager를 통해 파일을 생성할 때 보호 속성을 설정하기만 하면 됩니다.

Data Protection은 키 저장을 위한 Keychain을 대체하지 않습니다 — 파일, Core Data 데이터베이스 그리고 다른 대용량 데이터를 암호화하는 데 사용됩니다. Keychain (키 용)과 Data Protection (파일 용)의 조합은 iOS에서 완전한 안전 저장 사이클을 제공합니다.

코드 예시: Android와 iOS에서 데이터 암호화

Android와 iOS의 내장 API를 사용한 Secure Storage의 실전 예를 살펴보겠습니다.

Kotlin에서 EncryptedSharedPreferences

예제는 Android Keystore의 마스터 키를 사용하여 EncryptedSharedPreferences를 초기화하는 것을 보여줍니다. 그 후의 모든 읽기 및 쓰기 작업은 자동으로 암호화되고 복호화됩니다.

kotlin
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

prefs.edit()
    .putString("auth_token", "eyJhbGciOiJIUzI1NiJ9...")
    .apply()

Swift에서 Keychain

예제는 Security Framework를 사용하여 iOS Keychain에 데이터를 저장하고 읽는 것을 보여줍니다. 코드는 최대 보호를 위해 kSecAttrAccessibleWhenUnlockedThisDeviceOnly를 사용합니다.

swift
import Security

func saveToKeychain(key: String, data: Data) {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)
    SecItemAdd(query as CFDictionary, nil)
}

func readFromKeychain(key: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result
    )
    return status == errSecSuccess ? result as? Data : nil
}

Kotlin에서 SQLCipher

Android Keystore에 저장된 비밀번호를 사용하여 SQLCipher를 통해 암호화된 SQLite 데이터베이스에 연결하는 예입니다.

kotlin
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteOpenHelper

class SecureDBHelper(context: Context) :
    SQLiteOpenHelper(context, "secure.db", null, 1) {

    private val password = getKeyFromKeystore()

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL("CREATE TABLE tokens (id INTEGER PRIMARY KEY, value TEXT)")
    }

    override fun onUpgrade(
        db: SQLiteDatabase, oldVersion: Int, newVersion: Int
    ) {
        onCreate(db)
    }
}

// 사용 방법: 열 때 비밀번호 전달
val helper = SecureDBHelper(context)
val db = helper.getWritableDatabase(password)

안전한 데이터 저장 권장사항

Secure Storage의 올바른 사용은 일반적인 개발자 실수를 방지하는 몇 가지 기본 원칙을 따르도록 요구합니다.

데이터 분류를 정의하세요: 어떤 데이터가 하드웨어 보호(Keystore/Secure Enclave)를 필요로 하고, 어떤 데이터가 OS 수준 암호화(EncryptedSharedPreferences/Data Protection)를 필요로 하며, 어떤 데이터가 일반 파일 시스템에 저장될 수 있는지 결정합니다. 인증 토큰, 개인 키 및 결제 데이터 — 하드웨어 수준만. 사용자 설정(테마, 언어) — EncryptedSharedPreferences로 충분합니다. 세션 데이터(임시 캐시)는 메모리나 임시 디렉토리에 저장할 수 있습니다.

코드에 비밀을 저장하지 마세요: 소스 코드의 API 키, 비밀번호 또는 시드 구문이 포함된 문자열은 심각한 보안 실수입니다. 어떤 역공학도 이 데이터를 즉시 노출할 것입니다. 키에는 Keystore를, 그리고 구성에는 서버 측 로딩(원격 구성) 시스템을 사용하세요.

중요 작업에는 생체 인증 바인딩을 사용하세요: Android Keystore와 iOS Keychain은 키를 생체 인증에 연결하는 것을 지원합니다. 키에 접근할 때마다 시스템이 Face ID, Touch ID 또는 Android 생체 인식(BiometricPrompt)을 요청합니다. 이를 통해 기기에 대한 완전한 제어 권한을 가진 경우에도 공격자가 소유자 없이 저장된 데이터를 사용할 수 없다는 것이 보장됩니다.

보안을 테스트하세요: 보안 분석 도구를 사용하세요 — 정적 분석에 MobSF (Mobile Security Framework), 런타임 테스트에 objection, 보호 우회에 Frida를 사용합니다. 루팅 또는 탈옥 후 데이터에 접근할 수 없는지 확인하세요. Android는 SafetyNet Attestation 또는 Play Integrity API를 통해 루트 접근을 확인할 수 있고, iOS는 Secure Enclave 무결성 확인을 통해 확인할 수 있습니다.

암호화 라이브러리를 정기적으로 업데이트하세요: 암호화 라이브러리의 취약점은 정기적으로 발견됩니다. AndroidX Security, SQLCipher 및 Keychain 래퍼의 CVE를 모니터링하세요. Dependabot 또는 Renovate를 통해 새 버전에 대한 자동 알림 시스템을 구현하세요.

Apple Security Research (2025)에 따르면, Secure Storage의 적절한 구현은 기기에서 데이터 도난을 목적으로 한 공격의 96%를 방지합니다. 나머지 4%는 물리적 접근 및 제로데이 공격으로, 이에 대해서는 생체 인증 바인딩이 효과적입니다.

자주 묻는 질문

Keychain과 Keystore의 차이점은 무엇인가요?

iOS Keychain은 ACL을 통한 접근 제어로 비밀번호, 키 및 인증서를 저장하는 암호화된 데이터베이스입니다. Android Keystore는 격리된 환경(TEE/StrongBox)에서 키를 생성하고 저장하며 개인 키 추출을 허용하지 않는 암호화 제공자입니다.

EncryptedSharedPreferences는 어떤 암호화 알고리즘을 사용하나요?

EncryptedSharedPreferences는 값 암호화에 AES-256 GCM을, 키 암호화에 AES-256 SIV를 사용합니다. 마스터 키는 Android Keystore에 저장되어 이중 보호를 제공합니다. 추가적으로 무결성 확인을 위해 HMAC-SHA256이 사용됩니다.

이미 HTTPS로 보호되는 데이터를 암호화해야 하나요?

네, HTTPS는 전송 채널에서만 데이터를 보호합니다. 기기에서는 복호화 후 데이터가 평문으로 저장됩니다. 공격자가 기기에 물리적으로 접근하거나 맬웨어를 설치하면 HTTPS는 저장된 데이터를 보호하지 못합니다. 항상 저장 수준에서 데이터를 암호화하세요.

Android 루팅 후 데이터를 어떻게 보호하나요?

setUnlockedDeviceRequired(true) 플래그를 사용하는 Android Keystore를 사용하세요. 이는 루팅된 기기에서 키 접근을 차단합니다. 추가적으로 Play Integrity API를 통해 무결성을 확인하고 기준값에서 벗어나면 저장소에서 모든 비밀을 삭제하세요.

iOS에서 토큰 저장에 UserDefaults를 사용할 수 있나요?

아니요, UserDefaults는 샌드박스 내 plist 파일에 데이터를 평문으로 저장합니다. 역공학 도구를 가진 애플리케이션(백업 또는 탈옥을 통해)이 토큰을 읽을 수 있습니다. iOS에서 비밀을 저장하는 유일한 안전한 곳은 Keychain뿐입니다.

요약

  • Secure Storage는 기기에 물리적 접근이 있더라도 데이터 유출을 방지하는 모바일 애플리케이션 보호의 필수 요소입니다.
  • Android Keystore StrongBox는 전용 보안 칩에서 하드웨어 키 저장을 제공합니다.
  • iOS Keychain 보호 클래스(WhenUnlockedThisDeviceOnly)는 Apple 플랫폼에서 비밀을 저장하는 표준입니다.
  • EncryptedSharedPreferences는 이중 암호화로 Android에서 설정과 토큰을 암호화하는 바로 사용 가능한 솔루션입니다.
  • SQLCipher는 페이지별 AES-256-CBC 암호화로 암호화된 데이터베이스를 위한 선택입니다.
  • Data Protection(iOS)과 SafetyNet/Play Integrity(Android)는 추가 파일 시스템 보호 수준입니다.
  • 올바른 데이터 분류와 생체 인증 바인딩은 Apple Security Research에 따르면 저장된 데이터에 대한 공격의 96%를 방지합니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기