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 {
extName = 'izneo (webtoons)'
extClass = '.IzneoFactory'
extVersionCode = 7
extVersionCode = 8
isNsfw = false
}

View File

@ -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)

View File

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

View File

@ -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 {

View File

@ -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()
}
}

View File

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

View File

@ -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()

View File

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

View File

@ -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()
}