Android (안드로이드)

[Android] AES (2) - Android 환경에서 AES 사용하기

Oscar:) 2024. 8. 19. 09:00

 

 

 

지난 포스팅에서는 AES에 대해 알아보고,

복호화 · 암호화 작업을 웹 사이트에서 간단히 다뤄보았다.

 

 

[Android] AES (1) - AES란? / 암호화 / 복호화 사용해보기

이번 포스팅에서는 AES 암호화에 대해 알아본다.    AES 란?   ✅ Advanced Encryption Standard의 약자로직역하면 고급 암호화 표준이다.   ✅ 높은 안정성과 빠른 속도를 자랑하기에 현재 대중적으

oscarstory.tistory.com

 


 

 

 

이번에는 안드로이드 환경(Java · Kotlin)에서 진행해보자.

 

 

 


 

Android 환경에서의 AES

 

 

Java에서는 java.security, javax.crypto 패키지에서 AES를 지원하기에

해당 클래스를 참조해주면 쉽게 사용할 수 있다.

 

물론 Kotlin 또한 Java와 100% 호환을 자랑하기에

위 클래스를 모두 이용할 수 있다.

 

 

AES의 요소는 지난 포스팅 예제와 동일한 고정 값

(Secret Key, IV, Cypher Mode, Padding Mode)을 사용할 것이며,

암호화 · 복호화 예제를 위주로 진행할 것이다.

 

 

시작하기 전, 클라이언트와 서버에서의

암호화 · 복호화 작업을 진행하는 구조를 생각해보자.

 

 

 


 

암호화 · 복호화를 언제, 어디서 진행해야 할까?

 

 

DB에 저장하는 보안이 필요한 데이터들(사용자 개인정보 등)은 암호화 작업이 필수적이다.

하지만 클라이언트 기준으로는 암호화 작업을 의무적으로 진행하지 않아도 된다.

(물론, 하면 좋지만 법률적으로 위반되지는 않는다)

 

그렇기에 클라이언트와 서버 간 HTTP 통신을 구현할 때,

다음과 같은 구조를 생각해볼 수 있다.

 

 

 

✅ 서버에서 암호화 / 클라이언트에서 복호화 작업 진행하는 구조

 

 

 

사용자가 앱에서 비밀번호를 입력하면,

데이터를 그대로 서버에 전송한다는 시나리오다.

 

암호화되지 않은 데이터가 Request에 담겨 전송될 것이고,

서버에서는 DB에 저장하기 전에 암호화 작업을 처리할 것이다.

 

앱에서 사용자 비밀번호가 필요한 상황이 생겼을 때 서버에 요청하면

Response로 암호화된 상태의 데이터를 준다.

 

앱에서는 데이터를 복호화 작업 처리하여 사용할 것이다.

 

 

 

위 구조를 정리하자면 다음과 같다.

 

● DB에는 암호화된 데이터가 저장된다. (필수)

● HTTP 스트림에는 암호화가 모두 적용되어 있지 않다.
(Request에는 암호화되지 않은 데이터가 전송된다)

● 클라이언트에서는 복호화 작업만 처리하면 된다.

 

 


 

 

 

✅ 클라이언트에서 암호화 · 복호화 작업 진행하는 구조

 

 

 

사용자가 앱에서 비밀번호를 입력하면,

그 데이터를 암호화 처리 후 서버에 전송하는 시나리오다.

 

Request에도 암호화 처리가 되어있고,

서버에서는 별도의 암호화 작업 없이 저장할 수 있다.

 

당연히 Response에도 암호화된 데이터로 응답이 오며,

앱에서 데이터를 복호화 작업 처리 후 사용할 수 있다.

 

 

 

위 구조는 다음과 같다.

 

● DB에 암호화된 데이터가 저장된다. (필수)

● HTTP 스트림에 암호화가 적용되어 있다.

● 클라이언트에서 암호화 · 복호화 작업을 모두 처리해야 한다.

 

 

 

 

클라이언트쪽 작업이 많아지긴 하지만, 본인은 2번째 방법을 지향한다.

하지만 요즘 진행하고 있는 프로젝트에서는 1번째 방법으로 처리하고 있다.

 

각자의 상황에 알맞게 구조를 생각해보고 결정하기 바란다.

 

 

 


 

암호화

 

 

 

✅ 먼저 고정값을(상수) 세팅해준다.

companion object {
	const val SECRET_KEY = "ImOscarSecretKey"
	const val IV = "OscarInitVector!"
	const val CIPHER = "AES/CBC/PKCS5Padding"
}

 

지난 포스팅에서도 언급했지만,

SecretKey는 절대 외부에 유출되면 안 된다.

 

지금은 예제이기 때문에 상관없다지만,

ignore 파일 등을 잘 활용해서 유출되는 일을 예방하도록 하자.

 

위 상수 중에서 CIPHER를 보면 조금 특이한데,

Cipher Mode, Padding Mode를 모두 포함하여 위 형식으로 작성해주면 된다.

 

 

 

 

✅ 암호화 처리하는 함수를 작성해주자.

fun encrypt(data: String): String {

	// Secret Key와 IV를 ByteArray 형식으로 변환
	val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), "AES")
	val ivSpec = IvParameterSpec(IV.toByteArray())

	// Cipher 초기화
	val cipher = Cipher.getInstance(CIPHER)
	cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec)

	// 암호화할 데이터를 ByteArray 형식으로 변환 후 암호화 처리
	val encryptedBytes = cipher.doFinal(data.toByteArray())

	// 암호화된 ByteArray 형식의 데이터를 Base64 인코딩하여 문자열로 변환
	return String(Base64.getEncoder().encode(encryptedBytes), StandardCharsets.UTF_8)
    
}

 

복잡해 보일 수 있지만,

지난 포스팅에서 언급했던 암호화 흐름에 대해 생각해보면 어렵지 않다.

 

Text → Byte → 암호화 → 암호화된 Byte → 암호화된 Base64 인코딩 Text

 

 

위 플로우대로 작업한 것이고, 코드마다 주석을 달아놨으니 비교해보자.

 

 

 

 

✅ 위 함수를 출력해보자.

val data = "1234"
Log.d(TAG, "암호화 후 데이터 : ${encrypt(data)}")

 

값으로는 1234를 넣고 로그를 찍어본다.

 

 

 

 

✅ 결과

 

 

 

Xp0OVgM1Vvvg3IK92PRc2w== 라는 값이 출력되었다.

 

복호화 처리도 테스트해야 하니 위 결과 값을 잘 복사해두자.

 

 

 


 

복호화

 

 

Secret Key, IV 등 고정값은 그대로 사용할 것이니

상수 세팅하는 코드는 별도로 올리지 않겠다.

 

 

 

✅ 복호화 처리하는 함수

private fun decrypt(encryptData: String): String {

	// Secret Key와 IV를 ByteArray 형식으로 변환
	val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), "AES")
	val ivSpec = IvParameterSpec(IV.toByteArray())

	// Cipher 초기화
	val cipher = Cipher.getInstance(CIPHER)
	cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec)

	// 암호화된 문자열 데이터를 ByteArray 형식으로 변환
	val byteArrayEncryptedText = Base64.getDecoder().decode(encryptData)

	// 암호화된 ByteArray 형식의 데이터를 복호화 처리
	val decryptedBytes = cipher.doFinal(byteArrayEncryptedText)

	// 복호화된 ByteArray 형식의 데이터를 문자열로 변환
	return String(decryptedBytes, StandardCharsets.UTF_8)

}

 

 

암호화와 마찬가지로, 복호화도 아래 플로우대로 진행한 것이다.

 

암호화된 Base64 인코딩 Text → 암호화된 Byte → 복호화 → Byte → Text

 

코드마다 주석을 달아놨으니 비교해가며 이해하기 바란다.

 

 

 

 

✅ 위 함수를 출력해보자.

val data = "Xp0OVgM1Vvvg3IK92PRc2w=="
Log.d(TAG, "복호화 후 데이터 : ${decrypt(data)}")

 

암호화 예제에서 출력된 결과값을 넣고 로그를 찍어본다.

 

 

 

 

✅ 결과

 

 

초기에 사용했던 1234 라는 데이터를 다시 확인할 수 있다.

 

 

 

 


 

 

 

AES 암호화 · 복호화를 Android(Java · Kotlin) 환경에서 다루어 보았다.

 

암호화 · 복호화의 흐름을 이해하려 하지 않고

다짜고짜 예제 박치기만 했을 때는 조금 어렵다고 생각했는데,

흐름을 정리해 보며 코드를 작성하니 생각보다 쉬웠던 것 같다.