Use CipherSource to decrypt responses by streaming (#10253)

This commit is contained in:
stevenyomi 2025-08-24 09:53:31 +00:00 committed by Draff
parent e4cd4833e0
commit f50bec002b
Signed by: Draff
GPG Key ID: E8A89F3211677653
9 changed files with 34 additions and 31 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'izneo (webtoons)' extName = 'izneo (webtoons)'
extClass = '.IzneoFactory' extClass = '.IzneoFactory'
extVersionCode = 7 extVersionCode = 8
isNsfw = false isNsfw = false
} }

View File

@ -4,14 +4,14 @@ import android.util.Base64
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response 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.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
object ImageInterceptor : Interceptor { object ImageInterceptor : Interceptor {
private val mediaType = "image/jpeg".toMediaType()
private inline val AES: Cipher private inline val AES: Cipher
get() = Cipher.getInstance("AES/CBC/PKCS7Padding") get() = Cipher.getInstance("AES/CBC/PKCS7Padding")
@ -31,7 +31,7 @@ object ImageInterceptor : Interceptor {
private fun Response.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.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) private fun String.atob() = Base64.decode(this, Base64.URL_SAFE)

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'COMIC FUZ' extName = 'COMIC FUZ'
extClass = '.ComicFuz' extClass = '.ComicFuz'
extVersionCode = 1 extVersionCode = 2
isNsfw = true isNsfw = true
} }

View File

@ -3,14 +3,14 @@ package eu.kanade.tachiyomi.extension.ja.comicfuz
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response 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.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
object ImageInterceptor : Interceptor { object ImageInterceptor : Interceptor {
private val mediaType = "image/jpeg".toMediaType()
private inline val AES: Cipher private inline val AES: Cipher
get() = Cipher.getInstance("AES/CBC/PKCS7Padding") get() = Cipher.getInstance("AES/CBC/PKCS7Padding")
@ -29,7 +29,7 @@ object ImageInterceptor : Interceptor {
).build(), ).build(),
) )
val body = response.body.bytes() val body = response
.decode(key.decodeHex(), iv.decodeHex()) .decode(key.decodeHex(), iv.decodeHex())
return response.newBuilder() return response.newBuilder()
@ -37,9 +37,9 @@ object ImageInterceptor : Interceptor {
.build() .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.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 { private fun String.decodeHex(): ByteArray {

View File

@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.extension.pt.randomscan
import eu.kanade.tachiyomi.lib.zipinterceptor.ZipInterceptor import eu.kanade.tachiyomi.lib.zipinterceptor.ZipInterceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import java.io.ByteArrayInputStream import okio.BufferedSource
import okio.buffer
import okio.cipherSource
import java.io.InputStream import java.io.InputStream
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
@ -13,18 +15,18 @@ import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
class LuraZipInterceptor : ZipInterceptor() { 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 keyHash = MessageDigest.getInstance("SHA-256").digest(keyBytes)
val key: SecretKey = SecretKeySpec(keyHash, "AES") val key: SecretKey = SecretKeySpec(keyHash, "AES")
val counter = encryptedData.copyOfRange(0, 8) val counter = encryptedData.readByteArray(8)
val iv = IvParameterSpec(counter) val iv = IvParameterSpec(counter)
val cipher = Cipher.getInstance("AES/CTR/NoPadding") val cipher = Cipher.getInstance("AES/CTR/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, key, iv) cipher.init(Cipher.DECRYPT_MODE, key, iv)
val decryptedData = cipher.doFinal(encryptedData.copyOfRange(8, encryptedData.size)) val decryptedData = encryptedData.cipherSource(cipher).buffer()
return decryptedData return decryptedData
} }
@ -35,11 +37,11 @@ class LuraZipInterceptor : ZipInterceptor() {
override fun zipGetByteStream(request: Request, response: Response): InputStream { override fun zipGetByteStream(request: Request, response: Response): InputStream {
val keyData = listOf("obra_id", "slug", "cap_id", "cap_slug").joinToString("") { 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) }.toByteArray(StandardCharsets.UTF_8)
val encryptedData = response.body.bytes() val encryptedData = response.body.source()
val decryptedData = decryptFile(encryptedData, keyData) val decryptedData = decryptFile(encryptedData, keyData)
return ByteArrayInputStream(decryptedData) return decryptedData.inputStream()
} }
} }

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'Manwa' extName = 'Manwa'
extClass = '.Manwa' extClass = '.Manwa'
extVersionCode = 12 extVersionCode = 13
isNsfw = true isNsfw = true
} }

View File

@ -28,7 +28,9 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response 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.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
@ -57,13 +59,12 @@ class Manwa : ParsedHttpSource(), ConfigurableSource {
val originalResponse: Response = chain.proceed(chain.request()) val originalResponse: Response = chain.proceed(chain.request())
if (originalResponse.request.url.toString().endsWith("?v=20220724")) { if (originalResponse.request.url.toString().endsWith("?v=20220724")) {
// Decrypt images in mangas // Decrypt images in mangas
val orgBody = originalResponse.body.bytes()
val key = "my2ecret782ecret".toByteArray() val key = "my2ecret782ecret".toByteArray()
val aesKey = SecretKeySpec(key, "AES") val aesKey = SecretKeySpec(key, "AES")
val cipher = Cipher.getInstance("AES/CBC/NOPADDING") val cipher = Cipher.getInstance("AES/CBC/NOPADDING")
cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(key)) cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(key))
val result = cipher.doFinal(orgBody) val result = originalResponse.body.source().cipherSource(cipher).buffer()
val newBody = result.toResponseBody("image/webp".toMediaTypeOrNull()) val newBody = result.asResponseBody("image/webp".toMediaTypeOrNull())
originalResponse.newBuilder() originalResponse.newBuilder()
.body(newBody) .body(newBody)
.build() .build()

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'COLAMANGA' extName = 'COLAMANGA'
extClass = '.Onemanhua' extClass = '.Onemanhua'
extVersionCode = 25 extVersionCode = 26
isNsfw = true isNsfw = true
} }

View File

@ -3,15 +3,14 @@ package eu.kanade.tachiyomi.extension.zh.onemanhua
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response 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.Cipher
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
class ColaMangaImageInterceptor : Interceptor { class ColaMangaImageInterceptor : Interceptor {
private val iv = "0000000000000000".toByteArray()
private val mediaType = "image/jpeg".toMediaType()
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() val request = chain.request()
val response = chain.proceed(request) val response = chain.proceed(request)
@ -22,12 +21,13 @@ class ColaMangaImageInterceptor : Interceptor {
val key = request.url.fragment!!.substringAfter(KEY_PREFIX).toByteArray() val key = request.url.fragment!!.substringAfter(KEY_PREFIX).toByteArray()
val output = Cipher.getInstance("AES/CBC/PKCS7Padding").let { val output = Cipher.getInstance("AES/CBC/PKCS7Padding").let {
val iv = "0000000000000000".toByteArray()
it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
it.doFinal(response.body.bytes()) response.body.source().cipherSource(it).buffer()
} }
return response.newBuilder() return response.newBuilder()
.body(output.toResponseBody(mediaType)) .body(output.asResponseBody("image/jpeg".toMediaType()))
.build() .build()
} }