Replaced CryptoJS with javax.crypto for Comico (#13572)
This commit is contained in:
parent
6147a40686
commit
144d9bb004
|
@ -11,7 +11,6 @@ import javax.crypto.spec.SecretKeySpec
|
||||||
/**
|
/**
|
||||||
* Conforming with CryptoJS AES method
|
* Conforming with CryptoJS AES method
|
||||||
*/
|
*/
|
||||||
// see https://gist.github.com/thackerronak/554c985c3001b16810af5fc0eb5c358f
|
|
||||||
@Suppress("unused", "FunctionName")
|
@Suppress("unused", "FunctionName")
|
||||||
object CryptoAES {
|
object CryptoAES {
|
||||||
|
|
||||||
|
@ -22,10 +21,12 @@ object CryptoAES {
|
||||||
private const val KDF_DIGEST = "MD5"
|
private const val KDF_DIGEST = "MD5"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt
|
* Decrypt using CryptoJS defaults compatible method.
|
||||||
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
|
* Uses KDF equivalent to OpenSSL's EVP_BytesToKey function
|
||||||
|
*
|
||||||
|
* http://stackoverflow.com/a/29152379/4405051
|
||||||
|
* @param cipherText base64 encoded ciphertext
|
||||||
* @param password passphrase
|
* @param password passphrase
|
||||||
* @param cipherText encrypted string
|
|
||||||
*/
|
*/
|
||||||
fun decrypt(cipherText: String, password: String): String {
|
fun decrypt(cipherText: String, password: String): String {
|
||||||
try {
|
try {
|
||||||
|
@ -34,19 +35,52 @@ object CryptoAES {
|
||||||
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size)
|
val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size)
|
||||||
val md5: MessageDigest = MessageDigest.getInstance("MD5")
|
val md5: MessageDigest = MessageDigest.getInstance("MD5")
|
||||||
val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5)
|
val keyAndIV = generateKeyAndIV(32, 16, 1, saltBytes, password.toByteArray(Charsets.UTF_8), md5)
|
||||||
val cipher = Cipher.getInstance(HASH_CIPHER)
|
return decryptAES(cipherTextBytes,
|
||||||
val keyS = SecretKeySpec(keyAndIV!![0], AES)
|
keyAndIV?.get(0) ?: ByteArray(32),
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keyS, IvParameterSpec(keyAndIV!![1]))
|
keyAndIV?.get(1) ?: ByteArray(16))
|
||||||
return cipher.doFinal(cipherTextBytes).toString(Charsets.UTF_8)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return ""
|
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.
|
* 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
|
* This method is equivalent to OpenSSL's EVP_BytesToKey function
|
||||||
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
|
* (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.
|
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
|
||||||
|
|
|
@ -6,8 +6,12 @@ ext {
|
||||||
extName = 'Comico'
|
extName = 'Comico'
|
||||||
pkgNameSuffix = 'all.comico'
|
pkgNameSuffix = 'all.comico'
|
||||||
extClass = '.ComicoFactory'
|
extClass = '.ComicoFactory'
|
||||||
extVersionCode = 4
|
extVersionCode = 5
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(':lib-cryptoaes'))
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.comico
|
package eu.kanade.tachiyomi.extension.all.comico
|
||||||
|
|
||||||
import android.webkit.CookieManager
|
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.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
@ -44,10 +44,6 @@ open class Comico(
|
||||||
|
|
||||||
private val cookieManager by lazy { CookieManager.getInstance() }
|
private val cookieManager by lazy { CookieManager.getInstance() }
|
||||||
|
|
||||||
private val cryptoJs by lazy {
|
|
||||||
client.newCall(GET(CRYPTOJS)).execute().body!!.string()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val imgHeaders by lazy {
|
private val imgHeaders by lazy {
|
||||||
headersBuilder().set("Accept", ACCEPT_IMAGE).build()
|
headersBuilder().set("Accept", ACCEPT_IMAGE).build()
|
||||||
}
|
}
|
||||||
|
@ -166,14 +162,8 @@ open class Comico(
|
||||||
private fun paginate(route: String, page: Int) =
|
private fun paginate(route: String, page: Int) =
|
||||||
GET("$apiUrl/$route?pageNo=${page - 1}&pageSize=25", apiHeaders)
|
GET("$apiUrl/$route?pageNo=${page - 1}&pageSize=25", apiHeaders)
|
||||||
|
|
||||||
private fun String.decrypt() = QuickJs.create().use {
|
private fun String.decrypt() =
|
||||||
// javax.crypto.Cipher does not support empty IV
|
CryptoAES.decrypt(this, keyBytes, ivBytes)
|
||||||
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 val Response.data: JsonElement?
|
private val Response.data: JsonElement?
|
||||||
get() = json.parseToJsonElement(body!!.string()).jsonObject.also {
|
get() = json.parseToJsonElement(body!!.string()).jsonObject.also {
|
||||||
|
@ -206,8 +196,9 @@ open class Comico(
|
||||||
|
|
||||||
private const val AES_KEY = "a7fc9dc89f2c873d79397f8a0028a4cd"
|
private const val AES_KEY = "a7fc9dc89f2c873d79397f8a0028a4cd"
|
||||||
|
|
||||||
private const val CRYPTOJS =
|
private val keyBytes = AES_KEY.toByteArray(Charsets.UTF_8)
|
||||||
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"
|
|
||||||
|
private val ivBytes = ByteArray(16) // Zero filled array as IV
|
||||||
|
|
||||||
private const val ACCEPT_IMAGE =
|
private const val ACCEPT_IMAGE =
|
||||||
"image/avif,image/jxl,image/webp,image/*,*/*"
|
"image/avif,image/jxl,image/webp,image/*,*/*"
|
||||||
|
|
Loading…
Reference in New Issue