Вынос шифрования в отдельный класс

This commit is contained in:
Roman Pytkov
2025-01-05 16:32:06 +03:00
parent ab82aa6e7b
commit ccafffe398
2 changed files with 92 additions and 53 deletions

View File

@@ -5,6 +5,7 @@ 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.datatypes.StorageEncryptionInfo
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
@@ -22,16 +23,9 @@ 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.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.pathString import kotlin.io.path.pathString
import kotlin.random.Random
class EncryptedStorageAccessor( class EncryptedStorageAccessor(
private val source: IStorageAccessor, private val source: IStorageAccessor,
@@ -52,7 +46,7 @@ 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 var _secretKey: SecretKeySpec? = SecretKeySpec(key.to32Bytes(), "AES") private val _encryptor = Encryptor(SecretKeySpec(key.to32Bytes(), "AES"))
init { init {
collectSourceState() collectSourceState()
@@ -121,35 +115,11 @@ class EncryptedStorageAccessor(
) )
} }
@OptIn(ExperimentalEncodingApi::class)
private fun encryptString(str: String): String {
if(_secretKey == null)
throw Exception("Object was disposed")
val cipher = Cipher.getInstance(AES_SETTINGS)
val iv = IvParameterSpec(Random.nextBytes(IV_LEN))
cipher.init(Cipher.ENCRYPT_MODE, _secretKey, iv)
val bytesToEncrypt = iv.iv + str.toByteArray(Charsets.UTF_8)
val encryptedBytes = cipher.doFinal(bytesToEncrypt)
return Base64.Default.encode(encryptedBytes).replace("/", ".")
}
@OptIn(ExperimentalEncodingApi::class)
private fun decryptString(str: String): String {
if(_secretKey == null)
throw Exception("Object was disposed")
val cipher = Cipher.getInstance(AES_SETTINGS)
val bytesToDecrypt = Base64.Default.decode(str.replace(".", "/"))
val iv = IvParameterSpec(bytesToDecrypt.take(IV_LEN).toByteArray())
cipher.init(Cipher.DECRYPT_MODE, _secretKey, iv)
val decryptedBytes = cipher.doFinal(bytesToDecrypt.drop(IV_LEN).toByteArray())
return String(decryptedBytes, Charsets.UTF_8)
}
private fun encryptPath(pathStr: String): String { private fun encryptPath(pathStr: String): String {
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(encryptString(segment.pathString)) segments.add(_encryptor.encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -158,7 +128,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(decryptString(segment.pathString)) segments.add(_encryptor.decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray())) val res = Path("/",*(segments.toTypedArray()))
return res.pathString return res.pathString
} }
@@ -230,29 +200,13 @@ class EncryptedStorageAccessor(
} }
override suspend fun openWrite(path: String): OutputStream { override suspend fun openWrite(path: String): OutputStream {
if(_secretKey == null)
throw Exception("Object was disposed")
val stream = source.openWrite(encryptPath(path)) val stream = source.openWrite(encryptPath(path))
val iv = IvParameterSpec(Random.nextBytes(IV_LEN)) return _encryptor.encryptStream(stream)
stream.write(iv.iv) // Запись инициализационного вектора сырой файл
val cipher = Cipher.getInstance(AES_SETTINGS)
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 {
if(_secretKey == null)
throw Exception("Object was disposed")
val stream = source.openRead(encryptPath(path)) val stream = source.openRead(encryptPath(path))
val ivBytes = ByteArray(IV_LEN) // Буфер для 16 байт IV return _encryptor.decryptStream(stream)
val bytesRead = stream.read(ivBytes) // Чтение IV вектора
if(bytesRead != IV_LEN)
throw Exception("TODO iv не прочитан")
val iv = IvParameterSpec(ivBytes)
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, _secretKey, iv)
return CipherInputStream(stream, cipher)
} }
override suspend fun moveToTrash(path: String) { override suspend fun moveToTrash(path: String) {
@@ -261,12 +215,16 @@ class EncryptedStorageAccessor(
override fun dispose() { override fun dispose() {
_job.cancel() _job.cancel()
_secretKey = null _encryptor.dispose()
} }
companion object { companion object {
private const val IV_LEN = 16 private const val IV_LEN = 16
private const val AES_SETTINGS = "AES/CBC/PKCS5Padding" private const val AES_SETTINGS = "AES/CBC/PKCS5Padding"
fun generateEncryptionInfo(key: EncryptKey): StorageEncryptionInfo {
TODO()
}
} }
} }

View File

@@ -0,0 +1,81 @@
package com.github.nullptroma.wallenc.domain.encrypt
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 Encryptor(private var _secretKey: SecretKey?) : DisposableHandle {
@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)
}
private fun encryptBytes(bytes: ByteArray): ByteArray {
val secretKey = _secretKey ?: throw Exception("Object was disposed")
val cipher = Cipher.getInstance(AES_SETTINGS)
val iv = IvParameterSpec(Random.nextBytes(IV_LEN))
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
val encryptedBytes = iv.iv + cipher.doFinal(bytes) // iv + зашифрованные байты
return encryptedBytes
}
private fun decryptBytes(bytes: ByteArray): ByteArray {
val secretKey = _secretKey ?: throw Exception("Object was disposed")
val cipher = Cipher.getInstance(AES_SETTINGS)
val iv = IvParameterSpec(bytes.take(IV_LEN).toByteArray())
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
val decryptedBytes = cipher.doFinal(bytes.drop(IV_LEN).toByteArray())
return decryptedBytes
}
fun encryptStream(stream: OutputStream): OutputStream {
val secretKey = _secretKey ?: throw Exception("Object was disposed")
val iv = IvParameterSpec(Random.nextBytes(IV_LEN))
stream.write(iv.iv) // Запись инициализационного вектора сырой файл
val cipher = Cipher.getInstance(AES_SETTINGS)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv) // инициализация шифратора
return CipherOutputStream(stream, cipher)
}
fun decryptStream(stream: InputStream): InputStream {
val secretKey = _secretKey ?: throw Exception("Object was disposed")
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_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
return CipherInputStream(stream, cipher)
}
override fun dispose() {
_secretKey?.destroy()
_secretKey = null
}
companion object {
private const val IV_LEN = 16
private const val AES_SETTINGS = "AES/CBC/PKCS5Padding"
}
}