더 안전한 데이터 사용

  Android Jetpack의 구성요소

보안 라이브러리는 저장 데이터 읽기 및 쓰기와 관련된 보안 권장사항의 구현과 키 생성 및 인증을 제공합니다.

라이브러리는 빌더 패턴을 사용하여 다음 보안 수준에 안전한 기본 설정을 제공합니다.

  • 뛰어난 암호화와 우수한 성능 사이에서 균형을 이루는 강력한 보안. 이 보안 수준은 은행 및 채팅 앱, 인증서 취소 확인을 실행하는 기업 앱과 같은 소비자 앱에 적합합니다.
  • 최대 보안. 이 보안 수준은 키 액세스를 제공하는 데 하드웨어 지원 키 저장소와 사용자 정보가 필요한 앱에 적합합니다.

이 가이드에서는 보안 라이브러리의 권장 보안 구성을 사용하는 방법과 파일 및 공유 환경설정에 저장되어 있는 암호화된 데이터를 읽고 쓰는 방법을 보여줍니다.

키 관리

보안 라이브러리는 키 관리를 위해 2파트 시스템을 사용합니다.

  • 하나 이상의 키를 포함하여 파일 또는 공유 환경설정 데이터를 암호화하는 키 세트. 키 세트 자체는 SharedPreferences에 저장됩니다.

  • 모든 키 세트를 암호화하는 기본(master) 키. 이 키는 Android 키 저장소 시스템을 사용하여 저장됩니다.

라이브러리에 포함된 클래스

보안 라이브러리에는 다음 클래스가 포함되어 더 안전한 저장 데이터를 제공합니다.

EncryptedFile

FileInputStreamFileOutputStream의 맞춤 구현을 제공하여 앱에서 더 안전하게 스트리밍 읽기 및 쓰기 작업을 할 수 있습니다.

파일 스트림에서 안전한 읽기 및 쓰기 작업을 제공하기 위해 보안 라이브러리는 '연결된 데이터와 함께 스트리밍 인증 암호화(AEAD)' 프리미티브를 사용합니다. GitHub의 Tink 라이브러리 문서에서 이 프리미티브를 자세히 알아보세요.

EncryptedSharedPreferences

SharedPreferences 클래스를 래핑하고 다음 두 가지 스키마 메서드를 사용하여 키와 값을 자동으로 암호화합니다.

  • 가 결정론적 암호화 알고리즘을 사용하여 암호화되므로 키를 암호화하고 올바르게 찾을 수 있습니다.
  • AES-256 GCM을 사용하여 암호화되며 비결정론적입니다.

다음 섹션에서는 이러한 클래스를 사용하여 파일 및 공유 환경설정으로 일반 작업을 실행하는 방법을 보여줍니다.

프로젝트에 라이브러리 포함

보안 라이브러리를 사용하려면 필요에 따라 앱 모듈의 build.gradle 파일에 다음 종속 항목을 추가합니다.

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.0.0"

    // For Identity Credential APIs
    implementation "androidx.security:security-identity-credential:1.0.0-alpha03"

     // For App Authentication APIs
    implementation "androidx.security:security-app-authenticator:1.0.0-alpha02"

    // For App Authentication API testing
    androidTestImplementation "androidx.security:security-app-authenticator:1.0.0-alpha01"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.0.0")

    // For Identity Credential APIs
    implementation("androidx.security:security-identity-credential:1.0.0-alpha03")

    // For App Authentication APIs
    implementation("androidx.security:security-app-authenticator:1.0.0-alpha02")

    // For App Authentication API testing
    androidTestImplementation("androidx.security:security-app-authenticator:1.0.0-alpha01")

}

파일 읽기

다음 코드 스니펫은 EncryptedFile을 사용하여 파일의 콘텐츠를 읽는 방법을 보여줍니다.

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

자바

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToRead),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

파일 쓰기

다음 코드 스니펫은 EncryptedFile을 사용하여 파일의 콘텐츠를 쓰는 방법을 보여줍니다.

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

자바

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

추가 보안이 필요한 사용 사례의 경우 다음 단계를 완료하세요.

  1. KeyGenParameterSpec.Builder 객체를 만들어 setUserAuthenticationRequired()true를 전달하고 setUserAuthenticationParameters()의 첫 번째 인수로 0보다 큰 값을 전달합니다.
  2. createConfirmDeviceCredentialIntent()를 사용하여 사용자 인증 정보를 입력하라는 메시지를 사용자에게 표시합니다. 키 사용을 위한 사용자 인증을 요청하는 방법을 자세히 알아보세요.

  3. onActivityResult()를 재정의하여 확인된 사용자 인증 정보 콜백을 가져옵니다.

공유 환경설정 수정

다음 코드 스니펫은 EncryptedSharedPreferences를 사용하여 사용자의 공유 환경설정을 수정하는 방법을 보여줍니다.

Kotlin

val sharedPrefsFile: String = FILE_NAME
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
        sharedPrefsFile,
        mainKeyAlias,
        applicationContext,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

with (sharedPreferences.edit()) {
    // Edit the user's shared preferences...
    apply()
}

자바

String sharedPrefsFile = FILE_NAME;
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
        sharedPrefsFile,
        mainKeyAlias,
        getApplicationContext(),
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
// Edit the user's shared preferences...
sharedPrefsEditor.apply();

Android 5.0 및 Android 5.1 기기 지원

보안 라이브러리 버전 1.1.0을 사용하면 Android 5.0(API 수준 21) 이상을 실행하는 기기를 지원할 수 있습니다. Android 5.0 및 Android 5.1(API 수준 22)에서는 Android 키 저장소를 사용하여 키 세트를 저장할 수 없습니다.

파일 읽기

다음 코드 스니펫은 보안 라이브러리 버전 1.1.0을 사용하여 파일의 콘텐츠를 읽는 EncryptedFile 사용 방법을 보여줍니다.

Kotlin

val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(applicationContext,
        File(DIRECTORY, fileToRead),
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

자바

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
        new File(DIRECTORY, fileToRead),
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

파일 쓰기

다음 코드 스니펫은 보안 라이브러리 버전 1.1.0을 사용하여 파일의 콘텐츠를 작성하는 EncryptedFile 사용 방법을 보여줍니다.

Kotlin

val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
val fileToWrite = File(DIRECTORY, "my_sensitive_data.txt")
val encryptedFile = EncryptedFile.Builder(applicationContext,
        fileToWrite,
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

// File cannot exist before using openFileOutput
if (fileToWrite.exists()) {
    fileToWrite.delete()
}

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8))
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

자바

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
File fileToWrite = new File(DIRECTORY, "my_sensitive_data.txt");
EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
        fileToWrite,
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

// File cannot exist before using openFileOutput
if (fileToWrite.exists()) {
    fileToWrite.delete();
}

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

공유 환경설정 수정

다음 코드 스니펫은 보안 라이브러리 버전 1.1.0을 사용하여 사용자의 공유 환경설정을 수정하는 EncryptedSharedPreferences 사용 방법을 보여줍니다.

Kotlin

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

val sharedPreferences = EncryptedSharedPreferences.create(
    applicationContext,
    FILE_NAME,
    mainKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

with (sharedPreferences.edit()) {
    // Edit the user's shared preferences...
    apply()
}

Java

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences
        .create(
            context,
            FILE_NAME,
            mainKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        );

SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
// Edit the user's shared preferences...
sharedPrefsEditor.apply();