Статичный IV для имён файлов

This commit is contained in:
Пытков Роман
2025-02-08 17:45:13 +03:00
parent c95c374852
commit da8808a4b9
12 changed files with 201 additions and 86 deletions

View File

@@ -10,11 +10,11 @@ import com.github.nullptroma.wallenc.data.db.app.model.DbStorageKeyMap
@Dao @Dao
interface StorageKeyMapDao { interface StorageKeyMapDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun add(keymap: DbStorageKeyMap) suspend fun add(vararg keymaps: DbStorageKeyMap)
@Query("SELECT * FROM storage_key_maps") @Query("SELECT * FROM storage_key_maps")
suspend fun getAll(): List<DbStorageKeyMap> suspend fun getAll(): List<DbStorageKeyMap>
@Delete @Delete
suspend fun delete(keymap: DbStorageKeyMap) suspend fun delete(vararg keymaps: DbStorageKeyMap)
} }

View File

@@ -11,13 +11,13 @@ class StorageKeyMapRepository(
private val ioDispatcher: CoroutineDispatcher private val ioDispatcher: CoroutineDispatcher
) { ) {
suspend fun getAll() = withContext(ioDispatcher) { dao.getAll().map { it.toModel() } } suspend fun getAll() = withContext(ioDispatcher) { dao.getAll().map { it.toModel() } }
suspend fun add(keymap: StorageKeyMap) = withContext(ioDispatcher) { suspend fun add(vararg keymaps: StorageKeyMap) = withContext(ioDispatcher) {
val dbModel = DbStorageKeyMap.fromModel(keymap) val dbModels = keymaps.map { DbStorageKeyMap.fromModel(it) }
dao.add(dbModel) dao.add(*dbModels.toTypedArray())
} }
suspend fun delete(keymap: StorageKeyMap) = withContext(ioDispatcher) { suspend fun delete(vararg keymaps: StorageKeyMap) = withContext(ioDispatcher) {
val dbModel = DbStorageKeyMap.fromModel(keymap) val dbModels = keymaps.map { DbStorageKeyMap.fromModel(it) }
dao.delete(dbModel) dao.delete(*dbModels.toTypedArray())
} }
} }

View File

@@ -36,16 +36,23 @@ class UnlockManager(
vaultsManager.allStorages.collectLatest { vaultsManager.allStorages.collectLatest {
mutex.lock() mutex.lock()
val allKeys = keymapRepository.getAll() val allKeys = keymapRepository.getAll()
val keysToRemove = mutableListOf<StorageKeyMap>()
val allStorages = it.associateBy({ it.uuid }, { it }) val allStorages = it.associateBy({ it.uuid }, { it })
val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf() val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf()
for(keymap in allKeys) { for(keymap in allKeys) {
if(map.contains(keymap.sourceUuid)) if(map.contains(keymap.sourceUuid))
continue continue
val storage = allStorages[keymap.sourceUuid] ?: continue try {
val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid) val storage = allStorages[keymap.sourceUuid] ?: continue
map[storage.uuid] = encStorage val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid)
map[storage.uuid] = encStorage
}
catch (_: Exception) {
keysToRemove.add(keymap)
}
} }
_openedStorages.value = map _openedStorages.value = map
keymapRepository.delete(*keysToRemove.toTypedArray()) // удалить мёртвые ключи
mutex.unlock() mutex.unlock()
} }
} }

View File

@@ -4,10 +4,13 @@ import com.github.nullptroma.wallenc.data.db.app.repository.StorageMetaInfoRepos
import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
import com.github.nullptroma.wallenc.domain.interfaces.IStorage import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -15,11 +18,15 @@ import java.util.UUID
class EncryptedStorage private constructor( class EncryptedStorage private constructor(
private val source: IStorage, private val source: IStorage,
key: EncryptKey, private val key: EncryptKey,
private val ioDispatcher: CoroutineDispatcher, ioDispatcher: CoroutineDispatcher,
private val metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider, private val metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider,
override val uuid: UUID = UUID.randomUUID() override val uuid: UUID = UUID.randomUUID()
) : IStorage, DisposableHandle { ) : IStorage, DisposableHandle {
private val job = Job()
private val scope = CoroutineScope(ioDispatcher + job)
private val encInfo = source.metaInfo.value.encInfo ?: throw Exception("Storage is not encrypted") // TODO
override val size: StateFlow<Long?> override val size: StateFlow<Long?>
get() = source.size get() = source.size
override val numberOfFiles: StateFlow<Int?> override val numberOfFiles: StateFlow<Int?>
@@ -35,13 +42,19 @@ class EncryptedStorage private constructor(
override val isAvailable: StateFlow<Boolean> override val isAvailable: StateFlow<Boolean>
get() = source.isAvailable get() = source.isAvailable
override val accessor: EncryptedStorageAccessor = override val accessor: EncryptedStorageAccessor =
EncryptedStorageAccessor(source.accessor, key, ioDispatcher) EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, scope)
private suspend fun init() { private suspend fun init() {
checkKey()
readMeta() readMeta()
} }
private suspend fun readMeta() = withContext(ioDispatcher) { private fun checkKey() {
if(!Encryptor.checkKey(key, encInfo))
throw Exception("Incorrect key") // TODO
}
private suspend fun readMeta() = scope.run {
var meta = metaInfoProvider.get() var meta = metaInfoProvider.get()
if(meta == null) { if(meta == null) {
meta = CommonStorageMetaInfo() meta = CommonStorageMetaInfo()
@@ -50,7 +63,7 @@ class EncryptedStorage private constructor(
_metaInfo.value = meta _metaInfo.value = meta
} }
override suspend fun rename(newName: String) = withContext(ioDispatcher) { override suspend fun rename(newName: String) = scope.run {
val cur = _metaInfo.value val cur = _metaInfo.value
val newMeta = CommonStorageMetaInfo( val newMeta = CommonStorageMetaInfo(
encInfo = cur.encInfo, encInfo = cur.encInfo,
@@ -60,7 +73,7 @@ class EncryptedStorage private constructor(
metaInfoProvider.set(newMeta) metaInfoProvider.set(newMeta)
} }
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = withContext(ioDispatcher) { override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = scope.run {
val cur = _metaInfo.value val cur = _metaInfo.value
val newMeta = CommonStorageMetaInfo( val newMeta = CommonStorageMetaInfo(
encInfo = encInfo, encInfo = encInfo,
@@ -72,6 +85,7 @@ class EncryptedStorage private constructor(
override fun dispose() { override fun dispose() {
accessor.dispose() accessor.dispose()
job.cancel()
} }
companion object { companion object {
@@ -89,7 +103,13 @@ class EncryptedStorage private constructor(
metaInfoProvider = metaInfoProvider, metaInfoProvider = metaInfoProvider,
uuid = uuid uuid = uuid
) )
storage.init() try {
storage.init()
}
catch (e: Exception) {
storage.dispose()
throw e
}
return@withContext storage return@withContext storage
} }
} }

View File

@@ -1,19 +1,19 @@
package com.github.nullptroma.wallenc.data.storages.encrypt package com.github.nullptroma.wallenc.data.storages.encrypt
import android.util.Log
import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory
import com.github.nullptroma.wallenc.domain.common.impl.CommonFile import com.github.nullptroma.wallenc.domain.common.impl.CommonFile
import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.DataPackage import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
import com.github.nullptroma.wallenc.domain.encrypt.EncryptorWithStaticIv
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@@ -27,12 +27,10 @@ import kotlin.io.path.pathString
class EncryptedStorageAccessor( class EncryptedStorageAccessor(
private val source: IStorageAccessor, private val source: IStorageAccessor,
pathIv: ByteArray,
key: EncryptKey, key: EncryptKey,
ioDispatcher: CoroutineDispatcher private val scope: CoroutineScope
) : IStorageAccessor, DisposableHandle { ) : IStorageAccessor, DisposableHandle {
private val job = Job()
private val scope = CoroutineScope(ioDispatcher + job)
override val size: StateFlow<Long?> = source.size override val size: StateFlow<Long?> = source.size
override val numberOfFiles: StateFlow<Int?> = source.numberOfFiles override val numberOfFiles: StateFlow<Int?> = source.numberOfFiles
override val isAvailable: StateFlow<Boolean> = source.isAvailable override val isAvailable: StateFlow<Boolean> = source.isAvailable
@@ -43,10 +41,19 @@ class EncryptedStorageAccessor(
private val _dirsUpdates = MutableSharedFlow<DataPackage<List<IDirectory>>>() private val _dirsUpdates = MutableSharedFlow<DataPackage<List<IDirectory>>>()
override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates override val dirsUpdates: SharedFlow<DataPackage<List<IDirectory>>> = _dirsUpdates
private val encryptor = Encryptor(key.toAesKey()) private val dataEncryptor = Encryptor(key.toAesKey())
private val pathEncryptor = EncryptorWithStaticIv(key.toAesKey(), pathIv)
init { init {
collectSourceState() collectSourceState()
for(i in 1..5) {
val orig = "/hello/path/test.txt"
val enc = encryptPath(orig)
val dec = decryptPath(enc)
Log.d("MyTag", "Path $orig to $enc to $dec")
}
} }
private fun collectSourceState() { private fun collectSourceState() {
@@ -63,7 +70,6 @@ class EncryptedStorageAccessor(
} }
launch { launch {
source.dirsUpdates.collect { source.dirsUpdates.collect {
val dirs = it.data.map(::decryptEntity) val dirs = it.data.map(::decryptEntity)
_dirsUpdates.emit(DataPackage( _dirsUpdates.emit(DataPackage(
@@ -116,7 +122,7 @@ class EncryptedStorageAccessor(
val path = Path(pathStr) val path = Path(pathStr)
val segments = mutableListOf<String>() val segments = mutableListOf<String>()
for (segment in path) for (segment in path)
segments.add(encryptor.encryptString(segment.pathString)) segments.add(pathEncryptor.encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -125,7 +131,7 @@ class EncryptedStorageAccessor(
val path = Path(pathStr) val path = Path(pathStr)
val segments = mutableListOf<String>() val segments = mutableListOf<String>()
for (segment in path) for (segment in path)
segments.add(encryptor.decryptString(segment.pathString)) segments.add(pathEncryptor.decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -198,12 +204,12 @@ class EncryptedStorageAccessor(
override suspend fun openWrite(path: String): OutputStream { override suspend fun openWrite(path: String): OutputStream {
val stream = source.openWrite(encryptPath(path)) val stream = source.openWrite(encryptPath(path))
return encryptor.encryptStream(stream) return dataEncryptor.encryptStream(stream)
} }
override suspend fun openRead(path: String): InputStream { override suspend fun openRead(path: String): InputStream {
val stream = source.openRead(encryptPath(path)) val stream = source.openRead(encryptPath(path))
return encryptor.decryptStream(stream) return dataEncryptor.decryptStream(stream)
} }
override suspend fun moveToTrash(path: String) { override suspend fun moveToTrash(path: String) {
@@ -211,8 +217,7 @@ class EncryptedStorageAccessor(
} }
override fun dispose() { override fun dispose() {
job.cancel() dataEncryptor.dispose()
encryptor.dispose()
} }
} }

View File

@@ -9,10 +9,7 @@ import java.time.Instant
data class CommonStorageMetaInfo( data class CommonStorageMetaInfo(
override val encInfo: StorageEncryptionInfo = StorageEncryptionInfo( override val encInfo: StorageEncryptionInfo? = null,
isEncrypted = false,
encryptedTestData = null
),
override val name: String? = null, override val name: String? = null,
override val lastModified: Instant = Clock.systemUTC().instant() override val lastModified: Instant = Clock.systemUTC().instant()
) : IStorageMetaInfo ) : IStorageMetaInfo

View File

@@ -1,6 +1,24 @@
package com.github.nullptroma.wallenc.domain.datatypes package com.github.nullptroma.wallenc.domain.datatypes
data class StorageEncryptionInfo( data class StorageEncryptionInfo(
val isEncrypted: Boolean, val encryptedTestData: String,
val encryptedTestData: String? val pathIv: ByteArray
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StorageEncryptionInfo
if (encryptedTestData != other.encryptedTestData) return false
if (!pathIv.contentEquals(other.pathIv)) return false
return true
}
override fun hashCode(): Int {
var result = encryptedTestData.hashCode()
result = 31 * result + pathIv.contentHashCode()
return result
}
}

View File

@@ -14,7 +14,7 @@ import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.random.Random import kotlin.random.Random
class Encryptor(private var _secretKey: SecretKey?) : DisposableHandle { class Encryptor(private var secretKey: SecretKey) : DisposableHandle {
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
fun encryptString(str: String): String { fun encryptString(str: String): String {
val bytesToEncrypt = str.toByteArray(Charsets.UTF_8) val bytesToEncrypt = str.toByteArray(Charsets.UTF_8)
@@ -30,54 +30,57 @@ class Encryptor(private var _secretKey: SecretKey?) : DisposableHandle {
} }
fun encryptBytes(bytes: ByteArray): ByteArray { fun encryptBytes(bytes: ByteArray): ByteArray {
val secretKey = _secretKey ?: throw Exception("Object was disposed") if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS) val cipher = Cipher.getInstance(AES_SETTINGS)
val iv = IvParameterSpec(Random.nextBytes(IV_LEN)) val ivSpec = IvParameterSpec(Random.nextBytes(IV_LEN))
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv) cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
val encryptedBytes = iv.iv + cipher.doFinal(bytes) // iv + зашифрованные байты val encryptedBytes = ivSpec.iv + cipher.doFinal(bytes) // iv + зашифрованные байты
return encryptedBytes return encryptedBytes
} }
fun decryptBytes(bytes: ByteArray): ByteArray { fun decryptBytes(bytes: ByteArray): ByteArray {
val secretKey = _secretKey ?: throw Exception("Object was disposed") if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS) val cipher = Cipher.getInstance(AES_SETTINGS)
val iv = IvParameterSpec(bytes.take(IV_LEN).toByteArray()) val ivSpec = IvParameterSpec(bytes.take(IV_LEN).toByteArray())
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
val decryptedBytes = cipher.doFinal(bytes.drop(IV_LEN).toByteArray()) val decryptedBytes = cipher.doFinal(bytes.drop(IV_LEN).toByteArray())
return decryptedBytes return decryptedBytes
} }
fun encryptStream(stream: OutputStream): OutputStream { fun encryptStream(stream: OutputStream): OutputStream {
val secretKey = _secretKey ?: throw Exception("Object was disposed") if(secretKey.isDestroyed)
val iv = IvParameterSpec(Random.nextBytes(IV_LEN)) throw Exception("Object was destroyed")
stream.write(iv.iv) // Запись инициализационного вектора сырой файл val ivSpec = IvParameterSpec(Random.nextBytes(IV_LEN))
stream.write(ivSpec.iv) // Запись инициализационного вектора сырой файл
val cipher = Cipher.getInstance(AES_SETTINGS) val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv) // инициализация шифратора cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec) // инициализация шифратора
return CipherOutputStream(stream, cipher) return CipherOutputStream(stream, cipher)
} }
fun decryptStream(stream: InputStream): InputStream { fun decryptStream(stream: InputStream): InputStream {
val secretKey = _secretKey ?: throw Exception("Object was disposed") if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val ivBytes = ByteArray(IV_LEN) // Буфер для 16 байт IV val ivBytes = ByteArray(IV_LEN) // Буфер для 16 байт IV
val bytesRead = stream.read(ivBytes) // Чтение IV вектора val bytesRead = stream.read(ivBytes) // Чтение IV вектора
if(bytesRead != IV_LEN) if(bytesRead != IV_LEN)
throw Exception("TODO iv не прочитан") throw Exception("TODO iv не прочитан")
val iv = IvParameterSpec(ivBytes) val ivSpec = IvParameterSpec(ivBytes)
val cipher = Cipher.getInstance(AES_SETTINGS) val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
return CipherInputStream(stream, cipher) return CipherInputStream(stream, cipher)
} }
override fun dispose() { override fun dispose() {
_secretKey?.destroy() secretKey.destroy()
_secretKey = null
} }
companion object { companion object {
private const val IV_LEN = 16 public const val IV_LEN = 16
public const val AES_SETTINGS = "AES/CBC/PKCS5Padding"
private const val TEST_DATA_LEN = 512 private const val TEST_DATA_LEN = 512
private const val AES_SETTINGS = "AES/CBC/PKCS5Padding"
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
fun generateEncryptionInfo(key: EncryptKey) : StorageEncryptionInfo { fun generateEncryptionInfo(key: EncryptKey) : StorageEncryptionInfo {
@@ -85,15 +88,13 @@ class Encryptor(private var _secretKey: SecretKey?) : DisposableHandle {
val testData = ByteArray(TEST_DATA_LEN) val testData = ByteArray(TEST_DATA_LEN)
val encryptedData = encryptor.encryptBytes(testData) val encryptedData = encryptor.encryptBytes(testData)
return StorageEncryptionInfo( return StorageEncryptionInfo(
isEncrypted = true, encryptedTestData = Base64.Default.encode(encryptedData),
encryptedTestData = Base64.Default.encode(encryptedData) pathIv = Random.nextBytes(IV_LEN)
) )
} }
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
fun checkKey(key: EncryptKey, encInfo: StorageEncryptionInfo): Boolean { fun checkKey(key: EncryptKey, encInfo: StorageEncryptionInfo): Boolean {
if(encInfo.encryptedTestData == null)
return false
val encryptor = Encryptor(key.toAesKey()) val encryptor = Encryptor(key.toAesKey())
try { try {
val encData = Base64.Default.decode(encInfo.encryptedTestData) val encData = Base64.Default.decode(encInfo.encryptedTestData)

View File

@@ -0,0 +1,73 @@
package com.github.nullptroma.wallenc.domain.encrypt
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor.Companion.AES_SETTINGS
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor.Companion.IV_LEN
import kotlinx.coroutines.DisposableHandle
import java.io.InputStream
import java.io.OutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.random.Random
class EncryptorWithStaticIv(private var secretKey: SecretKey, iv: ByteArray) : DisposableHandle {
private val ivSpec = IvParameterSpec(iv)
@OptIn(ExperimentalEncodingApi::class)
fun encryptString(str: String): String {
val bytesToEncrypt = str.toByteArray(Charsets.UTF_8)
val encryptedBytes = encryptBytes(bytesToEncrypt)
return Base64.Default.encode(encryptedBytes).replace("/", ".")
}
@OptIn(ExperimentalEncodingApi::class)
fun decryptString(str: String): String {
val bytesToDecrypt = Base64.Default.decode(str.replace(".", "/"))
val decryptedBytes = decryptBytes(bytesToDecrypt)
return String(decryptedBytes, Charsets.UTF_8)
}
fun encryptBytes(bytes: ByteArray): ByteArray {
if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
val encryptedBytes = cipher.doFinal(bytes) // зашифрованные байты
return encryptedBytes
}
fun decryptBytes(bytes: ByteArray): ByteArray {
if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
val decryptedBytes = cipher.doFinal(bytes)
return decryptedBytes
}
fun encryptStream(stream: OutputStream): OutputStream {
if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec) // инициализация шифратора
return CipherOutputStream(stream, cipher)
}
fun decryptStream(stream: InputStream): InputStream {
if(secretKey.isDestroyed)
throw Exception("Object was destroyed")
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
return CipherInputStream(stream, cipher)
}
override fun dispose() {
secretKey.destroy()
}
}

View File

@@ -1,6 +1,18 @@
package com.github.nullptroma.wallenc.domain.interfaces package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import kotlinx.coroutines.flow.StateFlow
import java.time.Instant
import java.util.UUID
sealed interface IStorageInfo {
val uuid: UUID
val isAvailable: StateFlow<Boolean>
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val metaInfo: StateFlow<IStorageMetaInfo>
val isVirtualStorage: Boolean
}
interface IStorage: IStorageInfo { interface IStorage: IStorageInfo {
val accessor: IStorageAccessor val accessor: IStorageAccessor
@@ -8,3 +20,9 @@ interface IStorage: IStorageInfo {
suspend fun rename(newName: String) suspend fun rename(newName: String)
suspend fun setEncInfo(encInfo: StorageEncryptionInfo) suspend fun setEncInfo(encInfo: StorageEncryptionInfo)
} }
interface IStorageMetaInfo {
val encInfo: StorageEncryptionInfo?
val name: String?
val lastModified: Instant
}

View File

@@ -1,13 +0,0 @@
package com.github.nullptroma.wallenc.domain.interfaces
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
sealed interface IStorageInfo {
val uuid: UUID
val isAvailable: StateFlow<Boolean>
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val metaInfo: StateFlow<IStorageMetaInfo>
val isVirtualStorage: Boolean
}

View File

@@ -1,11 +0,0 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import java.time.Instant
interface IStorageMetaInfo {
val encInfo: StorageEncryptionInfo
val name: String?
val lastModified: Instant
}