Skip to content

Commit

Permalink
add DataSaverEncryptedProperties for Desktop
Browse files Browse the repository at this point in the history
  • Loading branch information
FunnySaltyFish committed Jul 24, 2024
1 parent c76f985 commit 8782522
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ actual object AppConfig {
registerAllTypeConverters()
}

private const val filename = "data_saver.properties"
private val userHome = System.getProperty("user.home")
private const val projectName = "ComposeDataSaver"


actual val dataSaver: DataSaverInterface = DataSaverProperties("$userHome/$projectName/$filename")
// File Path Example(Windows): C:/Users/username/ComposeDataSaver/data_saver.properties
actual val dataSaver: DataSaverInterface = DataSaverProperties("$userHome/$projectName/data_saver.properties")
// DataSaverEncryptedProperties("$userHome/$projectName/data_saver_encrypted.properties", "FunnySaltyFish")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package com.funny.data_saver.core
/**
* Some config that you can set:
* 1. DEBUG: whether to output some debug info
* 2. LIST_SEPARATOR: the separator used to convert a list into string, '#@#' by default (**don't use ',' which will occurs in json itself** )
*/
object DataSaverConfig {
var DEBUG = true
var LIST_SEPARATOR = "#@#"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.funny.data_saver.core

import java.io.FileNotFoundException
import java.io.FileReader
import java.io.FileWriter
import java.security.MessageDigest
import java.util.Base64
import java.util.Properties
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
* Use [Properties] to save data in a properties file with encryption. The algorithm is AES/CBC/NoPadding.
*
* @property filePath String The file path to save the data file. 数据文件的保存路径。
* @param encryptionKey String The key to encrypt the data, can be any string. 用于加密数据的密钥,可以是任意字符串(实际使用的密钥为该字符串的 SHA-256 哈希值,且仅用其前 16 位产生 iv)
*/

open class DataSaverEncryptedProperties(private val filePath: String, private val encryptionKey: String) : DataSaverInterface() {
private val properties = Properties()
private val hashedKey = hashKey(encryptionKey)
private val encryptCipher by lazy { createCipher(Cipher.ENCRYPT_MODE) }
private val decryptCipher by lazy { createCipher(Cipher.DECRYPT_MODE) }

init {
try {
createFile(filePath)
FileReader(filePath).use { reader ->
properties.load(reader)
}
} catch (e: FileNotFoundException) {
// Handle file not found exception
} catch (e: Exception) {
// Handle other exceptions
e.printStackTrace()
}
}

private fun saveProperties() {
try {
FileWriter(filePath).use { writer ->
properties.store(writer, null)
}
} catch (e: Exception) {
// Handle file write exception
e.printStackTrace()
}
}

private fun createCipher(mode: Int): Cipher {
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val keySpec = SecretKeySpec(hashedKey, "AES")
val ivParameterSpec = IvParameterSpec(hashedKey.copyOfRange(0, 16))
cipher.init(mode, keySpec, ivParameterSpec)
return cipher
}

private fun hashKey(key: String): ByteArray {
val md = MessageDigest.getInstance("SHA-256")
return md.digest(key.toByteArray())
}

private fun encrypt(value: String): String {
val encryptedBytes = encryptCipher.doFinal(value.toByteArray())
return Base64.getEncoder().encodeToString(encryptedBytes)
}

private fun decrypt(value: String): String {
val decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(value))
return String(decryptedBytes)
}

override fun <T> saveData(key: String, data: T) {
val encryptedValue = encrypt(data.toString())
properties[key] = encryptedValue
saveProperties()
}

override fun <T> readData(key: String, default: T): T {
val encryptedValue = properties.getProperty(key) ?: return default
val decryptedValue = decrypt(encryptedValue)
return when (default) {
is Int -> decryptedValue.toIntOrNull() ?: default
is Long -> decryptedValue.toLongOrNull() ?: default
is Boolean -> decryptedValue.toBooleanStrictOrNull() ?: default
is Double -> decryptedValue.toDoubleOrNull() ?: default
is Float -> decryptedValue.toFloatOrNull() ?: default
is String -> decryptedValue
else -> unsupportedType("read", default)
} as T
}

override fun remove(key: String) {
properties.remove(key)
saveProperties()
}

override fun contains(key: String): Boolean {
return properties.containsKey(key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import java.io.FileReader
import java.io.FileWriter
import java.util.Properties

/**
* Use [Properties] to save data in a file, all data are stored in **PLAIN TEXT**. If you want to encrypt the data, you can use [DataSaverEncryptedProperties] instead.
*
* ---
* 基于 [Properties] 的数据存储器,所有数据都是以 **明文** 存储的。如果你想要加密数据,可以使用 [DataSaverEncryptedProperties]。
* @property filePath String The file path to save the data file. 数据文件的保存路径。
* @constructor
*/
class DataSaverProperties(private val filePath: String) : DataSaverInterface() {
private val properties = Properties()

Expand Down Expand Up @@ -54,13 +62,11 @@ class DataSaverProperties(private val filePath: String) : DataSaverInterface() {
}
}

private fun createFile(filePath: String) {
internal fun createFile(filePath: String) {
val file = File(filePath)
if (!file.exists()) {
val parentFile = file.parentFile
if (!parentFile.exists()) {
parentFile.mkdirs()
}
parentFile?.mkdirs()
file.createNewFile()
}
}

0 comments on commit 8782522

Please sign in to comment.