코드 난독화(Code Obfuscation)는 실행 가능한 코드를 분석 및 리버스 엔지니어링이 어려운 형태로 변환하면서 애플리케이션의 완전한 기능을 유지하는 프로세스입니다. 난독화 방법에는 클래스와 메서드를 의미 없는 식별자로 이름 변경, 제어 흐름 난독화, 문자열 상수 암호화가 포함됩니다. Android Developers(2025)에 따르면, 난독화는 프로덕션 버전 빌드의 표준 단계입니다. Code Obfuscation은 지적 재산권 도용 및 애플리케이션의 취약점 발견을 어렵게 만듭니다.
주요 내용
코드 난독화(라틴어 obfuscare — 어둡게 하다, 혼란시키다에서 유래)는 애플리케이션의 소스 또는 중간 코드를 인간이나 자동 디컴파일 도구에 의한 분석을 최대한 방해하는 형태로 의도적으로 변환하는 것입니다. 난독화의 주요 요구 사항: 변환 후에도 프로그램은 원래 버전과 완전한 기능적 동등성을 유지해야 합니다.
난독화의 필요성은 중간 표현(JVM 바이트코드, .NET IL, JavaScript)을 가진 언어의 인기가 높아지면서 생겨났습니다. 이러한 언어는 기계 코드가 아닌 중간 바이트코드로 컴파일되며, 이는 쉽게 다시 읽을 수 있는 소스 코드로 디컴파일될 수 있습니다. 예를 들어, Java 바이트코드는 JD-GUI 또는 CFR과 같은 도구로 실질적으로 정보 손실 없이 디컴파일될 수 있어 지적 재산이 취약해집니다.
모바일 개발에서 난독화는 프로덕션 버전 빌드의 필수 단계가 되었습니다. Android는 Java/Kotlin 코드에 ProGuard와 R8을 사용하고, iOS는 LLVM 컴파일러와 최적화 및 SwiftShield와 같은 추가 도구를 사용합니다. Flutter 애플리케이션도 빌드 시 --obfuscate 플래그를 통해 난독화할 수 있으며, 이 플래그는 Dart 식별자를 임의의 문자로 이름 변경합니다.
난독화 방법에는 여러 가지가 있으며, 여러 범주로 나뉩니다. 어휘 난독화 — 클래스, 메서드 및 필드를 짧은 무의미한 이름(a, b, c)으로 이름 변경. 구조적 난독화 — 제어 흐름 변경, 데드 코드 삽입, 상속 계층 비대화. 데이터 보호 — 문자열 상수 암호화, 숫자 리터럴 난독화, 배열 분할.
가장 일반적인 난독화 방법 — 클래스, 메서드 및 필드의 의미 있는 이름을 짧은 식별자로 대체합니다. 그 결과 UserAuthenticationService 클래스는 a 클래스가 되고, validateLoginCredentials 메서드는 a(Bundle) 메서드가 됩니다. 이는 프로그램 동작을 변경하지 않지만 디컴파일된 코드를 사실상 읽을 수 없게 만듭니다. 1000개 클래스의 프로젝트는 공유 식별자의 수백 문자로 압축될 수 있습니다.
중요한 제한 사항: 이름 변경은 공개 API — reflection, Binding(DataBinding, ViewBinding), 직렬화(Gson, Kotlinx Serialization) 및 JNI 함수를 통해 호출되는 메서드에 영향을 주지 않아야 합니다. 이러한 경우 ProGuard는 특정 클래스 및 메서드의 이름 변경을 명시적으로 금지하는 -keep 규칙을 사용합니다.
제어 흐름 난독화(CFO)는 결과를 변경하지 않고 프로그램 구조를 변경하는 방법입니다. 컴파일러는 항상 동일하게 실행되는 가짜 조건 분기를 삽입하고, 동일한 의미를 가진 코드 블록을 복제하며, 선형 호출 시퀀스를 재귀적 또는 순환적 구조로 변환합니다. 이는 정적 코드 분석을 크게 복잡하게 만듭니다.
Obfuscator-LLVM과 같은 일부 도구는 LLVM IR 중간 표현 수준에서 고급 CFO를 구현합니다. 기본 블록을 작은 조각으로 분할하고, 섞은 다음 무조건 점프(goto)를 통해 연결합니다. 그 결과 제어 흐름 그래프는 미로처럼 되어 코드를 실행하지 않고는 재구성할 수 없습니다.
문자열 상수는 디컴파일된 코드의 가장 정보가 풍부한 요소입니다. API URL, API 키, SQL 쿼리, 오류 메시지 — 이 모든 것이 바이트코드에 일반 텍스트로 존재합니다. 문자열 암호화는 모든 문자열 상수를 암호화된 시퀀스로 대체하며, 첫 번째 액세스 시 런타임에 복호화됩니다.
// 난독화 전 소스 코드
String apiUrl = "https://api.example.com/v2/users";
String apiKey = "sk_live_abc123def456";
// 문자열 난독화 후(디컴파일된 보기)
String apiUrl = decrypt("x9K2pQ7mR4");
String apiKey = decrypt("z3F8nL1tV6");
// decrypt 메서드가 런타임에서 문자열을 복호화합니다
String decrypt(String encoded) {
return new String(xorDecode(base64Decode(encoded)), StandardCharsets.UTF_8);
}
ProGuard는 Android SDK에 통합된 Java/Kotlin 바이트코드의 축소, 최적화 및 난독화를 위한 고전적인 도구입니다. 2018년부터 Google은 R8 — ProGuard의 더 효율적인 대체품으로 동일한 기능을 더 빠르고 더 나은 최적화로 수행하는 것을 권장합니다. R8은 Android Gradle Plugin 버전 3.4.0부터 기본적으로 활성화되어 있습니다.
난독화 구성은 규칙 세트가 포함된 텍스트 파일인 ProGuard Rules를 통해 지정됩니다. 규칙은 어떤 클래스와 메서드를 유지할지(-keep), 이름을 변경할 수 있는지(-obfuscate), 제거해야 하는지(-dontwarn)를 정의합니다. proguard-rules.pro는 Android 프로젝트에서 규칙 파일의 표준 위치입니다.
// proguard-rules.pro — Android 기본 규칙
// reflection을 통해 사용되는 클래스 유지
-keep class com.example.models.** { *; }
// Gson을 통해 직렬화된 클래스 유지
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.** { *; }
// JNI 메서드 난독화 안 함
-keepclasseswithmembernames class * {
native <methods>;
}
// Activity(진입점) 유지
-keep class * extends android.app.Activity
minifyEnabled와 난독화의 차이를 이해하는 것이 중요합니다. build.gradle의 minifyEnabled true 플래그는 축소(사용되지 않는 코드 제거)를 활성화합니다. proguardFiles 플래그는 규칙 파일을 가리킵니다. 난독화를 활성화하려면 추가로 useProguard true를 지정하거나 minifyEnabled가 설정된 경우 기본적으로 난독화가 활성화되는 R8을 사용합니다.
난독화 중에 R8/ProGuard는 mapping.txt — 난독화된 이름과 원래 이름 간의 대응 파일을 생성합니다. 이 파일은 충돌 로그 분석에 중요합니다. 이 파일이 없으면 스택 추적에 a.b.c()와 같은 이름만 포함되어 읽을 수 없습니다. 매핑 파일은 각 릴리스 빌드에 대해 저장하고 Google Play Console 또는 Sentry에 업로드해야 합니다.
// build.gradle — Android 난독화 구성
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
), 'proguard-rules.pro'
}
}
}
iOS 생태계에서는 Swift 및 Objective-C용 LLVM 컴파일러가 리버스 엔지니어링을 부분적으로 어렵게 하는 여러 최적화를 수행하기 때문에 난독화가 Android만큼 일반적이지 않습니다. 그러나 iOS 애플리케이션의 완전한 난독화도 가능합니다. SwiftShield는 빌드 시 Swift 및 Objective-C 심볼을 임의의 문자열로 이름 변경하는 인기 있는 도구입니다.
SwiftShield는 컴파일 후 도구로 작동합니다. Mach-O 바이너리 파일을 분석하고 애플리케이션의 모든 심볼(클래스, 프로토콜, 메서드)을 난독화된 이름으로 대체합니다. 중요한 점은 SwiftShield가 시스템 라이브러리 심볼이나 공개 API를 건드리지 않아 App Store와의 호환성을 유지한다는 것입니다. Objective-C의 경우 추가 난독화 플래그와 함께 LLVM 컴파일러를 사용할 수 있습니다.
Obfuscator-LLVM은 추가 난독화 패스(제어 흐름 난독화, 문자열 암호화, 데드 코드 삽입)가 있는 LLVM 컴파일러의 포크입니다. C, C++, Objective-C 및 Swift를 지원하지만 컴파일러의 사용자 정의 버전 빌드가 필요합니다. 이 접근 방식이 가장 효과적이지만 CI/CD 파이프라인 설정 및 통합이 복잡합니다.
Flutter SDK는 릴리스 버전 빌드 시 --obfuscate 플래그를 통해 기본 난독화 지원을 제공합니다. 이 플래그는 ProGuard와 유사하게 임의의 문자를 사용하여 Dart 코드 식별자의 이름을 변경합니다. 추가 보호를 위해 Flutter 난독화를 R8(Android) 또는 SwiftShield(iOS)를 통한 네이티브 코드 난독화와 결합할 수 있습니다.
React Native 애플리케이션은 JavaScript 번들 수준에서 난독화됩니다. javascript-obfuscator(또는 JScrambler) 도구는 JS 코드를 변환합니다: 변수 이름 변경, 문자열 암호화, 더미 코드 삽입. 난독화 후 번들 크기는 50-100% 증가하지만 코드 분석이 훨씬 더 어려워집니다. 네이티브 래퍼 수준에서는 표준 Android 및 iOS 도구도 적용됩니다.
난독화는 지적 재산을 보호합니다 — 알고리즘과 비즈니스 로직의 복사는 디난독화에 필요한 시간 때문에 경제적으로 불리해집니다. 이는 비공식 스토어에서 애플리케이션 클론이 나타날 위험을 줄이고 이미지 처리 애플리케이션, 추천 시스템 또는 암호화폐 지갑의 고유 알고리즘을 보호합니다.
중요한 장점은 자동화된 분석으로부터의 보호입니다. 공격자가 취약점을 찾는 데 사용하는 많은 정적 분석 도구(데이터베이스 연결 문자열, API 키, 비밀 엔드포인트)는 난독화 후 효과를 잃습니다. 도구는 코드를 실행해야 하며(동적 분석), 이는 정적 분석보다 훨씬 어렵습니다.
첫 번째 제한 사항 — 난독화는 암호화가 아닙니다. 코드는 프로세서가 읽을 수 있는 상태로 유지되며 디버거(LLDB, Frida) 및 트레이서를 통해 런타임에 분석될 수 있습니다. 난독화는 리버스 엔지니어링을 복잡하게 할 뿐 공격자가 충분한 시간과 자원을 가지고 있으면 불가능하게 만들지는 않습니다.
두 번째 제한 사항 — 성능에 미치는 영향. 일부 난독화 방법(제어 흐름 난독화, 문자열 암호화)은 런타임에 오버헤드를 추가합니다. 공격적인 난독화는 시작 시간을 10-30%, 바이너리 파일 크기를 50-200%까지 증가시킬 수 있습니다. 따라서 방법 선택은 균형을 이루어야 합니다: 보호로 인해 애플리케이션이 허용할 수 없을 정도로 느려져서는 안 됩니다.
세 번째 제한 사항 — 도구 호환성. 매핑 파일이 구성되지 않은 경우 난독화는 충돌 보고 시스템(Firebase Crashlytics, Sentry)을 손상시킬 수 있습니다. Reflection 기반 라이브러리(Dagger/Hilt, Retrofit, Gson)는 명시적인 유지 규칙이 필요합니다. R8과 ProGuard는 정기적으로 업데이트되지만 구성 버그로 인해 사용 중인 코드가 제거될 수 있습니다.
자주 묻는 질문
난독화 — 읽을 수 있는 코드를 동일하게 작동하지만 분석하기 어려운 혼란스러운 코드로 변환하는 것입니다. 클래스와 메서드 이름이 의미 없는 문자 집합으로 대체됩니다.
build.gradle에서 릴리스 빌드에 대해 minifyEnabled true를 설정하고 proguardFiles를 지정합니다. R8은 기본적으로 활성화되어 있으며 축소, 최적화 및 난독화를 자동으로 수행합니다.
R8 — Google의 ProGuard를 대체하는 더 현대적이고 빠른 도구입니다. R8은 동일한 기능(축소, 최적화, 난독화)을 수행하지만 Android Gradle Plugin에 더 깊이 통합되어 더 효율적으로 작동합니다.
Mapping.txt — 난독화된 이름과 원래 클래스 및 메서드 이름 간의 대응 파일입니다. 충돌 로그 디난독화 및 릴리스 빌드 분석에 필요합니다.
-obfuscate-strings 플래그(Android) 또는 빌드 시 문자열 암호화 도구와 함께 ProGuard/R8을 사용합니다. iOS의 경우 상수 암호화 패스와 함께 SwiftShield 또는 Obfuscator-LLVM을 사용합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.