Android 보안 베스트 프랙티스
Android 보안 베스트 프랙티스
Android 앱의 보안을 강화하는 다양한 방법을 알아봅니다.
네트워크 보안
HTTPS 사용
모든 네트워크 통신은 HTTPS를 사용해야 합니다.
val url = URL("https://api.example.com/data")
val connection = url.openConnection() as HttpsURLConnection
connection.connect()
val inputStream = connection.inputStream
Network Security Config
Android 7.0(API 24)부터 지원되는 네트워크 보안 설정입니다.
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- 특정 도메인만 HTTP 허용 -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">legacy.example.com</domain>
</domain-config>
</network-security-config>
<!-- AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
SSL Pinning
중간자 공격(MITM)을 방지하기 위해 인증서를 고정합니다.
인증서 고정 대상
- 서버 인증서: 가장 확실하지만, 인증서 갱신 시 앱 업데이트 필요
- 중간 CA 인증서: 같은 CA에서 발급받으면 앱 업데이트 불필요
- Public Key: 인증서가 갱신되어도 Public Key는 유지될 수 있음
OkHttp에서 SSL Pinning
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
Network Security Config으로 SSL Pinning
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2025-12-31">
<pin digest="SHA-256">base64EncodedHash==</pin>
<!-- 백업 핀 -->
<pin digest="SHA-256">backupHash==</pin>
</pin-set>
</domain-config>
</network-security-config>
앱 무결성 검증
APK 서명 검증
앱이 변조되었는지 확인합니다.
fun hasValidSignature(context: Context): Boolean {
return try {
val pm = context.packageManager
val packageInfo = pm.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
val cert = packageInfo.signatures[0].toByteArray()
val digest = MessageDigest.getInstance("SHA1")
val hash = digest.digest(cert)
val calculated = hash.joinToString("") {
Integer.toString(it.toInt() and 0xff, 16).lowercase()
}
val expected = context.getString(R.string.expected_fingerprint)
calculated == expected
} catch (e: Exception) {
false
}
}
SafetyNet Attestation
루팅 기기 및 변조된 환경을 감지합니다.
SafetyNet.getClient(context)
.attest(nonce, apiKey)
.addOnSuccessListener { response ->
val jwsResult = response.jwsResult
// 서버에서 검증
}
.addOnFailureListener { e ->
// 실패 처리
}
데이터 보호
Provider 보안
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Signature 기반 권한
같은 서명을 가진 앱끼리만 데이터 공유:
<manifest>
<permission
android:name="com.example.MY_PERMISSION"
android:protectionLevel="signature" />
<application>
<provider
android:name=".MyProvider"
android:permission="com.example.MY_PERMISSION"
android:exported="true" />
</application>
</manifest>
코드 난독화
ProGuard/R8 설정
# proguard-rules.pro
# 모델 클래스 유지 (JSON 파싱용)
-keep class com.example.model.** { *; }
# 리플렉션 사용 클래스 유지
-keep class com.example.reflect.** { *; }
# 스택 트레이스 가독성
-keepattributes SourceFile,LineNumberTable
리소스 난독화
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
암호화
Android Keystore
민감한 키를 안전하게 저장합니다.
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
val keySpec = KeyGenParameterSpec.Builder(
"myKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.build()
keyGenerator.init(keySpec)
val secretKey = keyGenerator.generateKey()
EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
보안 체크리스트
- 네트워크
- HTTPS만 사용
- SSL Pinning 구현
- Network Security Config 설정
- 데이터
- 민감한 데이터 암호화
- SharedPreferences 대신 EncryptedSharedPreferences 사용
- 로그에 민감한 정보 제외
- 코드
- ProGuard/R8 난독화 적용
- 앱 서명 검증
- 디버그 빌드에서만 로깅
- 인증
- 토큰 안전하게 저장
- 세션 타임아웃 구현
- 생체 인증 고려
보안 도구
- Stetho: 디버그 시 DB 및 네트워크 검사 (디버그 빌드만)
- FindBugs: 보안 취약점 정적 분석
- OWASP ZAP: 동적 보안 테스트
결론
앱 보안은 여러 계층에서 적용해야 합니다. 네트워크 통신 암호화, 로컬 데이터 보호, 코드 난독화를 함께 적용하고, 정기적으로 보안 취약점을 점검하세요.
Comments