使用PHP或JS解码Youtube密码签名的最佳方法

osh3o9ms  于 2023-06-21  发布在  PHP
关注(0)|答案(4)|浏览(133)

当字典中的use_cipher_signature = true通过http://www.youtube.com/get_video_info?&video_id=Video_Id返回时,Youtube正在对一些视频使用密码签名
示例id:_JQH3G0cCtY
加密签名实际上是任何人都可以用几组工作签名来调试的加扰签名。但是Youtube一直在改变加扰算法。我已经看到很少的Youtube视频下载器,这是工作顺利,没有受到影响,通过这个不断变化的游戏。我认为他们正在检查播放器并从播放器文件中提取解码脚本,因此它使他们继续前进。
我需要一些关于在这种情况下使用的实际技术的帮助。我知道'youtube-dl' -一个下载视频的python程序。由于我不擅长Python,我认为他们使用相同的方法。
这里还有一个用户脚本JS文件:http://userscripts.org/scripts/show/25105,它也在做同样的事情。
任何有关在PHP或JS中解码密码的明智方法的帮助都将受到赞赏。

wixjitnu

wixjitnu1#

URL结构和密码通过Youtube不断变化。目前,对密码签名进行解码的最佳方法解释如下:
Youtube中的加密签名只是“加密”签名,您必须根据播放器文件(HTML5播放器或Flash播放器)中的算法重新排列它们。
例如,http://www.youtube.com/watch?v=UxxajLWwzqY目前使用以下HTML5播放器文件://s.ytimg.com/yts/jsbin/html5player-vfltdb6U3.js
在该文件中,您可以通过搜索“sig”来轻松搜索签名解密代码。在这种情况下,算法是:

function bz(a) {
    a = a.split("");
    a = cz(a, 61);
    a = cz(a, 5);
    a = a.reverse();
    a = a.slice(2);
    a = cz(a, 69);
    a = a.slice(2);
    a = a.reverse();
    return a.join("")
}

function cz(a, b) {
    var c = a[0];
    a[0] = a[b % a.length];
    a[b] = c;
    return a
};

上面是解密代码。
但要注意,当他们改变播放器文件时,它会不断变化,所以你必须在正在使用的播放器文件上保持点击。
此外,要下载具有密码签名的视频,您必须注意发送相同的cookie,使用相同的用户代理头,从相同的IP地址发送请求,并在提取后不久发送请求。所有这些都是或曾经在某些时候需要的
如果对密码解密算法感兴趣,请访问CipherAPI
另一个很酷的API:TYstream API

gz5pxeao

gz5pxeao2#

旧的s.ytimg.com/yts/jsbin/html5player-vfltdb6U3.js现在是404,我认为新的URL看起来更像hxxps://s.ytimg.com/yts/jsbin/player-en_US-vfl_cdzrt/base.js
如果你搜索JavaScript,你会发现一段代码,看起来像这样

function(a,b,c)
{
a=new Mr(a);
a.set("alr","yes");a.set("keepalive","yes");a.set("ratebypass","yes");a.set("mime",(0,window.encodeURIComponent)(b.mimeType.split(";")[0]));c&&a.set("signature",xr(c));return a},Jt=function(a,b){var c=Yr(b,"id"),c=c.replace(":",";");..............
}

上面的代码调用的xr函数如下所示

xr=function(a)
{
a=a.split("");
wr.rF(a,54);
wr.fs(a,75);
wr.N0(a,1);
wr.rF(a,52);
wr.N0(a,3);
wr.fs(a,31);
wr.rF(a,16);
wr.fs(a,38);
return a.join("")
}

在那之后,我开始有点迷失在JavaScript中,我自己也可以得到一些帮助,但是在代码项目中谈论这一点会让你陷入困境。

ki1q1bka

ki1q1bka3#

我把Akhilesh对iOS用户的回答翻译成了Swift 3:

func decryptSignature(signature:String)->String {
    return bz(signature)
}
func bz(_ a:String)->String {
    var arrayA = Array(a.characters)
    arrayA = cz(arrayA, 61)
    arrayA = cz(arrayA, 5)
    arrayA = arrayA.reversed()
    arrayA = Array(arrayA[2..<arrayA.count])
    arrayA = cz(arrayA, 69)
    arrayA = Array(arrayA[2..<arrayA.count])
    arrayA = arrayA.reversed()
    return String(arrayA)
}
func cz(_ a:Array<Character>, _ b:Int)->Array<Character> {
    var arrayA = a
    let c = a[0]
    arrayA[0] = a[b % a.count];
    arrayA[b] = c
    return arrayA
}

但我认为这种算法是不够的,它解密的签名遵循一个特定的规则。事实上,根据this perl脚本(来自Jamie Zawinski的youtubedown),算法每次都会改变,脚本会在几天内收集规则和算法的列表!到目前为止,在密码中只使用了三个命令,因此我们可以简洁地表示它们:

# - r  = reverse the string;
# - sN = slice from character N to the end;
# - wN = swap 0th and Nth character.

我认为最好的方法是实现这样的东西:

func decryptChiper(_ commands:String, signature:String)->String {
    var a = Array(signature.characters)
    let cmdArray:[String]! = commands.components(separatedBy: " ")
    for cmd in cmdArray {
        var value:Int!
        if cmd.characters.count>1 {
            let secondChar = cmd.index(cmd.startIndex, offsetBy: 1)
            value = Int(cmd.substring(from:secondChar))
        }

        switch cmd[cmd.startIndex] {
        case "r": a = a.reversed()
        case "s":
            if let sliceFrom = value {
                a = Array(a[sliceFrom..<a.count])
            }
        case "w":
            if let swapValue = value {
                a = swap(a,swapValue)
            }
        default:break
        }
    }
    return String(a)
}

func swap(_ a:Array<Character>, _ b:Int)->Array<Character> {
    var arrayA = a
    let c = a[0]
    arrayA[0] = a[b % a.count];
    arrayA[b] = c
    return arrayA
}

用法:

下面是Akhilesh的例子:

let signature = "D3D3434498D70C3080D9B084E48350F6519A9E9A71094.25F300BB180DDDD918EE0EBEDD174EE5D874EFEFF"
let decryptedSign = decryptChiper("w61 w5 r s2 w69 s2 r", signature: signature )
print(decryptedSign)

输出

33D3494498D70C3E80D9B084E48350F6519A9E9A71094.25F300BB180DDDDD18EE0EBEDD174EE5D874E
z0qdvdin

z0qdvdin4#

好吧,我希望我能早点发现这篇文章,因为我花了几天时间试图理解为什么我的旧base.js停止工作。我的是build b128dda0:

https://www.youtube.com/s/player/b128dda0/player_ias.vflset/en_GB/base.js

在他们发布了一个新版本之后

https://www.youtube.com/s/player/8c7583ff/player_ias.vflset/en_GB/base.js

并改变了挑战(他们称之为密码,而它实际上是一个挑战)计算算法。也许对于新手来说,谁偶然发现这个页面,让我解释一下,这是如何工作的。当你进入youtube页面时,他们会给你发送一个html页面,其中包含两种不同形式的挑战,有时使用一种,有时使用另一种,不知道他们如何决定使用哪种。
位于(在使用JSON.parse从文本中创建对象之后):

streamingData -> formats[XXX] array -> url

streamingData -> formats[XXX] array -> signatureCipher

你获取这个url并将url搜索转换为一个URLSearchParams对象,然后:

URLSearchParams.prototype.get.call(URLSearchParams_Instance, "n")

“n”是挑战。但是,如果您不在youtube页面上(例如使用“www.example.com”代码),或者您在youtube.com上导航,在这种情况下,youtube实际上并没有打开新页面,而是将新内容添加到现有页面中,那么您将在以下位置遇到挑战:youtube.com/embed/" code), or you navigate around youtube.com , in which case youtube doesn't actually open new pages, but rather ajaxes new content into the existing page, then you get your challenge at:

https://www.youtube.com/youtubei/v1/player?key=

但为什么这个挑战是必要的呢?好吧,你使用来自www.example.com的html,但是他们的视频流数据位于www.example.com上,所以它是这样工作的:youtube.com , but their video streaming data is located on googlevideo.com , so it works like this:

- youtube.com sends you the challenge, either thru html, or through a POST to: youtubei/v1/player

- base.js script takes this challenge and solves it using some math, which the googies constantly change, which is really annoying, because I made some changes to base.js and really don't feel like transferring all my changes to a new script, which they are constantly updating, changing the challenge solving algorithms along the way all the time

- the solved challenge is sent as a string to googlevideo.com as an URL search param:

    https://XXX.googlevideo.com/videoplayback?n=Your_Solved_Challenge

如果你错了,你会从服务器上得到一个很大的403。花了我一些时间才找到这两个(是的,该死的,有两个挑战功能!)功能。问题是,谷歌在发布代码时随机选择函数和对象名称,但我认为这两个函数保留了它们的名称,它们分别被称为“vsa”和“Wla”。你不解决“vsa”,你从服务器得到403。但是!但是!你没能解决第二个功能,你得到了视频流,你可以观看,但是Googies是阴险的,他们会以正常速度的1/10向你发送流,这意味着视频搜索,甚至观看变得真正慢。
因此,我没有将我所有的更改移动到新脚本中,而是将它们更新的挑战函数移动到我的旧脚本中。问题解决了对吧我打开一个视频进行测试,它仍然慢得像地狱。试着用新的剧本-完美地工作。我错过了什么?因此,我检查发送到“www.example.com”的XMLHttpRequest的主体。我得到两个主体-旧脚本发送的主体和新脚本发送的主体,美化它们,取消转义并比较差异。youtube.com/youtubei/v1/player唯一的区别是: Nothing there! The only differences are:

visitorData -> string from html page, randomly chosen. irrelevant

appInstallData -> string from html page, randomly chosen. irrelevant

deviceExperimentId -> string from html page, randomly chosen. irrelevant

clientScreenNonce -> ???

cpn -> ???

adSignalsInfo array -> you like ads?

signatureTimestamp -> well, just a time signature, right? totally irrelevant, I am sure

所以我想,也许在标题上有区别。比较头,新脚本发送“INNERTUBE_CONTEXT_CLIENT_VERSION”,我自己的脚本没有,因为如果你发送不安全的头,现代浏览器将首先发送OPTIONS请求,这不适合我,因为我使用本地html页面来ajax youtube html到本地iframe中使用XMLHttpRequest来避免crossorigin iframe问题,其中来自不同来源的帧不能相互访问。现在你可能会问,你是如何设法让视频在本地iframe中工作的,考虑到它发送“file://”作为Origin?好吧,我告诉我的浏览器代理在调用"youtubei/v1/player"时用youtube主机(https://www.youtube.com)替换"file://",lol,并使用String.prototype.replace在XMLHttpRequest返回的responseText的头部添加一个新的BASE

<base href='https://www.youtube.com/'>

另外,我必须在base.js中显式地将location.protocol设置为"https:",并将调用重定向到:

captions.js
heartbeat.js
annotations_module.js

因为这些脚本依赖于base.js,所以当googies在更新过程中更改了他们的base.js函数/对象名称时,原始脚本将无法工作(他们将尝试通过新名称调用base.js中的函数),另外我对它们进行了一些更改。
好吧,实际上,基于Chrome的浏览器(不知道Firefox)可以被欺骗接受虚假的OPTIONS请求。告诉你的本地代理继续寻找"Access-Control-Request-Headers",如果你不想让请求到达服务器,立即发送一个200,而不把请求传递给服务器+一个新的"Access-Control-Allow-Headers"包含所有的头Chrome/Edge等。但这很烦人,所以我禁用了base.js中所有不安全的头文件。

所以我也在新脚本中禁用了所有不安全的头文件,看看会发生什么。好吧,你猜怎么着,它仍然工作,我正在全速获取www.example.com数据!googlevideo.com我错过了什么?!所以我仔细看看挑战。这是...“youtubei/v1/player”正在向我发送一个19个字符的挑战(由我的旧“Wla”函数使用),而新脚本正在获得一个18个字符的挑战字符串。youtube服务器如何区分它们?我不知所措。发送的标题是相同的,发送的正文几乎相同,只有微不足道的差异,这是怎么回事?所以我删除身体属性和对象不同-"visitorData"?没有"appInstallData"没有"设备实验ID"没什么"广告信号信息"没有"clientScreenNonce"什么都没有。"CPN"?什么都没有。到底怎么回事?除了可笑的不相关的"signatureTimestamp"属性之外,什么都没有留下!不可能!我删除它,请求挑战...这个"signatureTimestamp"根本不是时间戳。这是youtube播放器的实际版本号。我的旧剧本发了“19513”,新剧本发了“19515”。字符串丢失,youtube假定您拥有最新版本。所以我找到了保存版本号的函数,并将其替换为新的。现在我得到了新的挑战,它适用于新的挑战函数。 So I found the function which holds the build number and replaced it by the new one. Now I get the new challenge which works for the new challenge functions.
问题是,如果challenge位于html内部(作为“n”搜索参数)-参见上文,它将始终包含最新的挑战解决函数的挑战,而不是“youtubei/v1/player”POST请求,html不会区分不同的构建版本,因此旧的算法会导致问题。

相关问题