만약 내 API Key가 GitHub에 노출된다면..?!

01

GitHub에 오픈소스 안드로이드 프로젝트를 올릴 때 유의하실 점이 있습니다.
소스코드는 공개되더라도 비밀정보는 숨겨야한다는 점인데요. 🤫
잘못 공개될 경우 라이선스 위반이 되거나, 악용될 소지도 있고 심한 경우는 금전적 피해가 발생할 수 있습니다.
실제로 제 주변에서도 AWS 토큰이 잠깐 노출되었다가 약 2천만원 상당의 과금이 부과된 사례도 있었습니다. 😱




어떤 정보를 숨겨야 할까? 🔐

Android 프로젝트에서 숨겨야 할 것이 있다면 Sign Key와 비밀번호Token 또는 API Key 등이 있습니다.
이번 포스팅에서는 이런 비밀정보를 오픈소스로 노출되지 않도록 프로젝트를 구성하는 방법을 알아봅니다.

Firebase의 프로젝트 설정 파일 google-service.json처럼 파일 통째로 숨겨야 한다면 아예 .gitignore로 숨기는게 좋습니다. 👨‍🚒




🔑 Sign Key 숨기기

상용 안드로이드 프로젝트라면 반드시 서명을 해야만 하는데요,
이 경우 key 자체와 서명할 때 사용하는 비밀번호 모두 숨겨야 합니다.
보통 local.properties 를 이용해서 숨기는 것이 일반 적인데요
실제로 구글 공식문서에서도 이런 방법을 추천하고 있습니다.
(https://developer.android.com/studio/publish/app-signing#secure-shared-keystore)

가끔 private repository라고 방심하여
프로젝트 내에 sign key를 포함하고 비밀번호를 하드코딩 하는 경우가 있는데
만에 하나라도 노출되면 돌이킬수 없는 사고로 이어질 수 있습니다.

절대 노출 될 일이 없다구요?

Github OAuth token으로 서드파티 어플리케이션 연동 중 실수로 프로젝트가 노출되는 사례도 있으므로
절대로 방심은 금물입니다. 😈😈😈



Properties 설정

local.properties파일은 기본적으로 .gitignore에 등록되어 노출될 위험이 없습니다.
local.properties파일에 아래와 같이 key=value형태로 값을 할당합니다.

keystore=... 
key_alias=...
key_password=...
store_password=...

keystore에는 sign key의 경로, 나머지에는 각각에 해당하는 서명 정보를 입력합니다.



build.gradle

이제 local.properties파일로부터 필요한 정보들을 읽어올 차례 입니다.
signingConfigs에서 받아온 정보로 서명 정보를 구성하면 됩니다.


groovy 인 경우

android {
    ...중략...
    // 서명키 설정 --> local.properties 에서 서명키 정보 관리
    signingConfigs {
        Properties properties = new Properties()
        properties.load(new FileInputStream("$project.rootDir/local.properties"))
        properties.each { prop ->
            project.ext.set(prop.key, prop.value)
        }
        release {
          storeFile file(properties['keystore'])
          keyAlias properties['key_alias']
          keyPassword properties['key_password']
          storePassword properties['store_password']
        }
    }
}


Kotlin-DSL 인 경우

android {
    ...중략...
    // 서명키 설정 --> local.properties 에서 서명키 정보 관리
    signingConfigs {
        val properties = Properties().apply {
            load(FileInputStream("${rootDir}/local.properties"))
        }
        create("release") {
            storeFile = file("${properties["storeFile"]}")
            keyAlias = "${properties["keyAlias"]}"
            keyPassword = "${properties["keyPassword"]}"
            storePassword = "${properties["storePassword"]}"   
        }
    }
}




🪙 API Key, Token 숨기기

만약 API 인증 등을 위해 필요한 토큰이나 API key를 사용하고 있다면
빌드 시에 자동으로 생성되는 BuildConfig.java 파일에 상수로 추가하도록 할 수 있습니다.
특히 저는 AWS나 GitHub token, Open API key 를 숨길 때 주로 사용합니다.



Properties 설정

위 서명키와 마찬가지로 여기서도 local.properties파일을 이용하여 값을 숨깁니다.
local.properties파일에 아래와 같이 key=value형태로 값을 할당합니다.

api_key=...



build.gradle

groovy 인 경우

android {
    ...중략...
    Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    def apiKey = properties.getProperty('api_key') ?: ""

    defaultConfig {
        ...중략...
        buildConfigField "String", "API_KEY", "\"$apiKey\""
    }
}


Kotlin-DSL 인 경우

android {
    ...중략...
    val properties = Properties().apply {
        load(FileInputStream("${rootDir}/local.properties"))
    }
    val apiKey = properties["api_key"] ?: ""

    defaultConfig {
        ...중략...
        buildConfigField("String", "API_KEY", "\"$apiKey\"")
    }
}


BuildConfig.java (자동생성)

build.gradle 설정 후 자동으로 생성되는 BuildConfig.java 파일에 아래와 같이 생성됩니다.
만약 gradle sync를 해도 바뀌지 않는다면 clean 후 assemble을 다시 해보시면 됩니다.

public final class BuildConfig {
  ...중략...
  // Field from default config.
  public static final String API_KEY = "...";
}



만약 String이 아니라면

가령 예를 들어 만약 정수라면 아래와 같이 작성하면 됩니다.

buildConfigField "int", "API_KEY", "$apiKey"

만약 객체라면 아래와 같이 처리할 수 있습니다.
이 경우에는 따로 import를 할 수 없으므로
클래스앞에 패키지명을 전부 붙여서 작성합니다.

buildConfigField "com.sample.pacakge.Foo", "Config", "com.sample.package.Foo(\"$apiKey\")"

요약

레포지토리에서 공개되지 않는 local.properties에 민감한 값을 넣고 build.gradle에서 그 값에 접근하는 방식으로
GitHub에 값을 노출시키지 않고도 안전하게 그 값을 사용할 수 있습니다.
혹시 나의 레포지토리 중 민감한 정보가 노출되고 있지는 않은지 확인해보시고
숨겨야할 게 있다면 한번 반드시 정리해보시기를 바랍니다.