Класс шифрования готов

This commit is contained in:
Roman Pytkov
2025-01-04 01:03:23 +03:00
parent 25947449af
commit db30408278
10 changed files with 128 additions and 107 deletions

View File

@@ -3,9 +3,9 @@ package com.github.nullptroma.wallenc.data.vaults.local
import com.fasterxml.jackson.core.JacksonException
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalDirectory
import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalFile
import com.github.nullptroma.wallenc.data.vaults.local.entity.LocalMetaInfo
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.CommonMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
@@ -114,7 +114,7 @@ class LocalStorageAccessor(
private class LocalStorageFilePair private constructor(
val file: File,
val metaFile: File,
val meta: LocalMetaInfo
val meta: CommonMetaInfo
) {
companion object {
@@ -135,23 +135,23 @@ class LocalStorageAccessor(
}
)
val metaFile = metaFilePath.toFile()
val metaInfo: LocalMetaInfo
val metaInfo: CommonMetaInfo
val storageFilePath = "/" + filePath.relativeTo(filesystemBasePath)
if (!metaFile.exists()) {
metaInfo = LocalMetaInfo(
metaInfo = CommonMetaInfo(
size = filePath.fileSize(),
path = storageFilePath
)
_jackson.writeValue(metaFile, metaInfo)
} else {
var readMeta: LocalMetaInfo
var readMeta: CommonMetaInfo
try {
val reader = metaFile.bufferedReader()
readMeta = _jackson.readValue(reader)
} catch (e: JacksonException) {
// если файл повреждён - пересоздать
readMeta = LocalMetaInfo(
readMeta = CommonMetaInfo(
size = filePath.fileSize(),
path = storageFilePath
)
@@ -174,7 +174,7 @@ class LocalStorageAccessor(
var pair: LocalStorageFilePair? = null
try {
val reader = metaFile.bufferedReader()
val metaInfo: LocalMetaInfo = _jackson.readValue(reader)
val metaInfo: CommonMetaInfo = _jackson.readValue(reader)
val pathString = Path(filesystemBasePath.pathString, metaInfo.path).pathString
val file = File(pathString)
if (!file.exists()) {
@@ -213,8 +213,8 @@ class LocalStorageAccessor(
private suspend fun scanStorage(
baseStoragePath: String,
maxDepth: Int,
fileCallback: (suspend (File, LocalFile) -> Unit)? = null,
dirCallback: (suspend (File, LocalDirectory) -> Unit)? = null
fileCallback: (suspend (File, CommonFile) -> Unit)? = null,
dirCallback: (suspend (File, CommonDirectory) -> Unit)? = null
) {
if (!checkAvailable())
throw Exception("Not available")
@@ -234,9 +234,9 @@ class LocalStorageAccessor(
workedMetaFiles.add(pair.metaFile.absolutePath)
if (pair.file.isFile) {
fileCallback?.invoke(pair.file, LocalFile(pair.meta))
fileCallback?.invoke(pair.file, CommonFile(pair.meta))
} else {
dirCallback?.invoke(pair.file, LocalDirectory(pair.meta, null))
dirCallback?.invoke(pair.file, CommonDirectory(pair.meta, null))
}
}
})
@@ -257,8 +257,8 @@ class LocalStorageAccessor(
var size = 0L
var numOfFiles = 0
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, localFile ->
size += localFile.metaInfo.size
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, CommonFile ->
size += CommonFile.metaInfo.size
numOfFiles++
if(numOfFiles % DATA_PAGE_LENGTH == 0) {
@@ -276,8 +276,8 @@ class LocalStorageAccessor(
return@withContext listOf()
val list = mutableListOf<IFile>()
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, localFile ->
list.add(localFile)
scanStorage(baseStoragePath = "/", maxDepth = -1, fileCallback = { _, CommonFile ->
list.add(CommonFile)
})
return@withContext list
}
@@ -287,8 +287,8 @@ class LocalStorageAccessor(
return@withContext listOf()
val list = mutableListOf<IFile>()
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, localFile ->
list.add(localFile)
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, CommonFile ->
list.add(CommonFile)
})
return@withContext list
}
@@ -299,7 +299,7 @@ class LocalStorageAccessor(
val buf = mutableListOf<IFile>()
var pageNumber = 0
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, localFile ->
scanStorage(baseStoragePath = path, maxDepth = 0, fileCallback = { _, CommonFile ->
if (buf.size == DATA_PAGE_LENGTH) {
val page = DataPage(
list = buf.toList(),
@@ -312,7 +312,7 @@ class LocalStorageAccessor(
emit(page)
buf.clear()
}
buf.add(localFile)
buf.add(CommonFile)
})
// отправка последней страницы
val page = DataPage(
@@ -381,11 +381,11 @@ class LocalStorageAccessor(
emit(page)
}.flowOn(ioDispatcher)
private fun writeMeta(metaFile: File, meta: LocalMetaInfo) {
private fun writeMeta(metaFile: File, meta: CommonMetaInfo) {
_jackson.writeValue(metaFile, meta)
}
private fun createFile(storagePath: String): LocalFile {
private fun createFile(storagePath: String): CommonFile {
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if(file.exists() && file.isDirectory) {
@@ -398,10 +398,10 @@ class LocalStorageAccessor(
val pair = LocalStorageFilePair.from(_filesystemBasePath, file) ?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = java.time.Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta)
return LocalFile(newMeta)
return CommonFile(newMeta)
}
private fun createDir(storagePath: String): LocalDirectory {
private fun createDir(storagePath: String): CommonDirectory {
val path = Path(_filesystemBasePath.pathString, storagePath)
val file = path.toFile()
if(file.exists() && !file.isDirectory) {
@@ -414,7 +414,7 @@ class LocalStorageAccessor(
val pair = LocalStorageFilePair.from(_filesystemBasePath, file) ?: throw Exception("Что то пошло не так") // TODO
val newMeta = pair.meta.copy(lastModified = java.time.Clock.systemUTC().instant())
writeMeta(pair.metaFile, newMeta)
return LocalDirectory(newMeta, 0)
return CommonDirectory(newMeta, 0)
}
override suspend fun touchFile(path: String): Unit = withContext(ioDispatcher) {

View File

@@ -1,8 +0,0 @@
package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
data class LocalDirectory(
override val metaInfo: LocalMetaInfo,
override val elementsCount: Int?
) : IDirectory

View File

@@ -1,5 +0,0 @@
package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.interfaces.IFile
data class LocalFile(override val metaInfo: LocalMetaInfo) : IFile

View File

@@ -1,13 +0,0 @@
package com.github.nullptroma.wallenc.data.vaults.local.entity
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import java.time.Instant
data class LocalMetaInfo(
override val size: Long,
override val isDeleted: Boolean = false,
override val isHidden: Boolean = false,
override val lastModified: Instant = java.time.Clock.systemUTC().instant(),
override val path: String
) : IMetaInfo

View File

@@ -0,0 +1,8 @@
package com.github.nullptroma.wallenc.domain.common.impl
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
data class CommonDirectory(
override val metaInfo: CommonMetaInfo,
override val elementsCount: Int?
) : IDirectory

View File

@@ -0,0 +1,5 @@
package com.github.nullptroma.wallenc.domain.common.impl
import com.github.nullptroma.wallenc.domain.interfaces.IFile
data class CommonFile(override val metaInfo: CommonMetaInfo) : IFile

View File

@@ -1,13 +1,14 @@
package com.github.nullptroma.wallenc.domain.encrypt.entity
package com.github.nullptroma.wallenc.domain.common.impl
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import java.time.Clock
import java.time.Instant
data class EncryptedMetaInfo(
data class CommonMetaInfo(
override val size: Long,
override val isDeleted: Boolean = false,
override val isHidden: Boolean = false,
override val lastModified: Instant = java.time.Clock.systemUTC().instant(),
override val lastModified: Instant = Clock.systemUTC().instant(),
override val path: String
) : IMetaInfo

View File

@@ -1,13 +1,14 @@
package com.github.nullptroma.wallenc.domain.encrypt
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.CommonMetaInfo
import com.github.nullptroma.wallenc.domain.datatypes.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.entity.EncryptedDirectory
import com.github.nullptroma.wallenc.domain.encrypt.entity.EncryptedFile
import com.github.nullptroma.wallenc.domain.encrypt.entity.EncryptedMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -15,10 +16,13 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.InputStream
import java.io.OutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64
@@ -46,8 +50,6 @@ class EncryptedStorageAccessor(
private val _secretKey = SecretKeySpec(key.to32Bytes(), "AES")
init {
val enc = encryptPath("/hello/world/test.txt")
val dec = decryptPath(enc)
collectSourceState(CoroutineScope(ioDispatcher))
}
@@ -55,16 +57,7 @@ class EncryptedStorageAccessor(
coroutineScope.launch {
launch {
source.filesUpdates.collect {
val files = it.data.map {
val meta = it.metaInfo
EncryptedFile(EncryptedMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = decryptPath(meta.path)
))
}
val files = it.data.map(::decryptEntity)
_filesUpdates.emit(DataPackage(
data = files,
isLoading = it.isLoading,
@@ -75,16 +68,7 @@ class EncryptedStorageAccessor(
launch {
source.dirsUpdates.collect {
val dirs = it.data.map {
val meta = it.metaInfo
EncryptedDirectory(EncryptedMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = decryptPath(meta.path)
), it.elementsCount)
}
val dirs = it.data.map(::decryptEntity)
_dirsUpdates.emit(DataPackage(
data = dirs,
isLoading = it.isLoading,
@@ -95,6 +79,42 @@ class EncryptedStorageAccessor(
}
}
private fun encryptEntity(file: IFile): IFile {
return CommonFile(encryptMeta(file.metaInfo))
}
private fun decryptEntity(file: IFile): IFile {
return CommonFile(decryptMeta(file.metaInfo))
}
private fun encryptEntity(dir: IDirectory): IDirectory {
return CommonDirectory(encryptMeta(dir.metaInfo), dir.elementsCount)
}
private fun decryptEntity(dir: IDirectory): IDirectory {
return CommonDirectory(decryptMeta(dir.metaInfo), dir.elementsCount)
}
private fun encryptMeta(meta: IMetaInfo): CommonMetaInfo {
return CommonMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = encryptPath(meta.path)
)
}
private fun decryptMeta(meta: IMetaInfo): CommonMetaInfo {
return CommonMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = decryptPath(meta.path)
)
}
@OptIn(ExperimentalEncodingApi::class)
private fun encryptString(str: String): String {
val cipher = Cipher.getInstance(AES_FOR_STRINGS)
@@ -121,7 +141,6 @@ class EncryptedStorageAccessor(
for (segment in path)
segments.add(encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray()))
logger.debug("encryptPath", "$pathStr to $res")
return res.pathString
}
@@ -131,56 +150,83 @@ class EncryptedStorageAccessor(
for (segment in path)
segments.add(decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray()))
logger.debug("decryptPath", "$pathStr to $res")
return res.pathString
}
override suspend fun getAllFiles(): List<IFile> {
TODO("Not yet implemented")
return source.getAllFiles().map(::decryptEntity)
}
override suspend fun getFiles(path: String): List<IFile> {
TODO("Not yet implemented")
return source.getFiles(encryptPath(path)).map(::decryptEntity)
}
override fun getFilesFlow(path: String): Flow<DataPackage<List<IFile>>> {
TODO("Not yet implemented")
val flow = source.getFilesFlow(encryptPath(path)).map {
DataPackage(
data = it.data.map(::decryptEntity),
isLoading = it.isLoading,
isError = it.isError
)
}
return flow
}
override suspend fun getAllDirs(): List<IDirectory> {
TODO("Not yet implemented")
return source.getAllDirs().map(::decryptEntity)
}
override suspend fun getDirs(path: String): List<IDirectory> {
TODO("Not yet implemented")
return source.getDirs(encryptPath(path)).map(::decryptEntity)
}
override fun getDirsFlow(path: String): Flow<DataPackage<List<IDirectory>>> {
TODO("Not yet implemented")
val flow = source.getDirsFlow(encryptPath(path)).map {
DataPackage(
data = it.data.map(::decryptEntity),
isLoading = it.isLoading,
isError = it.isError
)
}
return flow
}
override suspend fun touchFile(path: String) {
TODO("Not yet implemented")
source.touchFile(encryptPath(path))
}
override suspend fun touchDir(path: String) {
TODO("Not yet implemented")
source.touchDir(encryptPath(path))
}
override suspend fun delete(path: String) {
TODO("Not yet implemented")
source.delete(encryptPath(path))
}
override suspend fun openWrite(path: String): OutputStream {
TODO("Not yet implemented")
val stream = source.openWrite(encryptPath(path))
val iv = IvParameterSpec(Random.nextBytes(IV_LEN))
stream.write(iv.iv) // Запись инициализационного вектора сырой файл
val cipher = Cipher.getInstance(AES_FOR_STRINGS)
cipher.init(Cipher.ENCRYPT_MODE, _secretKey, iv) // инициализация шифратора
return CipherOutputStream(stream, cipher)
}
override suspend fun openRead(path: String): InputStream {
TODO("Not yet implemented")
val stream = source.openRead(encryptPath(path))
val ivBytes = ByteArray(IV_LEN) // Буфер для 16 байт IV
val bytesRead = stream.read(ivBytes) // Чтение IV вектора
if(bytesRead != IV_LEN)
throw Exception("TODO iv не прочитан")
val iv = IvParameterSpec(ivBytes)
val cipher = Cipher.getInstance(AES_FOR_STRINGS)
cipher.init(Cipher.DECRYPT_MODE, _secretKey, iv)
return CipherInputStream(stream, cipher)
}
override suspend fun moveToTrash(path: String) {
TODO("Not yet implemented")
source.moveToTrash(encryptPath(path))
}

View File

@@ -1,8 +0,0 @@
package com.github.nullptroma.wallenc.domain.encrypt.entity
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
data class EncryptedDirectory(
override val metaInfo: EncryptedMetaInfo,
override val elementsCount: Int?
) : IDirectory

View File

@@ -1,5 +0,0 @@
package com.github.nullptroma.wallenc.domain.encrypt.entity
import com.github.nullptroma.wallenc.domain.interfaces.IFile
data class EncryptedFile(override val metaInfo: EncryptedMetaInfo) : IFile