[Android/Java] EncryptedFile에 대해 알아보자!
레퍼런스
https://developer.android.com/reference/androidx/security/crypto/EncryptedFile
EncryptedFile | Android Developers
androidx.car.app.managers
developer.android.com
앱 보안 권장사항 | Android 개발자 | Android Developers
앱 보안 권장사항 앱 보안을 강화하여 사용자의 신뢰와 기기 무결성을 유지할 수 있습니다. 이 페이지에서는 앱 보안에 유의미하고 긍정적인 영향을 미치는 몇 가지 권장사항을 설명합니다. 보
developer.android.com
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)
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();
전체 코드
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();
}
}