Статичный IV для имён файлов
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user