Android

[Android/Java] EncryptedFile에 대해 알아보자!

윤정e다 2022. 3. 20. 15:22

레퍼런스

https://developer.android.com/reference/androidx/security/crypto/EncryptedFile

 

EncryptedFile  |  Android Developers

androidx.car.app.managers

developer.android.com

https://developer.android.com/topic/security/best-practices?hl=ko#safe-data
 

앱 보안 권장사항  |  Android 개발자  |  Android Developers

앱 보안 권장사항 앱 보안을 강화하여 사용자의 신뢰와 기기 무결성을 유지할 수 있습니다. 이 페이지에서는 앱 보안에 유의미하고 긍정적인 영향을 미치는 몇 가지 권장사항을 설명합니다. 보

developer.android.com

https://github.com/RenZhongrui/android-learn/blob/336c22b478f6b1e1e82c224f7ef3cbd894b4ba4e/021-android-jetpack-security/app/src/main/java/com/learn/jetpack/security/JetpackSecurityUtil.java

 

GitHub - RenZhongrui/android-learn: 学习Android相关技术

学习Android相关技术. Contribute to RenZhongrui/android-learn development by creating an account on GitHub.

github.com

 

 

EncryptedFile 이란??

우선 나는 노출되면 안되는 데이터를 앱 내부 저장소에 안전하게 저장하기 위해 문서를 뒤적거리다가 발견했다.

안드로이드 공식 문서에는 >> 저장하는 데이터가 특히 민감하거나 비공개인 경우 File 객체 대신 EncryptedFile 객체(보안 라이브러리에서 사용 가능)를 사용하는 것을 고려해 보세요 << 라고 적혀있었다!

 

Encrypt == 암호화하다.

AndroidX에서 Security 라이브러리에 포함되어 있는 클래스로 즉, 데이터를 저장할 때 암호화해서 저장해주는 역할을 한다!! 

 

 

내가 직접 해보진 않았지만 여러 블로그들을 참고해서 보니 "안드로이드는 루팅이 매우 손쉬운 운영체제기 때문에 설령 release 모드로 빌드한 앱이라 하더라도 adb 명령을 이용해 모두 접근할 수 있다"고 한다. EncryptedFile을 이용하여 암호화할 파일의 대상을 정의하며 암호화 방법은 AES256_GCM_HKDF_4KB을 사용하여 데이터를 안전하게 저장할 수 있다.

 

 

우선 나는 encryptedFile 예제 코드를 완성시키기까지 매우매우 고난의 역경이었다.. java의 파일 입출력을 대학교 2학년 이후로 처음 해본지라 File I/O부터 공부했다. 뭐, File 입출력은 쉬우니 다른 블로그를 보면 바로 이해가 될거다.

(기본 파일입출력은 쉬웠는데  EncryptedFile에 적용할 땐 코드가 먹힐 때도 있고 안먹힐 때도 있고.. 여튼.. EncryptedFile에서 좀 애먹었다!)

 

본론으로 넘어가서 EncryptedFile은 File 입출력에서 조금만 바꿔서 적용하면 되기 때문에 파일 입출력에 익숙한 사람이라면 금방 이해할 수 있다!!

 

 

build.gradle

 minSdk가 23 이상이어야 적용된다!! 바꿔주기~~

 

dependency도 추가해주기!

버전은 바뀔 수 있으니 여기서 한 번 확인해보길!

 

 

MasterKey 객체 생성

우선 안드로이드에서 암/복호화할 때 사용되는 MasterKey 객체를 생성해줘야 한다. 왜냐면,, 암호화해주는 핵심 역할을 하기 때문!!

 

각 File이나 SharedPreferences를 암/복호화 하는 과정에서 쓰이는 key들의 집합을 keyset이라 한다. 이 keyset은 SharedPreferences에 저장되고 모든 keyset들을 암호화하는 데 쓰이는 primary(master) key가 존재하며, master key는 Android keystore 에 저장된다.

 

MasterKey masterKey = new MasterKey.Builder(
                context,
                MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();

 

 

1. 데이터 암호화

File 객체와 EncryptedFile 객체를 생성한다. 

이때, EncryptedFile.Builder ( file, context, masterkey, fileEncryptionScheme) 는 deprecated 되었으니 주의하기!!!
인자 순서는 context, file 순서이다.

File encryptFile = new File(context.getFilesDir(), encryptPath);

if (encryptFile.exists()) {
	Log.d(TAG, "encryptFile is exist.");
	encryptFile.delete();
}


EncryptedFile encryptedFile = new EncryptedFile.Builder(
				context,
				encryptFile,
				masterKey,
				EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
				).build();

FileOutputStream fos = encryptedFile.openFileOutput();
fos.write(contents.getBytes()); //평문 암호화해서 EncryptedFile에 저장한다.


File file = new File(context.getFilesDir(), plainPath);
FileInputStream fis = new FileInputStream(file);
byte[] buff = new byte[length];
int len = 0;

while ((len = fis.read(buff)) != -1) {
	fos.write(buff, 0, len);
	fos.flush();
	Log.d(TAG, "file write complete!");
}

fis.close();
fos.close();

AES256_GCM_HKDF_4KB는 암호화 방식이다. (EncryptedFile에서는 이 암호화 방식만 사용하는 듯? 다른 암호화 방식도 적용되는지는 아직 잘 모른다. 안해봄 ㅎ 추후에 실험해보고 추가해서 글을 올리도록 하겠다! )

 

객체 생성까지 끝났으면 본인이 저장하고 싶은 데이터를 FileOutputStream으로 쓰면 알아서 저장된다. 이때, 암호화되어 저장되기 때문에 내부 파일을 확인해보면 다음과 같이 저장됨을 볼 수 있다. (절대 깨진게 아니다.. 난 처음에 한글이 깨져서 저장되는 줄 알고 몇시간동안 헤맸으나,, 당연히 암호화되어 저장되기 때문에 이렇게 보인다는 것을,, 그대로 저장되면 EncryptedFile을 사용하는 이유가 없지 않은가?? 멍청한 나.. 한참 삽질했다;;)

 

(보는 법 : View - Tool Windows - Device File Explorer 들어가서 app - data - data - packagename - files)

encrypt.txt

 

 

 

3. 데이터 복호화

당연히 EncryptedFile에 저장된 데이터를 복호화를 할 수 있어야겠죠?

MasterKey masterKey = new MasterKey.Builder(
                context,
                MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();


File encryptFile = new File(context.getFilesDir(), encryptPath);

EncryptedFile encryptedFile = new EncryptedFile.Builder(
				context, 
                encryptFile, 
                masterKey,
				EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
                ).build();


FileInputStream fis = encryptedFile.openFileInput();
byte[] buff = new byte[length];
int len = 0;

File file = new File(context.getFilesDir(), plainPath);
if (file.exists()) {
	file.delete();
}

FileOutputStream fos = new FileOutputStream(file);
while ((len = fis.read(buff)) != -1) {
	fos.write(buff, 0, len);
	fos.flush();
}
fis.close();
fos.close();

decrypt.txt

 

 

전체 코드

MainActivity.Java
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    EncryptedFileUtil util;
    Context context;

    final static String TAG = "MainActivity";
    final static String FILE_CONTENT = "EncryptedFile 예제 코드입니다.";
    final static String ENCRYPTE_FILE = "encrypt.txt";
    final static String DECRYPTE_FILE = "decrypt.txt";

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

        util = new EncryptedFileUtil();
        context=getApplicationContext();

        try {

            util.encryptFile(context, ENCRYPTE_FILE, DECRYPTE_FILE, FILE_CONTENT);
            util.decryptFile(context, ENCRYPTE_FILE, DECRYPTE_FILE);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG,"error : "+e.getMessage());
        }

    }
}

 

 

EncryptedFileUtil.Java
package com.example.myapplication;

import android.content.Context;
import android.util.Log;

import androidx.security.crypto.EncryptedFile;
import androidx.security.crypto.MasterKey;
import androidx.security.crypto.MasterKeys;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class EncryptedFileUtil {

    private static final int length = 2048;
    private static final String TAG = "EncryptedFileUtil";

    public static void encryptFile(Context context, String encryptPath, String plainPath, String contents) throws Exception {

        MasterKey masterKey = new MasterKey.Builder(
                context,
                MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();


        File encryptFile = new File(context.getFilesDir(), encryptPath);

        if (encryptFile.exists()) {
            Log.d("Main4 >> ", "encryptFile is exist.");
            encryptFile.delete();
        }


        EncryptedFile encryptedFile = new EncryptedFile.Builder(
                context,
                encryptFile,
                masterKey,
                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
        ).build();

        FileOutputStream fos = encryptedFile.openFileOutput();
        fos.write(contents.getBytes()); //평문 암호화해서 EncryptedFile에 저장한다.

        File file = new File(context.getFilesDir(), plainPath);
        FileInputStream fis = new FileInputStream(file);
        byte[] buff = new byte[length];
        int len = 0;
        while ((len = fis.read(buff)) != -1) {
            fos.write(buff, 0, len);
            fos.flush();
            Log.d(TAG, "file write complete!");
        }
        fis.close();
        fos.close();


    }

    public static void decryptFile(Context context, String encryptPath, String plainPath) throws Exception {

        MasterKey masterKey = new MasterKey.Builder(
                context,
                MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build();


        File encryptFile = new File(context.getFilesDir(), encryptPath);

        EncryptedFile encryptedFile = new EncryptedFile.Builder(context, encryptFile, masterKey,
                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build();


        FileInputStream fis = encryptedFile.openFileInput();
        byte[] buff = new byte[length];
        int len = 0;

        File file = new File(context.getFilesDir(), plainPath);
        if (file.exists()) {
            file.delete();
        }

        FileOutputStream fos = new FileOutputStream(file);
        while ((len = fis.read(buff)) != -1) {
            fos.write(buff, 0, len);
            fos.flush();
        }
        fis.close();
        fos.close();

    }
}