Skip to content

Commit e5f7c5b

Browse files
committed
[NDGL-18] feature: Token, uuid 암호화해서 저장하는 기능 추가
1 parent 5e6d23f commit e5f7c5b

2 files changed

Lines changed: 91 additions & 4 deletions

File tree

data/auth/src/main/java/com/yapp/ndgl/data/auth/local/LocalAuthDataSource.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
44
import androidx.datastore.preferences.core.Preferences
55
import androidx.datastore.preferences.core.edit
66
import androidx.datastore.preferences.core.stringPreferencesKey
7+
import com.yapp.ndgl.data.auth.local.security.CryptoManager
78
import com.yapp.ndgl.data.auth.local.util.handleException
89
import kotlinx.coroutines.flow.Flow
910
import kotlinx.coroutines.flow.first
@@ -14,17 +15,18 @@ import javax.inject.Singleton
1415
@Singleton
1516
class LocalAuthDataSource @Inject constructor(
1617
private val dataStore: DataStore<Preferences>,
18+
private val cryptoManager: CryptoManager,
1719
) {
1820
private val accessToken: Flow<String> = dataStore.data
1921
.handleException()
2022
.map { preferences ->
21-
preferences[ACCESS_TOKEN_KEY] ?: ""
23+
cryptoManager.decrypt(preferences[ACCESS_TOKEN_KEY] ?: "")
2224
}
2325

2426
private val uuid: Flow<String> = dataStore.data
2527
.handleException()
2628
.map { preferences ->
27-
preferences[UUID_KEY] ?: ""
29+
cryptoManager.decrypt(preferences[UUID_KEY] ?: "")
2830
}
2931

3032
suspend fun getAccessToken(): String = accessToken.first()
@@ -33,13 +35,13 @@ class LocalAuthDataSource @Inject constructor(
3335

3436
suspend fun setAccessToken(token: String) {
3537
dataStore.edit { preferences ->
36-
preferences[ACCESS_TOKEN_KEY] = token
38+
preferences[ACCESS_TOKEN_KEY] = cryptoManager.encrypt(token)
3739
}
3840
}
3941

4042
suspend fun setUuid(uuid: String) {
4143
dataStore.edit { preferences ->
42-
preferences[UUID_KEY] = uuid
44+
preferences[UUID_KEY] = cryptoManager.encrypt(uuid)
4345
}
4446
}
4547

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.yapp.ndgl.data.auth.local.security
2+
3+
import android.security.keystore.KeyGenParameterSpec
4+
import android.security.keystore.KeyProperties
5+
import android.util.Base64
6+
import timber.log.Timber
7+
import java.security.GeneralSecurityException
8+
import java.security.KeyStore
9+
import javax.crypto.Cipher
10+
import javax.crypto.KeyGenerator
11+
import javax.crypto.SecretKey
12+
import javax.crypto.spec.GCMParameterSpec
13+
import javax.inject.Inject
14+
import javax.inject.Singleton
15+
16+
@Singleton
17+
class CryptoManager @Inject constructor() {
18+
private val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply {
19+
load(null)
20+
}
21+
22+
private fun getKey(): SecretKey {
23+
return keyStore.getKey(KEY_ALIAS, null) as? SecretKey ?: createKey()
24+
}
25+
26+
private fun createKey(): SecretKey =
27+
KeyGenerator.getInstance(
28+
KeyProperties.KEY_ALGORITHM_AES,
29+
KEYSTORE_PROVIDER,
30+
).apply {
31+
init(
32+
KeyGenParameterSpec.Builder(
33+
KEY_ALIAS,
34+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
35+
)
36+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
37+
.setEncryptionPaddings(PADDING)
38+
.setUserAuthenticationRequired(false)
39+
.build(),
40+
)
41+
}.generateKey()
42+
43+
fun encrypt(plainText: String): String {
44+
check(plainText.isNotEmpty()) { "Plain text is empty" }
45+
46+
val cipher = Cipher.getInstance(TRANSFORMATION)
47+
cipher.init(Cipher.ENCRYPT_MODE, getKey())
48+
49+
val encryptedBytes = cipher.doFinal(plainText.toByteArray())
50+
val combined = cipher.iv + encryptedBytes
51+
52+
return Base64.encodeToString(combined, Base64.NO_WRAP)
53+
}
54+
55+
fun decrypt(encryptedText: String): String {
56+
try {
57+
if (encryptedText.isEmpty()) return ""
58+
59+
val combined = Base64.decode(encryptedText, Base64.NO_WRAP)
60+
val iv = combined.copyOfRange(0, IV_SIZE)
61+
val encryptedBytes = combined.copyOfRange(IV_SIZE, combined.size)
62+
63+
val cipher = Cipher.getInstance(TRANSFORMATION)
64+
val spec = GCMParameterSpec(GCM_TAG_LENGTH, iv)
65+
cipher.init(Cipher.DECRYPT_MODE, getKey(), spec)
66+
67+
val decryptedBytes = cipher.doFinal(encryptedBytes)
68+
return String(decryptedBytes)
69+
} catch (e: GeneralSecurityException) {
70+
Timber.e(e, "Failed to decrypt")
71+
return ""
72+
}
73+
}
74+
75+
companion object {
76+
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
77+
private const val KEY_ALIAS = "ndgl_encryption_key"
78+
private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
79+
private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
80+
private const val PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
81+
private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
82+
private const val IV_SIZE = 12
83+
private const val GCM_TAG_LENGTH = 128
84+
}
85+
}

0 commit comments

Comments
 (0)