카테고리 보관물: Android

안드로이드에서 비교적 안전하게 키 사용하기

많은 앱이 상품을 팔거나 각종 서비스에 연동하는 경우에 필요한 아이디나 키를 저장해서 사용한다. 이 키들을 안전하게 사용하려면 추가적인 처리가 필요하다. 짧은 지식으로 안전함의 순위를 나열하면 아래와 같다.

1. 안전한 방법 : 암호화해서 저장
2. 비교적 안전한 방법 : 네이티브 영역에 저장
3. 안전하지 않은 방법 : 리소스 파일에 저장

우선, 위 기준에 따른 사용방법을 살펴보자.

1. 안전한 방법
1.1 대칭키를 사용해서 키를 암호화한다.
1.2 안드로이드 리소스 파일에 암호화된 키를 저장한다.
1.3 암호화된 키를 사용하기 전에 서버에서 대칭키를 받아서 복호화한다.

2. 비교적 안전한 방법
2.1 네이티브 코드에 키를 리턴하는 메서드를 구현한다.
2.2 네이티브 메서드를 호출해서 키를 받는다.

3. 안전하지 않은 방법
3.1 키를 리소스 파일에 저장한다.
3.2 저장된 키를 사용한다.

이 글에서는 현실적으로 서버를 사용할 수 없는 경우에, 비교적 안전한 방법을 채택하는 형태를 살펴본다.

기존 프로젝트의 main 폴더에 jni 폴더를 만들고 아래와 같은 파일을 만든다.

위 파일의 내용은 아래와 같다.

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := keys
LOCAL_SRC_FILES := keys.c

include $(BUILD_SHARED_LIBRARY)

Application.mk : 아래 코드로 모든 플랫폼에 해당하는 .so 를 만든다.

APP_ABI := all

Keys.c : 자바에서 호출할 메서드와 키를 정의한다.

#include 

JNIEXPORT jstring JNICALL
Java_net_sjava_keytest_MainActivity_getNativeKey1(JNIEnv *env, jobject instance) {
    return (*env)->  NewStringUTF(env, "First Key");
}

JNIEXPORT jstring JNICALL
Java_net_sjava_keytest_MainActivity_getNativeKey2(JNIEnv *env, jobject instance) {
    return (*env)->NewStringUTF(env, "Second Key");
}

이제 build.gradle에서 ndk를 포함시키는 코드를 추가한다.

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"
    defaultConfig {
        applicationId "net.sjava.keytest"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'
        }
    }
}

그리고, MainActivity.java 에서 아래와 같이 키를 가져오는지 확인한다.

package net.sjava.keytest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("keys");
    }

    public native String getNativeKey1();
    public native String getNativeKey2();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String key1 = getNativeKey1();
        String key2 = getNativeKey2();

        ((TextView)findViewById(R.id.textview)).setText("Key1-->"+key1+"\nKey2-->"+key2);
    }
}

이제, 위 코드를 실행하면 아래의 실행 결과를 볼 수 있다.

현실적으로 앱 크래킹을 완벽하게 막기는 불가능한 것 같다. 그래서 프로가드(Proguard)로 난독화 처리와, 앱에서 사용하는 키를 비교적 안전하게 관리해서 크래킹을 어렵게 만드는 게 현실적인 방법인 것 같다.

* 레퍼런스 : https://medium.com/@abhi007tyagi/storing-api-keys-using-android-ndk-6abb0adcadad

안드로이드 지원(support) 라이브러리 구글 메이븐 리파지토리

구글이 지원(support) 라이브러리 다운로드를 위해서 자체 메이븐 리파지토리를 구축해서 25.4.0 버전부터는 구글의 메이븐 리파지토리에서 다운로드 받을 수 있다. 주소는 maven.google.com 이다. 그래서 안드로이드 프로젝트 build.xml 파일에 아래와 같이 구글의 리파지토리도 추가한다.

allprojects {
  repositories {
  jcenter()
  maven { url "https://jitpack.io" }
  maven { url "https://maven.google.com" }
  }
}

https://maven.google.com에 접속하면 https://dl.google.com/dl/android/maven2/ 로 리다이렉트 된다. 구글 메이븐 리파지토리는 서버에서 리스팅 기능을 제공하지 않아서 호스팅 하는 전체 라이브러리를 볼 수 가 없다.

그래서 지원 라이브러리 중에 appcompat-v7 에 대해서 약간의 정보를 살펴봤다.

메타 데이터 정보
https://dl.google.com/dl/android/maven2/com/android/support/appcompat-v7/maven-metadata.xml

라이브러리 파일
https://dl.google.com/dl/android/maven2/com/android/support/appcompat-v7/25.4.0/appcompat-v7-25.4.0.aar

그리고, 구글 지원 라이브러리 종류는 아래에서 확인할 수 있고, 위의 패턴으로 개별 라이브러리를 확인할 수 있다.
https://developer.android.com/topic/libraries/support-library/packages.html

안드로이드 페브릭(Fabric) 그래들(Gradle) 플러그인 버전 지정하기

페브릭(Fabric)은 아주 유용한 기능을 모바일 플랫폼에 쉽게 적용할 수 있게 도와주는 서비스이다. Crashlytics도 페브릭에서 제공하는 크래시 리포트 서비스이다. Crashlytics을 안드로이드에서 사용하는 방법은 fabric.io/kits/android/crashlytics/install 에서 확인할 수 있다. 여기에서 아래의 아래의 코드를 추가하라고 한다.

buildscript {
repositories {
maven { url 'https://maven.fabric.io/public' }
}

dependencies {
// These docs use an open ended version so that our plugin
// can be updated quickly in response to Android tooling updates

// We recommend changing it to the latest version from our changelog:
// https://docs.fabric.io/android/changelog.html#fabric-gradle-plugin
classpath 'io.fabric.tools:gradle:1.+'
}
}

위 코드를 보면 알겠지만, 페브릭의 그래들 플러그인 버전이 ‘io.fabric.tools:gradle:1.+’로 선언되어 있다. 주석을 읽어보면 알겠지만, docs.fabric.io/android/changelog.html#fabric-gradle-plugin 에서 최신 버전으로 수정하라고 한다. 이 문서를 보면, 아래와 같이 최신 버전을 확인할 수 있고, 변경 내용도 확인할 수 있다.

2017년 6월 14일에 확인해 본 결과로 1.22.2 버전이 최신버전이다.

위 과정에 더불어, 페브릭 그래들 플러그인에 대한 메타 정보는 아래 URL에서 확인할 수 있다.
fabric-artifacts.s3.amazonaws.com/public/io/fabric/tools/gradle/maven-metadata.xml

위 이미지에서 최신 버전 릴리즈 시점이 2017년 05월 31일 이라는 것도 알 수 있다.

결론으로 app 모듈에서 build.gradle 파일의 맨 처음은 아래와 같은 모습이 될 것이다.

buildscript {
    repositories {
        jcenter()
        maven { url 'https://maven.fabric.io/public' }
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'

        classpath 'io.fabric.tools:gradle:1.22.2'
    }
}

앱(APK) 크기를 줄이는 또 다른 방법

앱 크기를 줄이는 팁으로 사용하지 않는 이미지, Proguard 사용 등의 여러 가지 방법이 있다. 이 방법들 모두는 결국 zip 포맷으로 패키징되는 파일을 개수나 크기를 줄여서 .apk 파일의 크기를 줄이는 것이다. 이 글에서는 .apk 파일이 포함하는 또 다른 파일인 텍스트 파일을 제거해서 앱의 크기를 줄이는 방법을 살펴보자. 텍스트 파일이라서 앱 크기에 큰 영향을 미치지는 않지만, 그래도 약간의 크기를 줄일 수 있는 또 다른 방법으로 알아두면 좋겠다.

이 방법은 안드로이드 스튜디오에서 빌드와 패키징을 담당하는 툴인 그레들의 빌드파일(build.gradle)에 packagingOptions{} 영역에 패키징시에 제외할 파일을 기술하는 것이다. 이 설정은 아래와 같이 동일 파일의 중복으로 인한 에러를 해결하는데 또한 사용한다.

Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/DEPENDENCIES

그래서, 위 에러를 해결하기 위한 최소의 설정은 다음과 같다.

android {
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
    }
}

컴파일한 앱의 크기를 확인해 보면, 크기가 9,154,124 바이트이다.

다음으로 어느 파일을 제외할 수 있는지 살펴보자. 안드로이드 스튜디오에서 Build > Analyze Apk 로 위의 파일을 선택하면 아래의 화면을 볼 수 있다.

위 화면에서 체크한 파일들을 앱 패키징시에 제외시키도록 packageOptions에 추가하자. 그리고 일반적으로 많이 사용하는 텍스트 파일도 역시 미리 포함시켜 넣어서 아래와 같이 설정한다.

android {
	packagingOptions {
	    exclude 'META-INF/DEPENDENCIES.txt'
	    exclude 'META-INF/DEPENDENCIES'
	    exclude 'META-INF/dependencies.txt'
	    exclude 'META-INF/LICENSE.txt'
	    exclude 'META-INF/LICENSE'
	    exclude 'META-INF/license.txt'
	    exclude 'META-INF/LGPL2.1'
	    exclude 'META-INF/ASL2.0'
	    exclude 'META-INF/NOTICE.txt'
	    exclude 'META-INF/NOTICE'
	    exclude 'META-INF/notice.txt'
	    exclude 'META-INF/MANIFEST.MF'
	    exclude 'META-INF/manifest.mf'
	    exclude 'META-INF/MANIFEST'
	    exclude 'META-INF/manifest'

	    exclude 'META-INF/CHANGES'
	    exclude 'META-INF/README'
	    exclude 'META-INF/NOTES.TXT'

	    exclude 'licenses/thoughtworks.TXT'
	    exclude 'licenses/extreme.indiana.edu.license.TXT'
	    exclude 'licenses/javolution.license.TXT'
	}
}

이제 다시 패키징한 파일을 살펴보자. 필요 없는 텍스트 파일들을 제외하고, 컴파일한 파일의 크기는 9,137,085 바이트이다.

이 과정으로 앱의 크기가 17,000 바이트를 줄일 수 있다. 이 과정으로 메가 바이트 단위로 앱의 크기를 줄이기는 힘들겠지만, 약간이나마 크기를 줄이는데 기여할 수 있다.

멀티 dex 사용하기

안드로이드(Android) 앱(APK) 파일은 DEX(Dalvik Executable) 파일 형식의 실행 가능 바이트코드 파일을 포함하고 있다. 개별 .dex파일은 사용할 수 있는 메서드의 총 개수가 65,536로 제한되어 있다. 그래서 이 제한을 보통 ’64K 참조 제한’이라고 한다.

안드로이드 스튜디오의 메뉴에서 Build > Analyze APK… 를 선택하면 .apk 파일을 구성하는 것들을 자세히 살펴볼 수 있고, 이 제한을 넘어서 더 많은 메서드등을 사용하는데는 2개 이상의 .dex 파일이 필요하다. 그래서 2개 이상의 .dex 파일을 생성하도록 앱 빌드 프로세스를 구성해야 하며, 이것을 multidex 구성이라고 한다. 아래 이미지를 보면, classes.dex와 classes2.dex파일로 .dex 파일이 2개 있는 것을 알 수 있다. 

안드로이드 앱에서 멀티 dex를 사용하는 방법은 3가지의 형태가 있을 수 있다. 첫번째가 Application 클래스를 사용하지 않는 경우, 두번째는 Application 클래스를 사용하는 경우, 그리고 마지막은 MultiDexApplication 클래스를 사용하는 경우이다.

멀티 dex를 사용하는데 필요한 과정의 기본은 build.gradle 파일에 multidex 라이브러리를 추가하고, multidexEnabled 옵션을 true로 설정하는 것이다.

android {
	defaultConfig {
		multiDexEnabled true
	}
}

dependencies {
	compile 'com.android.support:multidex:1.0.1'
}

이제 위 설정이 완료됐으면, 멀티 dex를 사용하는 3가지 형태에 대해서 살펴보자.

1. Application을 사용하지 않는 경우

이 경우에는 아래의 코드를 AndroidManifest.xml 파일에 아래처럼 Application을 설정해야 한다.



    
        ...
    

2. Application을 사용하는 경우

프로젝트의 AppApplication 클래스가 Application을 상속해서 구현하고 있다고 가정한다. 이 경우에는 아래와 같은 코드가 AndroidManifest.xml 파일에 설정해야 한다.



    
        ...
    

그리고, AppApplication 에 아래의 코드를 추가한다.

@Override
protected void attachBaseContext(Context base) {
	super.attachBaseContext(base);
	MultiDex.install(this);
}

3. MultiDexApplication을 사용하는 경우

이 경우가 가장 간단하게 사용할 수 있다. AndroidManifest.xml은 위 2.와 동일하게 설정한다. 그리고, 아래처럼 Application 클래스가 MultiDexApplication을 상속하면 된다.

public class AppApplication extends MultiDexApplication {
}

어떻게 MultidexApplication만 상속하면 돼는지, 이 클래스의 소스를 살펴보면 다음과 같다.

package android.support.multidex;

import android.app.Application;
import android.content.Context;
import android.support.multidex.MultiDex;

public class MultiDexApplication extends Application {
	public MultiDexApplication() { 
	}

	protected void attachBaseContext(Context base) {
		super.attachBaseContext(base);
		MultiDex.install(this);
	}
}

이 소스를 보면, Application에서 사용하는 코드를 제공하는 래퍼 클래스임을 알 수 있다. 결론으로, Application 클래스를 사용하는 것은 꽤 유용하므로, 개인적으로 3번째 방법을 사용해서 멀티 dex를 제공하는 것이 좋겠다.