안드로이드/Android

1 2 3 4 5

[Android] Keystore 보안

2020. 11. 17. 21:54

Keystore 보안


 

앱을 배포하기 위해 apk 또는 aab에 앱서명을 하게되면 build.gradle에

Keystore의 Alias, password, File경로가 나타나게 됩니다.

signingConfigs {
	release {
        	storeFile file('keystore 경로')
        	storePassword '비밀번호'
    		keyAlias 'key()'
        	keyPassword '비밀번호'
	}
}

 

이와 같이 keystore의 정보가 노출된 상태로 Github나 기타 사이트에 업로드되면 악용될 수 있습니다.

그러므로 별도의 keystore file을 만들어 보안을 강화하는 것을 권장합니다.

 

- 프로젝트 탭을 선택하신 후 루트디렉토리에다가 keystore.properties파일을 만들어줍니다.

 

- keystore.properties 작성

파일 내부에 keystore 값들을 작성해줍니다.

 

storeFile = 경로
storePassword = 비밀번호
keyAlias = 별칭
keyPassword = 비밀번호

 

- build.gradle 작성

상단에 apply와 android 사이에 keystore.properties의 값들을 불러오는 작업을 해줍니다.

// Keystore Security
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

 

- signingConfigs 내부 수정

기존의 값들을 keystoreProperties['keyPassword']와 같이 바꿔주시면 됩니다.

signingConfigs {
	debug {
		storeFile file(keystoreProperties['storeFile'])
        	storePassword keystoreProperties['storePassword']
        	keyAlias keystoreProperties['keyAlias']
        	keyPassword keystoreProperties['keyPassword']
	}
}

 

 

 

이제 깃허브나 기타 사이트에 업로드하실 때 keystore.properties파일을 제외하고 업로드하시면 됩니다 :)



Android App Bundle 만들기


앱 제작을 완료한 후 apk파일이나 aab (Android App Bundle) 파일을 만드는 방법입니다.

 

- Build에서 Generate Signed Bundle / APK 클릭.

 

- 만들고자하는 확장자명 선택하시고 Next (aab 또는 apk 선택)

   aab는 apk보다 경량화된 앱을 제공합니다. 

 

- 처음 만드시는 분은 "Create new..."를 눌러줍니다.

- 빈칸을 입력해주시면 됩니다.    OK & Next

   ( ※ 주의: 키파일이랑 비밀번호를 분실하시면 추후에 앱 업데이트가 불가능합니다.)

- Finish를 눌러주시면 aab파일이 생성됩니다.

   apk파일도 같은 방법으로 생성하실 수 있습니다.

 



퍼미션 권한체크


테드퍼미션 https://github.com/ParkSangGwon/TedPermission

 

ParkSangGwon/TedPermission

Easy check permission library for Android Marshmallow - ParkSangGwon/TedPermission

github.com

박상권님 블로그 주소 : gun0912.tistory.com/

 

 

박상권이라는 분이 직접 만드신 라이브러리입니다. 권한체크방식을 라이브러리를 통해서 간단하게 줄일 수 있게 해주셨습니다.

감사합니다 (큰절)

 

사용방법을 알아보겠습니다.

우선 사용하기 위해서 gradle을 추가해줍니다.

implementation 'gun0912.ted:tedpermission:2.2.3'

 

PermissionListener로 접근 허용/거부 시 실행할 코드 작성가능하고,

TedPermission클래스를 이용해서 권한체크에 필요한 설정을 하실 수 있습니다.

PermissionListener permissionListener = new PermissionListener() {
	@Override
    public void onPermissionGranted() {
		// 접근허용 시 실행할 코드
	}

    @Override
	public void onPermissionDenied(List<String> deniedPermissions) {
		// 접근거부 시 실행할 코드
	}
};

TedPermission.with(this)
	.setPermissionListener(permissionListener)
    .setDeniedMessage("접근 거부하셨습니다.\n[설정] - [권한]에서 권한을 허용해주세요.")
    .setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
    .check();

- setRationalMessage() : 권한체크 하기전 Dialog 메시지 설정

- setDeniedMessage() : 권한 거부했을 때 띄울 Dialog 메시지 설정

- setDeniedCloseButtonText() : 권한 거부 시 뜨는 Dialog의 [닫기]버튼 텍스트 설정

- setGotoSettingButtonText() : 권한 거부 시 [설정]버튼 텍스트 설정

- setGotoSettiongButton() : 권한 거부 시 [설정]버튼을 보여줄지 설정 (true / false)

 

※주의

TedPermission 클래스에서 setPermissions를 여러개 추가하시면 안됩니다.

저장공간에 대한 permission의 경우 .

setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)

와 같이 적어주셔야 됩니다.

저처럼 setPermissions 두번 작성했다가 못찾으면 고통받습니다.

.setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)

.setPermissions(Manifest.permission.READ_EXTERNAL_STORAGE)

 

 



다른 액티비티에 접근하기


1. Context 생성

가져오려고하는 Activity에서 context를 선언해주고, onCreate 내부에 this로 할당시켜줍니다.

public class MainActivity extends AppCompatActivity {
 
     public static Context context;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
 
          context = this;
     }
}

 

2. 접근

사용하려는 Activity에서 아래와 같이 접근하시면 됩니다.

메서드 또는 변수에 접근하실 수 있습니다.

((MainActivity) MainActivity.context);

 

((MainActivity) MainActivity.context).aaa();
((MainActivity) MainActivity.context).num;


지문인식 설정


 

sseong66.tistory.com/60

 

[Android] 지문인식 구현하기

지문인식 구현하기 1. AndroidManifest.xml 권한추가 2. gradle dependency추가 3. java코드 추가 1. AndroidManifest.xml 권한추가 2. gradle dependency추가 implementation 'androidx.biometric:biometric:1.0..

sseong66.tistory.com

위 게시글을 보면 지문인식을 MainActivity에서 구현을 했는데

환경설정에서 ON, OFF 했을 때 Main에서 동작을 켜고끄려면 SharedPreferences를 사용해서 SwitchPreference에 설정한 값을 불러와주시면 됩니다.

 

Preference 뷰는 간단하게 만들어봤습니다. SwitchPreference를 가져올 key값을 fingerprint로 설정했습니다.

MainActivity에서 SwitchPreference로 접근해보겠습니다.

<PreferenceCategory
	android:title="보안 설정">

	<SwitchPreference
		android:key="fingerprint"
		app:iconSpaceReserved="false"
		android:defaultValue="false"
		android:summaryOn="켜짐"
		android:summaryOff="꺼짐"
		android:title="지문인식"/>
</PreferenceCategory>

 

MainActivity에서 Preference에서 설정한 값이 저장된 SharedPreferences에 접근합니다.

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);

 

fingerprint에 해당하는 boolean값을 preferences.getBoolean으로 가져와서 원하는 설정을 해주시면 됩니다.

아래와 같이 해주시면 스위치ON일 때 지문인식이 실행되고, OFF일때 취소되게 됩니다.

if (preferences.getBoolean("fingerprint", false)){
	biometricPrompt.authenticate(promptInfo);
}else {
	biometricPrompt.cancelAuthentication();
}

 



지문인식 구현하기

1. AndroidManifest.xml 권한추가

2. gradle dependency추가

3. java코드 추가


1. AndroidManifest.xml 권한추가

<!-- 지문인식 권한부여 -->
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>

 

2. gradle dependency추가

implementation 'androidx.biometric:biometric:1.0.1'

 

3. java코드 추가

public class MainActivity extends AppCompatActivity {

    private Executor executor;
    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;

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


        executor = ContextCompat.getMainExecutor(this);
        biometricPrompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Toast.makeText(getApplicationContext(), "인증에러!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                Toast.makeText(getApplicationContext(), "인증성공!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Toast.makeText(getApplicationContext(), "인증실패!", Toast.LENGTH_SHORT).show();
            }
        });

        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("지문 인증")
                .setNegativeButtonText("취소")
                .setDeviceCredentialAllowed(false)
                .build();
    }
}

지문인식을하기 위한 기본 설정은 완료했습니다.

 

마지막으로 실행시키기 위해서 아래코드를 원하는 동작에 입력해주시면 됩니다.

biometricPrompt.authenticate(promptInfo);

취소하기 위해서는 cancelAuthentication()을 사용하시면 됩니다.

biometricPrompt.cancelAuthentication();

 



SQLite 백업 및 복구


백업 및 복구를 하기 전에 storage에 접근하기 위한 permission 권한을 가지고 있어야 합니다.

AndroidManifest.xml에서 manifest태그 안에 읽기와 쓰기에 대한 권한을 부여해줍니다.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 

 

 

*백업*

앱 내부에 있는 파일을 스마트폰 저장공간(/Download)으로 복사하는 코드입니다.

getExternalStorageDirectory()는 /storage/emulated/0

getDataDirectory()는 /data

위치값을 가져옵니다.

FileChannel을 이용해서 파일을 복사해주고 close로 닫아주면 됩니다.

try {
	File sd = Environment.getExternalStorageDirectory();
	File data = Environment.getDataDirectory();

	if (sd.canWrite()) {
		File currentDB = new File(data, "/data/패키지명/databases/파일명");
		File backupDB = new File(sd, "/Download/파일명");

		FileChannel src = new FileInputStream(currentDB).getChannel();
		FileChannel dst = new FileOutputStream(backupDB).getChannel();
		dst.transferFrom(src, 0, src.size());

		src.close();
		dst.close();
		Snackbar.make(getView(), "백업이 완료되었습니다.", Snackbar.LENGTH_SHORT).show();
	}
} catch (Exception e) {
	Snackbar.make(getView(), "백업이 실패되었습니다.", Snackbar.LENGTH_SHORT).show();
}

 

*복원*

백업과 반대로 스마트폰 저장공간에 있는 파일을 앱 내부 공간으로 복사합니다.

try {
	File sd = Environment.getExternalStorageDirectory();
	File data = Environment.getDataDirectory();

	if (sd.canWrite()) {
		File currentDB = new File(sd, "/Download/DB파일");
		File restoreDB = new File(data, "/data/패키지명/databases/DB파일");

		FileChannel src = new FileInputStream(currentDB).getChannel();
		FileChannel dst = new FileOutputStream(restoreDB).getChannel();
		dst.transferFrom(src, 0, src.size());

		src.close();
		dst.close();
		Snackbar.make(getView(), "복구가 완료되었습니다.", Snackbar.LENGTH_SHORT).show();
	}
} catch (Exception e) {
	Snackbar.make(getView(), "복구에 실패하였습니다.", Snackbar.LENGTH_SHORT).show();
}

 

※ 추가정보

- 파일 복사가 아닌 파일 이동의 경우 currentDB.delete()로 제거하면 됩니다.

 

try문에 진입하지 않고 catch로 넘어갈 경우

- catch에 어떤부분이 문제가 생겼는지 Log로 확인하면 됩니다.

-실행했을 때 제대로 실행되지 않고 open failed: EACCES (Permission denied)라고 예외처리가 되면 접근권한이 없으므로

  manifest에 퍼미션 추가해주셔야합니다.

  manifest - application 태그 안에 android:requestLegacyExternalStorage="true"를 작성하면 정상적으로 접근가능합니다.

 

try문은 진입했으나 if문을 들어가지 않을 경우

- sd.canWrite가 false를 반환하고 있을겁니다.

  이 경우에는 앱에서 저장공간 액세스 권한이 없으므로 "설정-애플리케이션-앱-권한"에 들어가서 액세스 허용을 시켜줘야 합니다. 

  (퍼미션체크 설정: sseong66.tistory.com/64 )



Date to String

&

String to Date

변환하기


Date to String

 

- Calendar 객체를 생성하여 현재 날짜와 시간정보를 가져옵니다.

Calendar calendar = Calendar.getInstance();

- 원하는 Date 형식을 지정.

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

- 가져온 정보를 format 후 String으로 저장.

String date = dateFormat.format(calendar.getTime());

String to Date

 

- String 타입으로 되어있는 날짜값이 어떤값인지 알기 위해서 SimpleDateFormat으로 맞춰줍니다.

  그리고 parse를 이용해서 Date 변수에 담아주면 됩니다.

  ( ※ parse는 try-catch문 안에서 사용하지 않을 경우 에러가 발생합니다. )

Date parse_date = null;
String date = "20200101";
SimpleDateFormat dateFormat1 = new SimpleDateFormat("yyyyMMdd");

try {

	parse_date = dateFormat1.parse(date);
    
}catch (Exception e){ e.printStackTrace(); }

 



Preferences 앱 설정화면

with PreferenceFragmentCompat


PreferenceActivity를 사용하려고 했는데 API 29부터는 deprecated 되기 때문에 대안으로

PreferenceFragmentCompat을 이용해서 사용할 수 있습니다.

PreferenceFragmentCompat은 Activity가 아니라서 AndroidManifest에 등록이 되지 않습니다.

해결방법은 AppCompatActivity를 상속한 레이아웃을 만들어서 android:name으로 클래스를 선언해주시면 됩니다.

 

 


 gradle 추가

∨ Toolbar 생성 및 설정

 menu 생성

∨ onCreateOptionsMenu, onOptionItemSelected 메서드 오버라이드

res/xml/user_preference.xml 파일생성 & PreferenceFragmentCompat 상속받은 UserPreferences 클래스 생성

∨ res/layout/preferences.xml 파일생성 & AppCompatActivity 상속받은 PreferencesActivity 클래스 생성

∨ manifest.xml에 해당 Activity 추가


 

implementation 'androidx.preference:preference:1.1.0'

gradle에 preference 추가.

 

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
// 툴바 설정
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

xml에 Toolbar 추가하고 java파일에 설정.

 

res 우클릭 - New - Android Resource Directory에서 menu 폴더를 생성해줍니다.

생성한 menu폴더 우클릭 - New - Menu Resource File로 원하는 이름으로 메뉴 xml 파일을 생성해주세요.

 

menu_main.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_settings"
        android:title="settings"
        android:icon="@drawable/ic_baseline_settings_24"
        app:showAsAction="always"/>
</menu>

생성한 menu파일에 추가하려는 버튼을 item으로 추가시킵니다.

app:showAsAction은 액션바에 바로 보이게하거나 버튼을 눌렀을 때 보이게하도록 설정할 수 있습니다.

 

@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {

        switch (item.getItemId()){
            case R.id.action_settings:
                startActivity(new Intent(this, PreferencesActivity.class));
                break;
        }
        return super.onOptionsItemSelected(item);
    }

onCreateOptionMenu와 onOPtionsItemSelected를 오버라이드 해줍니다.

onCreateOptionMenu로 액션바에 메뉴를 추가하고,

onOptionsItemSelected로 해당 버튼을 클릭했을 때 수행할 코드를 작성시켜줍니다.

버튼이 하나일 경우에는 if로 간단하게 하셔도 됩니다. 여러개일 경우에는 위와 같이 switch case로 하시면 됩니다.

 

 

UserPreferences.java

public class UserPreferences extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.user_preferences, rootKey);
    }
}

PreferenceFragmentCompat을 상속한 클래스를 생성합니다.

 

user_preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
    <PreferenceCategory
        app:key="notifications_category"
        app:title="Notifications">
        <SwitchPreferenceCompat
            app:key="notifications"
            app:title="알림"/>
        <CheckBoxPreference
            app:title="체크박스"/>
    </PreferenceCategory>
    <PreferenceCategory
        app:key="help_category"
        app:title="Help">
        <Preference
            app:key="feedback"
            app:summary="Report technical issues or suggest new features"
            app:title="Send feedback"/>
    </PreferenceCategory>
</PreferenceScreen>

res 우클릭 - New - Android Resource Directory로 xml 폴더 생성.

xml 폴더 우클릭해서 root element를 PreferenceScreen로 해서 xml파일을 생성.

스위치, 체크박스 등 설정이 가능하고, 카테고리로 지정해서 묶을수도 있습니다.

 

PreferencesActivity.java

public class PreferencesActivity extends AppCompatActivity {

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

AppCompatActivity를 상속한 클래스를 생성합니다.

 

preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
    	android:id="@+id/settings_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="클래스명"/>

</FrameLayout>

res/layout에 xml파일을 생성합니다.

FrameLayout에 fragment를 만들고 android:name에 PreferenceFragmentCompat을 상속받은 클래스명을 입력해주시면 됩니다.

(settings_container로 id 설정 안해주시면 에러 발생합니다.)

 

<activity android:name=".PreferencesActivity"/>

manifest에 activity를 추가해줍니다. AppCompatActivity가 상속된!

 

 

preferences 파일이 여러개여서 혼동이 올 수 있으니 유의하세요.



Intent로 화면 전환, 데이터 전달


다른 액티비티로 이동을 하거나, 데이터 전달을 하려고 경우에 Intent를 사용하면 됩니다.

 

1. 화면 이동

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getBaseContext(), NextActivity.class);
                startActivity(intent);
            }
        });

위는 버튼을 눌렀을 때 다른 Activity로 이동하는 액티비티입니다.

Intent 생성 후 new Intent 안에 첫 번째 인자(현재Activity의 content), 두 번째 인자(이동하려는 Activity.class)로 작성해주시면 됩니다.

그리고 startActivity에 intent를 담아 실행시켜주시면 해당버튼을 클릭했을 때 원하는 Activity로 이동할 수 있습니다.

 

2. 데이터 전달

- 데이터 전달

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getBaseContext(), NextActivity.class);
                intent.putExtra("name", "chocolate");
                intent.putExtra("amount", 20);
                startActivity(intent);
            }
        });

화면 이동 코드에서 putExtra만 추가해주면 됩니다.

putExtra 첫 번째 인자에는 키값을 원하는 String값으로 설정해주고, 두 번째 인자에는 전달할 값을 적어줍니다.

 

- 데이터 수신

Intent intent = getIntent();
String name = intent.getExtra().getString("name");
String amount = intent.getExtra().getInt("amount");

전달된 데이터를 받을 때는 getIntent()로 받아옵니다.

String의 경우에는 intent.getExtra().getString("키값");

int의 경우에는 intent.getExtra().getInt("키값"); 으로 값을 받아와 변수에 담아주면 됩니다.


+ Recent posts