Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ colorpicker = "6b46b49"
conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything
constraintlayout = "2.2.1"
coreKtx = "1.18.0"
cryptography = "0.6.0"
desugar_jdk_libs_nio = "2.1.5"
dokkaGradlePlugin = "2.2.0"
espressoCore = "3.7.0"
Expand Down Expand Up @@ -69,6 +70,8 @@ conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref =
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
core = { module = "androidx.test:core" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
cryptography-core = { module = "dev.whyoleg.cryptography:cryptography-core", version.ref = "cryptography" }
cryptography-provider-optimal = { module = "dev.whyoleg.cryptography:cryptography-provider-optimal", version.ref = "cryptography" }
databinding = { module = "androidx.databinding:viewbinding", version.ref = "androidGradlePlugin" }
desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
Expand Down Expand Up @@ -128,6 +131,7 @@ kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref

[bundles]
coil = ["coil", "coil-network-okhttp"]
cryptography = ["cryptography-core", "cryptography-provider-optimal"]
lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"]
media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"]
navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"]
Expand Down
1 change: 1 addition & 0 deletions library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ kotlin {
implementation(libs.rhino) // Run JavaScript
implementation(libs.newpipeextractor)
implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit
implementation(libs.bundles.cryptography) // Cryptography
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import dev.whyoleg.cryptography.CryptographyProvider
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.AES
import java.net.URI
import java.nio.charset.StandardCharsets
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

class Bysezejataos : ByseSX() {
class Bysezejataos : ByseSX() {
override var name = "Bysezejataos"
override var mainUrl = "https://bysezejataos.com"
}
Expand All @@ -39,6 +39,8 @@ open class ByseSX : ExtractorApi() {
override var mainUrl = "https://byse.sx"
override val requiresReferer = true

private val aesGcm = CryptographyProvider.Default.get(AES.GCM)

private fun b64UrlDecode(s: String): ByteArray {
val fixed = s.replace('-', '+').replace('_', '/')
val pad = (4 - fixed.length % 4) % 4
Expand Down Expand Up @@ -83,31 +85,29 @@ open class ByseSX : ExtractorApi() {
return p1 + p2
}

@OptIn(DelicateCryptographyApi::class)
private fun decryptPlayback(playback: Playback): String? {
val keyBytes = buildAesKey(playback)
val ivBytes = b64UrlDecode(playback.iv)
val cipherBytes = b64UrlDecode(playback.payload)

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, ivBytes)
val secretKey = SecretKeySpec(keyBytes, "AES")
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
val aesKey = aesGcm.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes)
// 128-bit GCM tag (default)
val cipher = aesKey.cipher()
val plainBytes = cipher.decryptWithIvBlocking(ivBytes, cipherBytes)

val plainBytes = cipher.doFinal(cipherBytes)
var jsonStr = String(plainBytes, StandardCharsets.UTF_8)

if (jsonStr.startsWith("\uFEFF")) jsonStr = jsonStr.substring(1)

val root = try {
tryParseJson<PlaybackDecrypt>((jsonStr))
tryParseJson<PlaybackDecrypt>(jsonStr)
} catch (_: Exception) {
return null
}

return root?.sources?.firstOrNull()?.url
}


override suspend fun getUrl(
url: String,
referer: String?,
Expand All @@ -116,8 +116,7 @@ open class ByseSX : ExtractorApi() {
) {
val refererUrl = getBaseUrl(url)
val playbackRoot = getPlayback(url) ?: return
val streamUrl = decryptPlayback(playbackRoot.playback) ?: return

val streamUrl = decryptPlayback(playbackRoot.playback) ?: return

val headers = mapOf("Referer" to refererUrl)
M3u8Helper.generateM3u8(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import dev.whyoleg.cryptography.CryptographyProvider
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.AES
import dev.whyoleg.cryptography.algorithms.MD5
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

class Megacloud : Rabbitstream() {
override val name = "Megacloud"
Expand Down Expand Up @@ -63,7 +63,6 @@ class Megacloud : Rabbitstream() {

return indexPairs
}

}

class Dokicloud : Rabbitstream() {
Expand All @@ -80,6 +79,10 @@ open class Rabbitstream : ExtractorApi() {
open val embed = "ajax/embed-4"
open val key = "https://raw.githubusercontent.com/eatmynerds/key/e4/key.txt"

private val aesCbc = CryptographyProvider.Default.get(AES.CBC)
@OptIn(DelicateCryptographyApi::class)
private val md5Hasher = CryptographyProvider.Default.get(MD5).hasher()

override suspend fun getUrl(
url: String,
referer: String?,
Expand Down Expand Up @@ -123,8 +126,6 @@ open class Rabbitstream : ExtractorApi() {
)
)
}


}

open suspend fun extractRealKey(sources: String): Pair<String, String> {
Expand All @@ -141,8 +142,8 @@ open class Rabbitstream : ExtractorApi() {
private fun decrypt(input: String, key: String): String {
return decryptSourceUrl(
generateKey(
base64DecodeArray(input).copyOfRange(8, 16),
key.toByteArray()
salt = base64DecodeArray(input).copyOfRange(8, 16),
secret = key.toByteArray()
), input
)
}
Expand All @@ -157,20 +158,18 @@ open class Rabbitstream : ExtractorApi() {
return currentKey
}

private fun md5(input: ByteArray): ByteArray {
return MessageDigest.getInstance("MD5").digest(input)
}
private fun md5(input: ByteArray): ByteArray =
md5Hasher.hashBlocking(input)

@OptIn(DelicateCryptographyApi::class)
private fun decryptSourceUrl(decryptionKey: ByteArray, sourceUrl: String): String {
val cipherData = base64DecodeArray(sourceUrl)
val encrypted = cipherData.copyOfRange(16, cipherData.size)
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
aesCBC.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(decryptionKey.copyOfRange(0, 32), "AES"),
IvParameterSpec(decryptionKey.copyOfRange(32, decryptionKey.size))
)
val decryptedData = aesCBC?.doFinal(encrypted) ?: throw ErrorLoadingException("Cipher not found")
val keyBytes = decryptionKey.copyOfRange(0, 32)
val ivBytes = decryptionKey.copyOfRange(32, decryptionKey.size)

val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes)
val decryptedData = aesKey.cipher(padding = true).decryptWithIvBlocking(ivBytes, encrypted)
return String(decryptedData, StandardCharsets.UTF_8)
}

Expand All @@ -196,5 +195,4 @@ open class Rabbitstream : ExtractorApi() {
@JsonProperty("encrypted") val encrypted: Boolean? = null,
@JsonProperty("tracks") val tracks: List<Tracks?>? = emptyList(),
)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.fixUrl
import com.lagradost.cloudstream3.utils.newExtractorLink
import dev.whyoleg.cryptography.CryptographyProvider
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.AES
import java.net.URI
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

class Server1uns : VidStack() {
override var name = "Vidstack"
Expand All @@ -32,8 +32,7 @@ open class VidStack : ExtractorApi() {
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
)
{
) {
val headers = mapOf("User-Agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0")
val hash = url.substringAfterLast("#").substringAfter("/")
val baseurl = getBaseUrl(url)
Expand Down Expand Up @@ -93,16 +92,16 @@ open class VidStack : ExtractorApi() {
}

object AesHelper {
private const val TRANSFORMATION = "AES/CBC/PKCS5PADDING"
private val aesCbc = CryptographyProvider.Default.get(AES.CBC)

@OptIn(DelicateCryptographyApi::class)
fun decryptAES(inputHex: String, key: String, iv: String): String {
val cipher = Cipher.getInstance(TRANSFORMATION)
val secretKey = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "AES")
val ivSpec = IvParameterSpec(iv.toByteArray(Charsets.UTF_8))

cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
val decryptedBytes = cipher.doFinal(inputHex.hexToByteArray())
return String(decryptedBytes, Charsets.UTF_8)
val keyBytes = key.toByteArray(Charsets.UTF_8)
val ivBytes = iv.toByteArray(Charsets.UTF_8)
val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes)
val cipher = aesKey.cipher(padding = true)
val decrypted = cipher.decryptWithIvBlocking(ivBytes, inputHex.hexToByteArray())
return String(decrypted, Charsets.UTF_8)
}

private fun String.hexToByteArray(): ByteArray {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,86 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.base64DecodeArray
import com.lagradost.cloudstream3.base64Encode
import com.lagradost.cloudstream3.utils.AppUtils
import java.security.DigestException
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import dev.whyoleg.cryptography.CryptographyProvider
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.AES
import dev.whyoleg.cryptography.algorithms.MD5

object AesHelper {

private const val HASH = "AES/CBC/PKCS5PADDING"
private const val KDF = "MD5"
private val provider = CryptographyProvider.Default
private val aesCbc = provider.get(AES.CBC)
@OptIn(DelicateCryptographyApi::class)
private val md5Hasher = provider.get(MD5).hasher()

@OptIn(DelicateCryptographyApi::class)
fun cryptoAESHandler(
data: String,
pass: ByteArray,
encrypt: Boolean = true,
padding: String = HASH,
// padding parameter kept for API compatibility; PKCS7 is always used
@Suppress("UNUSED_PARAMETER") padding: String = "AES/CBC/PKCS5PADDING",
): String? {
val parse = AppUtils.tryParseJson<AesData>(data) ?: return null
val (key, iv) = generateKeyAndIv(
pass,
parse.s.hexToByteArray(),
password = pass,
salt = parse.s.hexToByteArray(),
ivLength = parse.iv.length / 2,
saltLength = parse.s.length / 2
) ?: return null
val cipher = Cipher.getInstance(padding)

val aesKey = aesCbc.keyDecoder().decodeFromByteArrayBlocking(AES.Key.Format.RAW, key)
val cipher = aesKey.cipher(padding = true)

return if (!encrypt) {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
String(cipher.doFinal(base64DecodeArray(parse.ct)))
val plainBytes = cipher.decryptWithIvBlocking(iv, base64DecodeArray(parse.ct))
String(plainBytes)
} else {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
base64Encode(cipher.doFinal(parse.ct.toByteArray()))
base64Encode(cipher.encryptWithIvBlocking(iv, parse.ct.toByteArray()))
}
}

// https://stackoverflow.com/a/41434590/8166854
fun generateKeyAndIv(
password: ByteArray,
salt: ByteArray,
hashAlgorithm: String = KDF,
keyLength: Int = 32,
ivLength: Int,
saltLength: Int,
iterations: Int = 1
): Pair<ByteArray,ByteArray>? {

val md = MessageDigest.getInstance(hashAlgorithm)
val digestLength = md.digestLength
val targetKeySize = keyLength + ivLength
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
val generatedData = ByteArray(requiredLength)
var generatedLength = 0

try {
md.reset()
iterations: Int = 1,
): Pair<ByteArray, ByteArray>? {
return try {
val digestLength = 16 // MD5 digest is always 16 bytes
val targetKeySize = keyLength + ivLength
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
val generatedData = ByteArray(requiredLength)
var generatedLength = 0

while (generatedLength < targetKeySize) {
if (generatedLength > 0)
md.update(
generatedData,
generatedLength - digestLength,
digestLength
)

md.update(password)
md.update(salt, 0, saltLength)
md.digest(generatedData, generatedLength, digestLength)
val hashFn = md5Hasher.createHashFunction()
if (generatedLength > 0) {
// update(source, startIndex, endIndex) — endIndex is exclusive
hashFn.update(generatedData, generatedLength - digestLength, generatedLength)
}
hashFn.update(password)
hashFn.update(salt, 0, saltLength)
val digest = hashFn.hashToByteArray()
digest.copyInto(generatedData, generatedLength)

for (i in 1 until iterations) {
md.update(generatedData, generatedLength, digestLength)
md.digest(generatedData, generatedLength, digestLength)
val iterFn = md5Hasher.createHashFunction()
iterFn.update(generatedData, generatedLength, generatedLength + digestLength)
val iterDigest = iterFn.hashToByteArray()
iterDigest.copyInto(generatedData, generatedLength)
}

generatedLength += digestLength
}
return generatedData.copyOfRange(0, keyLength) to generatedData.copyOfRange(keyLength, targetKeySize)
} catch (e: DigestException) {
return null

generatedData.copyOfRange(0, keyLength) to
generatedData.copyOfRange(keyLength, targetKeySize)
} catch (_: Exception) {
null
}
}

Expand All @@ -96,5 +99,4 @@ object AesHelper {
@JsonProperty("iv") val iv: String,
@JsonProperty("s") val s: String
)

}
}
Loading