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

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.datatypes.DataPackage
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.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
@@ -22,16 +23,9 @@ 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
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.path.Path
import kotlin.io.path.pathString
import kotlin.random.Random
class EncryptedStorageAccessor(
private val source: IStorageAccessor,
@@ -52,7 +46,7 @@ class EncryptedStorageAccessor(
private val _dirsUpdates = MutableSharedFlow<DataPackage<List<IDirectory>>>()
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 {
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 {
val path = Path(pathStr)
val segments = mutableListOf<String>()
for (segment in path)
segments.add(encryptString(segment.pathString))
segments.add(_encryptor.encryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray()))
return res.pathString
}
@@ -158,7 +128,7 @@ class EncryptedStorageAccessor(
val path = Path(pathStr)
val segments = mutableListOf<String>()
for (segment in path)
segments.add(decryptString(segment.pathString))
segments.add(_encryptor.decryptString(segment.pathString))
val res = Path("/",*(segments.toTypedArray()))
return res.pathString
}
@@ -230,29 +200,13 @@ class EncryptedStorageAccessor(
}
override suspend fun openWrite(path: String): OutputStream {
if(_secretKey == null)
throw Exception("Object was disposed")
val stream = source.openWrite(encryptPath(path))
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)
return _encryptor.encryptStream(stream)
}
override suspend fun openRead(path: String): InputStream {
if(_secretKey == null)
throw Exception("Object was disposed")
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_SETTINGS)
cipher.init(Cipher.DECRYPT_MODE, _secretKey, iv)
return CipherInputStream(stream, cipher)
return _encryptor.decryptStream(stream)
}
override suspend fun moveToTrash(path: String) {
@@ -261,12 +215,16 @@ class EncryptedStorageAccessor(
override fun dispose() {
_job.cancel()
_secretKey = null
_encryptor.dispose()
}
companion object {
private const val IV_LEN = 16
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"
}
}