From f50bec002be80185b559ffbe528cd37364fd1fad Mon Sep 17 00:00:00 2001 From: stevenyomi <95685115+stevenyomi@users.noreply.github.com> Date: Sun, 24 Aug 2025 09:53:31 +0000 Subject: [PATCH] Use CipherSource to decrypt responses by streaming (#10253) --- src/all/izneo/build.gradle | 2 +- .../extension/all/izneo/ImageInterceptor.kt | 8 ++++---- src/ja/comicfuz/build.gradle | 2 +- .../extension/ja/comicfuz/ImageInterceptor.kt | 12 ++++++------ .../pt/randomscan/LuraZipInterceptor.kt | 16 +++++++++------- src/zh/manwa/build.gradle | 2 +- .../kanade/tachiyomi/extension/zh/manwa/Manwa.kt | 9 +++++---- src/zh/onemanhua/build.gradle | 2 +- .../zh/onemanhua/ColaMangaImageInterceptor.kt | 12 ++++++------ 9 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/all/izneo/build.gradle b/src/all/izneo/build.gradle index 0452d47e0..5eab20ced 100644 --- a/src/all/izneo/build.gradle +++ b/src/all/izneo/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'izneo (webtoons)' extClass = '.IzneoFactory' - extVersionCode = 7 + extVersionCode = 8 isNsfw = false } diff --git a/src/all/izneo/src/eu/kanade/tachiyomi/extension/all/izneo/ImageInterceptor.kt b/src/all/izneo/src/eu/kanade/tachiyomi/extension/all/izneo/ImageInterceptor.kt index 683093cd4..a065050d6 100644 --- a/src/all/izneo/src/eu/kanade/tachiyomi/extension/all/izneo/ImageInterceptor.kt +++ b/src/all/izneo/src/eu/kanade/tachiyomi/extension/all/izneo/ImageInterceptor.kt @@ -4,14 +4,14 @@ import android.util.Base64 import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.ResponseBody.Companion.asResponseBody +import okio.buffer +import okio.cipherSource import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec object ImageInterceptor : Interceptor { - private val mediaType = "image/jpeg".toMediaType() - private inline val AES: Cipher get() = Cipher.getInstance("AES/CBC/PKCS7Padding") @@ -31,7 +31,7 @@ object ImageInterceptor : Interceptor { private fun Response.decode(key: ByteArray, iv: ByteArray) = AES.let { it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - newBuilder().body(it.doFinal(body.bytes()).toResponseBody(mediaType)).build() + newBuilder().body(body.source().cipherSource(it).buffer().asResponseBody("image/jpeg".toMediaType())).build() } private fun String.atob() = Base64.decode(this, Base64.URL_SAFE) diff --git a/src/ja/comicfuz/build.gradle b/src/ja/comicfuz/build.gradle index 2bac3a57c..63881db1d 100644 --- a/src/ja/comicfuz/build.gradle +++ b/src/ja/comicfuz/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'COMIC FUZ' extClass = '.ComicFuz' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/ja/comicfuz/src/eu/kanade/tachiyomi/extension/ja/comicfuz/ImageInterceptor.kt b/src/ja/comicfuz/src/eu/kanade/tachiyomi/extension/ja/comicfuz/ImageInterceptor.kt index f3aac944e..0ae75e524 100644 --- a/src/ja/comicfuz/src/eu/kanade/tachiyomi/extension/ja/comicfuz/ImageInterceptor.kt +++ b/src/ja/comicfuz/src/eu/kanade/tachiyomi/extension/ja/comicfuz/ImageInterceptor.kt @@ -3,14 +3,14 @@ package eu.kanade.tachiyomi.extension.ja.comicfuz import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.ResponseBody.Companion.asResponseBody +import okio.buffer +import okio.cipherSource import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec object ImageInterceptor : Interceptor { - private val mediaType = "image/jpeg".toMediaType() - private inline val AES: Cipher get() = Cipher.getInstance("AES/CBC/PKCS7Padding") @@ -29,7 +29,7 @@ object ImageInterceptor : Interceptor { ).build(), ) - val body = response.body.bytes() + val body = response .decode(key.decodeHex(), iv.decodeHex()) return response.newBuilder() @@ -37,9 +37,9 @@ object ImageInterceptor : Interceptor { .build() } - private fun ByteArray.decode(key: ByteArray, iv: ByteArray) = AES.let { + private fun Response.decode(key: ByteArray, iv: ByteArray) = AES.let { it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - it.doFinal(this).toResponseBody(mediaType) + body.source().cipherSource(it).buffer().asResponseBody("image/jpeg".toMediaType()) } private fun String.decodeHex(): ByteArray { diff --git a/src/pt/randomscan/src/eu/kanade/tachiyomi/extension/pt/randomscan/LuraZipInterceptor.kt b/src/pt/randomscan/src/eu/kanade/tachiyomi/extension/pt/randomscan/LuraZipInterceptor.kt index 1397a9f8f..19a7872e9 100644 --- a/src/pt/randomscan/src/eu/kanade/tachiyomi/extension/pt/randomscan/LuraZipInterceptor.kt +++ b/src/pt/randomscan/src/eu/kanade/tachiyomi/extension/pt/randomscan/LuraZipInterceptor.kt @@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.extension.pt.randomscan import eu.kanade.tachiyomi.lib.zipinterceptor.ZipInterceptor import okhttp3.Request import okhttp3.Response -import java.io.ByteArrayInputStream +import okio.BufferedSource +import okio.buffer +import okio.cipherSource import java.io.InputStream import java.nio.charset.StandardCharsets import java.security.MessageDigest @@ -13,18 +15,18 @@ import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec class LuraZipInterceptor : ZipInterceptor() { - fun decryptFile(encryptedData: ByteArray, keyBytes: ByteArray): ByteArray { + private fun decryptFile(encryptedData: BufferedSource, keyBytes: ByteArray): BufferedSource { val keyHash = MessageDigest.getInstance("SHA-256").digest(keyBytes) val key: SecretKey = SecretKeySpec(keyHash, "AES") - val counter = encryptedData.copyOfRange(0, 8) + val counter = encryptedData.readByteArray(8) val iv = IvParameterSpec(counter) val cipher = Cipher.getInstance("AES/CTR/NoPadding") cipher.init(Cipher.DECRYPT_MODE, key, iv) - val decryptedData = cipher.doFinal(encryptedData.copyOfRange(8, encryptedData.size)) + val decryptedData = encryptedData.cipherSource(cipher).buffer() return decryptedData } @@ -35,11 +37,11 @@ class LuraZipInterceptor : ZipInterceptor() { override fun zipGetByteStream(request: Request, response: Response): InputStream { val keyData = listOf("obra_id", "slug", "cap_id", "cap_slug").joinToString("") { - request.url.queryParameterValues(it).first().toString() + request.url.queryParameter(it)!! }.toByteArray(StandardCharsets.UTF_8) - val encryptedData = response.body.bytes() + val encryptedData = response.body.source() val decryptedData = decryptFile(encryptedData, keyData) - return ByteArrayInputStream(decryptedData) + return decryptedData.inputStream() } } diff --git a/src/zh/manwa/build.gradle b/src/zh/manwa/build.gradle index 2f096d5f7..ce635527e 100644 --- a/src/zh/manwa/build.gradle +++ b/src/zh/manwa/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Manwa' extClass = '.Manwa' - extVersionCode = 12 + extVersionCode = 13 isNsfw = true } diff --git a/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt index 0aaf7cc66..e563cda61 100644 --- a/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt +++ b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt @@ -28,7 +28,9 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.ResponseBody.Companion.asResponseBody +import okio.buffer +import okio.cipherSource import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable @@ -57,13 +59,12 @@ class Manwa : ParsedHttpSource(), ConfigurableSource { val originalResponse: Response = chain.proceed(chain.request()) if (originalResponse.request.url.toString().endsWith("?v=20220724")) { // Decrypt images in mangas - val orgBody = originalResponse.body.bytes() val key = "my2ecret782ecret".toByteArray() val aesKey = SecretKeySpec(key, "AES") val cipher = Cipher.getInstance("AES/CBC/NOPADDING") cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(key)) - val result = cipher.doFinal(orgBody) - val newBody = result.toResponseBody("image/webp".toMediaTypeOrNull()) + val result = originalResponse.body.source().cipherSource(cipher).buffer() + val newBody = result.asResponseBody("image/webp".toMediaTypeOrNull()) originalResponse.newBuilder() .body(newBody) .build() diff --git a/src/zh/onemanhua/build.gradle b/src/zh/onemanhua/build.gradle index 726e56f84..ec1d71d21 100644 --- a/src/zh/onemanhua/build.gradle +++ b/src/zh/onemanhua/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'COLAMANGA' extClass = '.Onemanhua' - extVersionCode = 25 + extVersionCode = 26 isNsfw = true } diff --git a/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/ColaMangaImageInterceptor.kt b/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/ColaMangaImageInterceptor.kt index 173452890..c0eadaaa0 100644 --- a/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/ColaMangaImageInterceptor.kt +++ b/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/ColaMangaImageInterceptor.kt @@ -3,15 +3,14 @@ package eu.kanade.tachiyomi.extension.zh.onemanhua import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody +import okhttp3.ResponseBody.Companion.asResponseBody +import okio.buffer +import okio.cipherSource import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec class ColaMangaImageInterceptor : Interceptor { - private val iv = "0000000000000000".toByteArray() - private val mediaType = "image/jpeg".toMediaType() - override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val response = chain.proceed(request) @@ -22,12 +21,13 @@ class ColaMangaImageInterceptor : Interceptor { val key = request.url.fragment!!.substringAfter(KEY_PREFIX).toByteArray() val output = Cipher.getInstance("AES/CBC/PKCS7Padding").let { + val iv = "0000000000000000".toByteArray() it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - it.doFinal(response.body.bytes()) + response.body.source().cipherSource(it).buffer() } return response.newBuilder() - .body(output.toResponseBody(mediaType)) + .body(output.asResponseBody("image/jpeg".toMediaType())) .build() }