Parcourir la source

保持逻辑一致

sequoia00 il y a 1 mois
Parent
commit
7632c5f752
1 fichiers modifiés avec 134 ajouts et 87 suppressions
  1. 134 87
      androidApp/app/src/main/java/com/example/lockx/crypto/CryptoUtils.kt

+ 134 - 87
androidApp/app/src/main/java/com/example/lockx/crypto/CryptoUtils.kt

@@ -1,11 +1,13 @@
 package com.example.lockx.crypto
 
 import java.io.File
+import java.io.RandomAccessFile
 import java.util.zip.CRC32
 import javax.crypto.Mac
 import javax.crypto.spec.SecretKeySpec
 
 object CryptoUtils {
+
     private const val MAGIC = "FMLKv1!!"
     private const val VERSION = 1
     private const val SALT_SIZE = 16
@@ -38,16 +40,27 @@ object CryptoUtils {
         val dk = org.bouncycastle.crypto.generators.SCrypt.generate(
             password.toByteArray(Charsets.UTF_8),
             salt,
-            1 shl 14, 8, 1, 64
+            1 shl 14,
+            8,
+            1,
+            64
         )
         return dk.sliceArray(0..31) to dk.sliceArray(32..63)
     }
 
     fun buildVerifier(checkKey: ByteArray): ByteArray {
-        return hmacSha256(checkKey, "FMLK-PASSWORD-CHECK".toByteArray(Charsets.UTF_8)).take(VERIFIER_SIZE).toByteArray()
+        return hmacSha256(checkKey, "FMLK-PASSWORD-CHECK".toByteArray(Charsets.UTF_8))
+            .take(VERIFIER_SIZE)
+            .toByteArray()
     }
 
-    fun buildTrailer(chunkSize: Long, originalSize: Long, salt: ByteArray, nonce: ByteArray, verifier: ByteArray): ByteArray {
+    fun buildTrailer(
+        chunkSize: Long,
+        originalSize: Long,
+        salt: ByteArray,
+        nonce: ByteArray,
+        verifier: ByteArray
+    ): ByteArray {
         val head = ByteArray(TRAILER_SIZE - 4)
         var off = 0
         for (b in MAGIC.toByteArray()) head[off++] = b
@@ -58,6 +71,7 @@ object CryptoUtils {
         salt.copyInto(head, off); off += 16
         nonce.copyInto(head, off); off += 16
         verifier.copyInto(head, off); off += 16
+
         val crc = crc32(head)
         val trailer = ByteArray(TRAILER_SIZE)
         head.copyInto(trailer)
@@ -98,7 +112,14 @@ object CryptoUtils {
         return TrailerMeta(chunkSize, originalSize, salt, nonce, verifier)
     }
 
-    fun encryptFile(file: File, password: String, chunkMb: Int, onProgress: ((Long, Long) -> Unit)? = null): Result<Unit> {
+    // ==================== 原地修改版本 ====================
+
+    fun encryptFile(
+        file: File,
+        password: String,
+        chunkMb: Int,
+        onProgress: ((Long, Long) -> Unit)? = null
+    ): Result<Unit> {
         return try {
             if (!file.exists() || !file.isFile) return Result.failure(Exception("不是有效文件"))
             if (isEncrypted(file)) return Result.failure(Exception("已加密"))
@@ -107,132 +128,155 @@ object CryptoUtils {
             val originalSize = file.length()
             val lockedPath = lockedName(file.absolutePath)
             val lockedFile = File(lockedPath)
+
             if (lockedFile.exists() && lockedPath != file.absolutePath) {
                 return Result.failure(Exception("目标文件已存在"))
             }
 
             val chunkSize = chunkMb.toLong() * 1024 * 1024
+            val n = minOf(chunkSize, originalSize)
+
             val salt = ByteArray(SALT_SIZE).also { java.security.SecureRandom().nextBytes(it) }
             val nonce = ByteArray(NONCE_SIZE).also { java.security.SecureRandom().nextBytes(it) }
             val (streamKey, checkKey) = deriveKeys(password, salt)
             val verifier = buildVerifier(checkKey)
             val trailer = buildTrailer(chunkSize, originalSize, salt, nonce, verifier)
-            val n = if (chunkSize < originalSize) chunkSize else originalSize
-
-            file.inputStream().use { input ->
-                lockedFile.outputStream().use { output ->
-                    var processed = 0L
-                    var blockOffset = 0
-                    val bufSize = if (IO_CHUNK_SIZE < n) IO_CHUNK_SIZE.toInt() else n.toInt()
-                    val buffer = ByteArray(bufSize)
-                    while (processed < n) {
-                        val remaining = n - processed
-                        val readSize = if (buffer.size < remaining) buffer.size else remaining.toInt()
-                        val bytesRead = input.read(buffer, 0, readSize)
-                        if (bytesRead <= 0) break
-                        val plain = buffer.copyOfRange(0, bytesRead)
-                        val cipher = xorBytes(plain, streamKey, nonce, blockOffset)
-                        output.write(cipher)
-                        processed += bytesRead
-                        blockOffset += (bytesRead + 31) / 32
-                        onProgress?.invoke(processed, n)
-                    }
-                    // 剩余明文原样拷贝(只加密前 n 字节,其余保持原样)
-                    if (originalSize > n) {
-                        input.copyTo(output)
-                    }
-                    output.write(trailer)
+
+            RandomAccessFile(file, "rw").use { raf ->
+                var processed = 0L
+                var blockOffset = 0
+                val buffer = ByteArray(minOf(IO_CHUNK_SIZE.toLong(), n).toInt())
+
+                while (processed < n) {
+                    val remaining = n - processed
+                    val readSize = minOf(buffer.size.toLong(), remaining).toInt()
+
+                    raf.seek(processed)
+                    val bytesRead = raf.read(buffer, 0, readSize)
+                    if (bytesRead <= 0) break
+
+                    val plain = buffer.copyOfRange(0, bytesRead)
+                    val cipher = xorBytes(plain, streamKey, nonce, blockOffset)
+
+                    raf.seek(processed)
+                    raf.write(cipher)
+
+                    processed += bytesRead
+                    blockOffset += (bytesRead + 31) / 32
+                    onProgress?.invoke(processed, n)
                 }
+
+                // 追加 trailer
+                raf.seek(originalSize)
+                raf.write(trailer)
             }
-            if (lockedPath != file.absolutePath) file.delete()
+
+            // 需要加后缀时再 rename
+            if (lockedPath != file.absolutePath) {
+                if (!file.renameTo(lockedFile)) {
+                    file.copyTo(lockedFile, overwrite = true)
+                    file.delete()
+                }
+            }
+
             Result.success(Unit)
-        } catch (e: Exception) { Result.failure(e) }
+        } catch (e: Exception) {
+            Result.failure(e)
+        }
     }
 
-    fun decryptFile(file: File, password: String, onProgress: ((Long, Long) -> Unit)? = null): Result<Unit> {
+    fun decryptFile(
+        file: File,
+        password: String,
+        onProgress: ((Long, Long) -> Unit)? = null
+    ): Result<Unit> {
         return try {
             if (!file.exists() || !file.isFile) return Result.failure(Exception("不是有效文件"))
+
             val meta = readTrailer(file) ?: return Result.failure(Exception("未加密"))
-            if (meta.originalSize + TRAILER_SIZE != file.length()) return Result.failure(Exception("文件损坏"))
+            if (meta.originalSize + TRAILER_SIZE != file.length()) {
+                return Result.failure(Exception("文件损坏"))
+            }
 
             val unlockedPath = unlockedName(file.absolutePath)
             val unlockedFile = File(unlockedPath)
+
             if (unlockedFile.exists() && unlockedPath != file.absolutePath) {
                 return Result.failure(Exception("目标文件已存在"))
             }
 
             val (streamKey, checkKey) = deriveKeys(password, meta.salt)
             val verifier = buildVerifier(checkKey)
-            if (!verifier.contentEquals(meta.verifier)) return Result.failure(Exception("密码错误"))
-
-            val n = if (meta.chunkSize < meta.originalSize) meta.chunkSize else meta.originalSize
-            val tmpFile = File(unlockedFile.absolutePath + ".tmp")
-
-            file.inputStream().use { input ->
-                tmpFile.outputStream().use { output ->
-                    var processed = 0L
-                    var blockOffset = 0
-                    val bufSize = if (IO_CHUNK_SIZE < n) IO_CHUNK_SIZE.toInt() else n.toInt()
-                    val buffer = ByteArray(bufSize)
-                    while (processed < n) {
-                        val remaining = n - processed
-                        val readSize = if (buffer.size < remaining) buffer.size else remaining.toInt()
-                        val bytesRead = input.read(buffer, 0, readSize)
-                        if (bytesRead <= 0) break
-                        val cipher = buffer.copyOfRange(0, bytesRead)
-                        val plain = xorBytes(cipher, streamKey, meta.nonce, blockOffset)
-                        output.write(plain)
-                        processed += bytesRead
-                        blockOffset += (bytesRead + 31) / 32
-                        onProgress?.invoke(processed, n)
-                    }
-                    // 拷贝明文尾部(从位置 n 到 originalSize),trailer 自然留在流末尾被丢弃
-                    val plainTailSize = meta.originalSize - meta.chunkSize
-                    if (plainTailSize > 0L) {
-                        val tailBuf = ByteArray(4 * 1024 * 1024)
-                        var toCopy = plainTailSize
-                        while (toCopy > 0L) {
-                            val toRead = minOf(toCopy, tailBuf.size.toLong()).toInt()
-                            val bytesRead = input.read(tailBuf, 0, toRead)
-                            if (bytesRead <= 0) break
-                            output.write(tailBuf, 0, bytesRead)
-                            toCopy -= bytesRead
-                        }
-                    }
+            if (!verifier.contentEquals(meta.verifier)) {
+                return Result.failure(Exception("密码错误"))
+            }
+
+            val n = minOf(meta.chunkSize, meta.originalSize)
+
+            RandomAccessFile(file, "rw").use { raf ->
+                var processed = 0L
+                var blockOffset = 0
+                val buffer = ByteArray(minOf(IO_CHUNK_SIZE.toLong(), n).toInt())
+
+                while (processed < n) {
+                    val remaining = n - processed
+                    val readSize = minOf(buffer.size.toLong(), remaining).toInt()
+
+                    raf.seek(processed)
+                    val bytesRead = raf.read(buffer, 0, readSize)
+                    if (bytesRead <= 0) break
+
+                    val cipher = buffer.copyOfRange(0, bytesRead)
+                    val plain = xorBytes(cipher, streamKey, meta.nonce, blockOffset)
+
+                    raf.seek(processed)
+                    raf.write(plain)
+
+                    processed += bytesRead
+                    blockOffset += (bytesRead + 31) / 32
+                    onProgress?.invoke(processed, n)
                 }
+
+                // 截断掉 trailer
+                raf.setLength(meta.originalSize)
             }
 
+            // 需要去后缀时再 rename
             if (unlockedPath != file.absolutePath) {
-                unlockedFile.delete()
-                file.delete()
-                if (!tmpFile.renameTo(unlockedFile)) {
-                    tmpFile.copyTo(unlockedFile, overwrite = true)
-                    tmpFile.delete()
-                }
-            } else {
-                file.delete()
-                if (!tmpFile.renameTo(file)) {
-                    tmpFile.copyTo(file, overwrite = true)
-                    tmpFile.delete()
+                if (!file.renameTo(unlockedFile)) {
+                    file.copyTo(unlockedFile, overwrite = true)
+                    file.delete()
                 }
             }
+
             Result.success(Unit)
-        } catch (e: Exception) { Result.failure(e) }
+        } catch (e: Exception) {
+            Result.failure(e)
+        }
     }
 
-    private fun xorBytes(data: ByteArray, key: ByteArray, nonce: ByteArray, blockOffset: Int): ByteArray {
+    // ==================== 内部工具方法 ====================
+
+    private fun xorBytes(
+        data: ByteArray,
+        key: ByteArray,
+        nonce: ByteArray,
+        blockOffset: Int
+    ): ByteArray {
         if (data.isEmpty()) return ByteArray(0)
         val out = ByteArray(data.size)
         var i = 0
         while (i < data.size) {
-            val blockSize = if (32 < data.size - i) 32 else data.size - i
+            val blockSize = minOf(32, data.size - i)
             val counter = blockOffset + i / 32
             val counterBytes = counter.toLong().toBEBytes(8)
             val combined = ByteArray(nonce.size + counterBytes.size)
             nonce.copyInto(combined, 0)
             counterBytes.copyInto(combined, nonce.size)
             val ks = hmacSha256(key, combined)
-            for (j in 0 until blockSize) out[i + j] = ((data[i + j].toInt() xor ks[j].toInt()) and 0xFF).toByte()
+            for (j in 0 until blockSize) {
+                out[i + j] = ((data[i + j].toInt() xor ks[j].toInt()) and 0xFF).toByte()
+            }
             i += blockSize
         }
         return out
@@ -254,7 +298,6 @@ object CryptoUtils {
         return mac.doFinal(data)
     }
 
-    /** CRC32 只占 4 字节,用独立函数避免 8 字节越界 */
     private fun packBigEndian4(buf: ByteArray, offset: Int, value: Long) {
         var v = value
         for (i in 3 downTo 0) {
@@ -265,7 +308,9 @@ object CryptoUtils {
 
     private fun unpackBigEndian4(buf: ByteArray, offset: Int): Long {
         var result = 0L
-        for (i in offset until offset + 4) result = (result shl 8) or (buf[i].toLong() and 0xFFL)
+        for (i in offset until offset + 4) {
+            result = (result shl 8) or (buf[i].toLong() and 0xFFL)
+        }
         return result
     }
 
@@ -279,7 +324,9 @@ object CryptoUtils {
 
     private fun unpackBigEndian(buf: ByteArray, offset: Int): Long {
         var result = 0L
-        for (i in offset until offset + 8) result = (result shl 8) or (buf[i].toLong() and 0xFFL)
+        for (i in offset until offset + 8) {
+            result = (result shl 8) or (buf[i].toLong() and 0xFFL)
+        }
         return result
     }