diff --git a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt index 58d219f70..808758439 100644 --- a/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt +++ b/lib/cryptoaes/src/main/java/eu/kanade/tachiyomi/lib/cryptoaes/CryptoAES.kt @@ -11,7 +11,6 @@ import javax.crypto.spec.SecretKeySpec /** * Conforming with CryptoJS AES method */ -// see https://gist.github.com/thackerronak/554c985c3001b16810af5fc0eb5c358f @Suppress("unused", "FunctionName") object CryptoAES { @@ -22,10 +21,12 @@ object CryptoAES { private const val KDF_DIGEST = "MD5" /** - * Decrypt - * Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051 + * Decrypt using CryptoJS defaults compatible method. + * Uses KDF equivalent to OpenSSL's EVP_BytesToKey function + * + * http://stackoverflow.com/a/29152379/4405051 + * @param cipherText base64 encoded ciphertext * @param password passphrase - * @param cipherText encrypted string */ fun decrypt(cipherText: String, password: String): String { try { @@ -34,19 +35,52 @@ object CryptoAES { val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) val md5: MessageDigest = MessageDigest.getInstance("MD5") val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5) - val cipher = Cipher.getInstance(HASH_CIPHER) - val keyS = SecretKeySpec(keyAndIV!![0], AES) - cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(keyAndIV!![1])) - return cipher.doFinal(cipherTextBytes).toString(Charsets.UTF_8) + return decryptAES(cipherTextBytes, + keyAndIV?.get(0) ?: ByteArray(32), + keyAndIV?.get(1) ?: ByteArray(16)) } catch (e: Exception) { return "" } } + /** + * Decrypt using CryptoJS defaults compatible method. + * + * @param cipherText base64 encoded ciphertext + * @param keyBytes key as a bytearray + * @param ivBytes iv as a bytearray + */ + fun decrypt(cipherText: String, keyBytes: ByteArray, ivBytes: ByteArray): String { + return try { + val cipherTextBytes = Base64.decode(cipherText, Base64.DEFAULT) + decryptAES(cipherTextBytes, keyBytes, ivBytes) + } catch (e: Exception) { + "" + } + } + + /** + * Decrypt using CryptoJS defaults compatible method. + * + * @param cipherTextBytes encrypted text as a bytearray + * @param keyBytes key as a bytearray + * @param ivBytes iv as a bytearray + */ + private fun decryptAES(cipherTextBytes: ByteArray, keyBytes: ByteArray, ivBytes: ByteArray): String { + return try { + val cipher = Cipher.getInstance(HASH_CIPHER) + val keyS = SecretKeySpec(keyBytes, AES) + cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(ivBytes)) + cipher.doFinal(cipherTextBytes).toString(Charsets.UTF_8) + } catch (e: Exception) { + "" + } + } + /** * Generates a key and an initialization vector (IV) with the given salt and password. * - * Thanks to @Codo on Stackoverflow (https://stackoverflow.com/a/41434590) + * https://stackoverflow.com/a/41434590 * This method is equivalent to OpenSSL's EVP_BytesToKey function * (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c). * By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data. diff --git a/src/all/comico/build.gradle b/src/all/comico/build.gradle index 587a21794..d1ba4f2b6 100644 --- a/src/all/comico/build.gradle +++ b/src/all/comico/build.gradle @@ -6,8 +6,12 @@ ext { extName = 'Comico' pkgNameSuffix = 'all.comico' extClass = '.ComicoFactory' - extVersionCode = 4 + extVersionCode = 5 isNsfw = true } apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib-cryptoaes')) +} \ No newline at end of file diff --git a/src/all/comico/src/eu/kanade/tachiyomi/extension/all/comico/Comico.kt b/src/all/comico/src/eu/kanade/tachiyomi/extension/all/comico/Comico.kt index 8964c896d..e0bbfa456 100644 --- a/src/all/comico/src/eu/kanade/tachiyomi/extension/all/comico/Comico.kt +++ b/src/all/comico/src/eu/kanade/tachiyomi/extension/all/comico/Comico.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.extension.all.comico import android.webkit.CookieManager -import app.cash.quickjs.QuickJs +import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.source.model.FilterList @@ -44,10 +44,6 @@ open class Comico( private val cookieManager by lazy { CookieManager.getInstance() } - private val cryptoJs by lazy { - client.newCall(GET(CRYPTOJS)).execute().body!!.string() - } - private val imgHeaders by lazy { headersBuilder().set("Accept", ACCEPT_IMAGE).build() } @@ -166,14 +162,8 @@ open class Comico( private fun paginate(route: String, page: Int) = GET("$apiUrl/$route?pageNo=${page - 1}&pageSize=25", apiHeaders) - private fun String.decrypt() = QuickJs.create().use { - // javax.crypto.Cipher does not support empty IV - val script = """ - const key = CryptoJS.enc.Utf8.parse('$AES_KEY'), iv = {words: []} - CryptoJS.AES.decrypt('$this', key, {iv}).toString(CryptoJS.enc.Utf8) - """ - it.evaluate(cryptoJs + script).toString() - } + private fun String.decrypt() = + CryptoAES.decrypt(this, keyBytes, ivBytes) private val Response.data: JsonElement? get() = json.parseToJsonElement(body!!.string()).jsonObject.also { @@ -206,8 +196,9 @@ open class Comico( private const val AES_KEY = "a7fc9dc89f2c873d79397f8a0028a4cd" - private const val CRYPTOJS = - "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js" + private val keyBytes = AES_KEY.toByteArray(Charsets.UTF_8) + + private val ivBytes = ByteArray(16) // Zero filled array as IV private const val ACCEPT_IMAGE = "image/avif,image/jxl,image/webp,image/*,*/*"