diff --git a/app/src/main/java/gallery/memories/service/AccountService.kt b/app/src/main/java/gallery/memories/service/AccountService.kt index 036cb5f2..68f5dafc 100644 --- a/app/src/main/java/gallery/memories/service/AccountService.kt +++ b/app/src/main/java/gallery/memories/service/AccountService.kt @@ -1,5 +1,6 @@ package gallery.memories.service +import SecureStorage import android.content.Intent import android.net.Uri import android.util.Log @@ -16,6 +17,8 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ val TAG = AccountService::class.java.simpleName } + private val store = SecureStorage(mCtx) + /** * Login to a server * @param baseUrl The base URL of the server @@ -160,11 +163,7 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ * @param password The password to store */ fun storeCredentials(url: String, user: String, password: String) { - mCtx.getSharedPreferences("credentials", 0).edit() - .putString("memoriesUrl", url) - .putString("user", user) - .putString("password", password) - .apply() + store.saveCredentials(url, user, password) mHttp.setBaseUrl(url) mHttp.setAuthHeader(Pair(user, password)) } @@ -174,12 +173,10 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ * @return The stored credentials */ fun getCredentials(): Pair? { - val prefs = mCtx.getSharedPreferences("credentials", 0) - mHttp.setBaseUrl(prefs.getString("memoriesUrl", null)) - val user = prefs.getString("user", null) - val password = prefs.getString("password", null) - if (user == null || password == null) return null - return Pair(user, password) + val saved = store.getCredentials() + if (saved == null) return null + mHttp.setBaseUrl(saved.first) + return Pair(saved.second, saved.third) } /** @@ -188,11 +185,7 @@ class AccountService(private val mCtx: MainActivity, private val mHttp: HttpServ fun deleteCredentials() { mHttp.setAuthHeader(null) mHttp.setBaseUrl(null) - mCtx.getSharedPreferences("credentials", 0).edit() - .remove("memoriesUrl") - .remove("user") - .remove("password") - .apply() + store.deleteCredentials() } /** diff --git a/app/src/main/java/gallery/memories/service/SecureStorage.kt b/app/src/main/java/gallery/memories/service/SecureStorage.kt new file mode 100644 index 00000000..f2d08fbc --- /dev/null +++ b/app/src/main/java/gallery/memories/service/SecureStorage.kt @@ -0,0 +1,92 @@ +import android.content.Context +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.security.keystore.KeyProperties.KEY_ALGORITHM_AES +import android.security.keystore.KeyProperties.PURPOSE_DECRYPT +import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT +import android.util.Base64 +import java.security.KeyStore +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec + +class SecureStorage(private val context: Context) { + + private val keyStore = KeyStore.getInstance("AndroidKeyStore") + private val keyAlias = "MemoriesKey" + + init { + keyStore.load(null) + if (!keyStore.containsAlias(keyAlias)) { + generateNewKey() + } + } + + fun saveCredentials(url: String, username: String, token: String) { + val cipher = getCipher() + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey()) + val encryptedToken = cipher.doFinal(token.toByteArray()) + + context.getSharedPreferences("credentials", Context.MODE_PRIVATE).edit() + .putString("url", url) + .putString("username", username) + .putString("encryptedToken", Base64.encodeToString(encryptedToken, Base64.DEFAULT)) + .putString("iv", Base64.encodeToString(cipher.iv, Base64.DEFAULT)) + .apply() + } + + fun getCredentials(): Triple? { + val sharedPreferences = context.getSharedPreferences("credentials", Context.MODE_PRIVATE) + val url = sharedPreferences.getString("url", null) + val username = sharedPreferences.getString("username", null) + val encryptedToken = sharedPreferences.getString("encryptedToken", null) + val ivStr = sharedPreferences.getString("iv", null) + + if (url != null && username != null && encryptedToken != null && ivStr != null) { + val iv = Base64.decode(ivStr, Base64.DEFAULT) + + val cipher = getCipher() + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IvParameterSpec(iv)) + + val token = String(cipher.doFinal(Base64.decode(encryptedToken, Base64.DEFAULT))) + return Triple(url, username, token) + } + + return null + } + + fun deleteCredentials() { + context.getSharedPreferences("credentials", Context.MODE_PRIVATE).edit() + .remove("url") + .remove("encryptedUsername") + .remove("encryptedToken") + .remove("iv") + .apply() + } + + private fun generateNewKey() { + val keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM_AES, "AndroidKeyStore") + val keyGenSpec = KeyGenParameterSpec.Builder( + keyAlias, + PURPOSE_ENCRYPT or PURPOSE_DECRYPT + ) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(false) // Change this if needed + .build() + + keyGenerator.init(keyGenSpec) + keyGenerator.generateKey() + } + + private fun getCipher(): Cipher { + val transformation = + "$KEY_ALGORITHM_AES/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}" + return Cipher.getInstance(transformation) + } + + private fun getSecretKey(): SecretKey { + return keyStore.getKey(keyAlias, null) as SecretKey + } +} \ No newline at end of file