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

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

View File

@@ -1,13 +1,14 @@
package com.github.nullptroma.wallenc.domain.encrypt 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.DataPackage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey 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.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger 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 com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -15,10 +16,13 @@ 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
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
@@ -46,8 +50,6 @@ class EncryptedStorageAccessor(
private val _secretKey = SecretKeySpec(key.to32Bytes(), "AES") private val _secretKey = SecretKeySpec(key.to32Bytes(), "AES")
init { init {
val enc = encryptPath("/hello/world/test.txt")
val dec = decryptPath(enc)
collectSourceState(CoroutineScope(ioDispatcher)) collectSourceState(CoroutineScope(ioDispatcher))
} }
@@ -55,16 +57,7 @@ class EncryptedStorageAccessor(
coroutineScope.launch { coroutineScope.launch {
launch { launch {
source.filesUpdates.collect { source.filesUpdates.collect {
val files = it.data.map { val files = it.data.map(::decryptEntity)
val meta = it.metaInfo
EncryptedFile(EncryptedMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = decryptPath(meta.path)
))
}
_filesUpdates.emit(DataPackage( _filesUpdates.emit(DataPackage(
data = files, data = files,
isLoading = it.isLoading, isLoading = it.isLoading,
@@ -75,16 +68,7 @@ class EncryptedStorageAccessor(
launch { launch {
source.dirsUpdates.collect { source.dirsUpdates.collect {
val dirs = it.data.map { val dirs = it.data.map(::decryptEntity)
val meta = it.metaInfo
EncryptedDirectory(EncryptedMetaInfo(
size = meta.size,
isDeleted = meta.isDeleted,
isHidden = meta.isHidden,
lastModified = meta.lastModified,
path = decryptPath(meta.path)
), it.elementsCount)
}
_dirsUpdates.emit(DataPackage( _dirsUpdates.emit(DataPackage(
data = dirs, data = dirs,
isLoading = it.isLoading, 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) @OptIn(ExperimentalEncodingApi::class)
private fun encryptString(str: String): String { private fun encryptString(str: String): String {
val cipher = Cipher.getInstance(AES_FOR_STRINGS) val cipher = Cipher.getInstance(AES_FOR_STRINGS)
@@ -121,7 +141,6 @@ class EncryptedStorageAccessor(
for (segment in path) for (segment in path)
segments.add(encryptString(segment.pathString)) segments.add(encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
logger.debug("encryptPath", "$pathStr to $res")
return res.pathString return res.pathString
} }
@@ -131,56 +150,83 @@ class EncryptedStorageAccessor(
for (segment in path) for (segment in path)
segments.add(decryptString(segment.pathString)) segments.add(decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
logger.debug("decryptPath", "$pathStr to $res")
return res.pathString return res.pathString
} }
override suspend fun getAllFiles(): List<IFile> { override suspend fun getAllFiles(): List<IFile> {
TODO("Not yet implemented") return source.getAllFiles().map(::decryptEntity)
} }
override suspend fun getFiles(path: String): List<IFile> { 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>>> { 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> { override suspend fun getAllDirs(): List<IDirectory> {
TODO("Not yet implemented") return source.getAllDirs().map(::decryptEntity)
} }
override suspend fun getDirs(path: String): List<IDirectory> { 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>>> { 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) { override suspend fun touchFile(path: String) {
TODO("Not yet implemented") source.touchFile(encryptPath(path))
} }
override suspend fun touchDir(path: String) { override suspend fun touchDir(path: String) {
TODO("Not yet implemented") source.touchDir(encryptPath(path))
} }
override suspend fun delete(path: String) { override suspend fun delete(path: String) {
TODO("Not yet implemented") source.delete(encryptPath(path))
} }
override suspend fun openWrite(path: String): OutputStream { 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 { 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) { 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