1

I've noticed that some Youtube videos are having an encrypted signature... I've googled the problem a bit and found out that Youtube is storing the Algorithm in their HTML5 Player's Java Script as you can see here : https://s.ytimg.com/yts/jsbin/html5player-iw_IL-vflC7Zf5J.js

I've found those functions :

function zn(a)
{
a=a.split("");
a=a.slice(2);
a=a.reverse();
a=An(a,7);
a=An(a,21);
a=a.reverse();
return a.join("")
}

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

Now, I have couple of questions :

First, If I have an encrypted signature, I need to put the signature through those 2 functions to get the decrypted one, or I have to write an opposite function to this one to decrypt the data?

Second, Is there a way to make it generic, or every time youtube updates their algorithm of the encrypted signatures, I have to re-write it again ? (the encrypted function)

Thanks!!

Amit
  • 683
  • 3
  • 12
  • 25

2 Answers2

1

Second, Is there a way to make it generic, or every time youtube updates their algorithm of the encrypted signatures, I have to re-write it again ? (the encrypted function)

You could pass in a string of the same length with known distinct values, like character String.fromCharCode(0), String.fromCharCode(1), ... - and see where they get moved to in the output string: that's all the information you need to reverse the transformation, as long as it's not destructive (i.e. the output string does end up the same length with each character appearing once).

First, If I have an encrypted signature, I need to put the signature through those 2 functions to get the decrypted one, or I have to write an opposite function to this one to decrypt the data?

Sounds like you can just try that and see which works...?

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 1) Then do you happen to have a copy of an encrypted signature and a decreypted one, so I can check if i wrote the function that decrypt successfully.. ? I didnt understand your answer as 2. What do you mean as pass a string. to where ? – Amit Jun 01 '14 at 08:08
  • 1) nope... but why do you need one? If you decrypt successfully, can't you see that the video's playable? 2) pass a string to `zn(a)` - basically you call it yourself with the special string and the output string is a kind of list of indices for where characters have been moved to: for example, if `zn` returns a string with 3 as the first character, then the 4th character must have been moved there - you know where to put it back to.... – Tony Delroy Jun 01 '14 at 09:30
  • What I have to pass in the above parameter of zn function? what is a in zn?? – Ars May 25 '17 at 05:33
  • @Ars: `a` is `String.fromCharCode(0), String.fromCharCode(1), ...` – Tony Delroy May 25 '17 at 07:37
  • can you tell me more in detail about it please what the value of a whats input of the above function will be?? – Ars May 26 '17 at 01:47
  • @Ars: it's been a couple years since I looked at youtube urls - there are aspects of the question and my own answer that I'm not sure about now, such as how to know the length you want `a` to be, but my idea was to start `a` as an empty string, then in a loop add `String.forCharCode(i)` for `0 <= i < N` (but I'm not sure now how you know what value `N` should be), then when you call `output = f(a)` you can see if and where each of the distinct characters you put into `a` were placed in `output`. But, that doesn't mean the operation can be reversed, as it might have been destructive. – Tony Delroy May 26 '17 at 14:54
0

Actually there is an alternating couple of patterns in some Javascript fragment for signature descrambling, each not really generic as some post may suggest or expect. Here in one solution

/* Java Class SignatureDecoder, Version 2019-01-03 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class SignatureDecoder
{
    String cacheUrl = null;
    String js = null;

    public SignatureDecoder()
    {
    }

    public String decode(String jsUrl, String rawSignature, Proxy proxy)
    {
        if (!loadJs(jsUrl, proxy))
        {
            return null;
        }

        // Get scramble function from script, e.g. ".signature=Aq("
        final String func = getSignFunction();
        if (func == null)
        {
            return null;
        }
        final String fs[] = getFuncScript(func);
        if (fs == null)
        {
            return null;
        }
        String var = null;
        String vs = "";
        final String ident = "[$A-Za-z0-9_]+";
        final String varPat = "(" + ident + ")\\." + ident + "\\([ ]*" + fs[1];
        final Pattern vpt = Pattern.compile(varPat);
        final Matcher vmt = vpt.matcher(fs[0]);
        if (vmt.find(0))
        {
            var = vmt.group(1);
            vs = getVarDef(js, var);
        }

        final String extract = vs + "\n" + fs[0];

        final ScriptEngineManager mgr = new ScriptEngineManager();
        final ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
        try
        {
            jsEngine.eval(extract);
            final Invocable inv = (Invocable) jsEngine;
            final String enc = (String) inv.invokeFunction(func, rawSignature);
            return enc;
        }
        catch (final ScriptException ex)
        {
            ex.printStackTrace();
        }
        catch (final NoSuchMethodException x)
        {
            x.printStackTrace();
        }
        return rawSignature;
    }

    public String decode(String jsUrl, String rawSignature)
    {
        return decode(jsUrl, rawSignature, null);
    }

    protected boolean loadJs(String jsUrl, Proxy proxy)
    {
        if (cacheUrl == null || !cacheUrl.equals(jsUrl))
        {
            cacheUrl = null;
            URL jsUrl1;
            try
            {
                jsUrl1 = new URL(jsUrl);
            }
            catch (final MalformedURLException e1)
            {
                e1.printStackTrace();
                return false;
            }
            try
            {
                BufferedReader in;
                final StringBuffer buf = new StringBuffer();
                final URLConnection conn = proxy == null ? jsUrl1.openConnection() : jsUrl1.openConnection(proxy);
                final InputStream is = conn.getInputStream();
                in = new BufferedReader(new InputStreamReader(is));
                String inputLine;
                while ((inputLine = in.readLine()) != null)
                {
                    buf.append(inputLine);
                    buf.append('\n');
                }
                is.close();
                this.js = buf.toString();
                this.cacheUrl = jsUrl;
            }
            catch (final UnknownHostException e)
            {
                System.err.println("Cannot access script at  " + jsUrl);
                return false;
            }
            catch (final IOException e)
            {
                System.err.println("Error loading script from  " + jsUrl);
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    // var zq={ID:function(a,b){a.splice(0,b)},Bd:function(a){a.reverse()},Px:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}};
    protected String getBlock(String script, int pos1)
    {
        String res = null;

        int pos = pos1;
        boolean stop = false;
        int brack = 0;
        boolean containsBlock = false;

        while (!stop)
        {
            final char ch = script.charAt(pos);
            pos++;
            switch (ch)
            {
                case '(':
                    brack++;
                    break;
                case '{':
                    brack++;
                    break;
                case '[':
                    brack++;
                    break;
                case ')':
                    brack--;
                    break;
                case ']':
                    brack--;
                    break;
                case '}':
                    brack--;
                    break;
                case ';':
                    stop = brack == 0;
                    break;
                case '\n':
                    stop = brack == 0;
                    break;
                default:
                    ;
            }
            if (brack > 0)
            {
                containsBlock = true;
            }
            else
            {
                stop = containsBlock;
            }
        }
        res = script.substring(pos1, pos);

        return res;
    }

    protected String getVarDef(String script, String var)
    {
        String res = null;

        final String varPat = "var[ ]+" + var.replace("$", "\\$") + "[ ]*=";

        final Pattern pt = Pattern.compile(varPat);
        final Matcher mt = pt.matcher(script);
        if (mt.find(0))
        {
            res = mt.group(0);
            final int posBlock = mt.end(0);
            final String block = getBlock(script, posBlock);
            res = res + block;
        }
        return res;
    }

    protected String[] getFuncScript(String func)
    {
        String res[] = null;

        final String funcDollar = func.replace("$", "\\$");

        final String paramPat = "[ ]*\\(([^\\)]+)\\)";
        String funcPat = "function[ ]+" + funcDollar + paramPat;

        Pattern pt = Pattern.compile(funcPat);
        Matcher mt = pt.matcher(js);
        boolean ok = mt.find(0);
        if (!ok)
        {
            funcPat = funcDollar + "=function" + paramPat;
            pt = Pattern.compile(funcPat);
            mt = pt.matcher(js);
            ok = mt.find(0);
        }
        if (ok)
        {
            res = new String[2];
            String scr = mt.group(0);
            final String arg = mt.group(1);
            final int posBlock = mt.end(0);
            final String block = getBlock(js, posBlock);
            scr = scr + block;
            res[0] = scr;
            res[1] = arg;
        }
        return res;
    }

    protected String getSignFunction()
    {
        if (js == null)
        {
            return null;
        }
        // Pattern 1
        String func = null;
        String funcPat = "\\.signature=([^\\(]+)\\(";
        Pattern pt = Pattern.compile(funcPat);
        Matcher mt = pt.matcher(js);
        if (mt.find(0))
        {
            func = mt.group(1);
        }

        // Pattern 2
        if (func == null)
        {
            funcPat = "\\(\\\"signature\\\",([^\\(]+)\\(";
        }
        pt = Pattern.compile(funcPat);
        mt = pt.matcher(js);
        if (mt.find(0))
        {
            func = mt.group(1);
        }

        // Pattern 3
        if (func == null)
        {
            final int pos = js.indexOf("set(\"alr\",\"yes\")");
            if (pos >= 0)
            {
                int pos1 = pos;
                while (pos1 > 0 && js.charAt(pos1) != '\n')
                {
                    pos1--;
                }
                int pos2 = pos;
                while (pos2 < js.length() && js.charAt(pos2) != '\n')
                {
                    pos2++;
                }
                final String js1 = js.substring(pos1 + 1, pos2);
                // d.set("alr","yes");c&&(b||(b="signature"),d.set(b,WK(c)));return d};
                final String ident = "[a-zA-Z0-9]+";
                funcPat = "(" + ident + ")\\.set\\(\\\"alr\\\",\\\"yes\\\"\\);";
                pt = Pattern.compile(funcPat);
                mt = pt.matcher(js1);
                if (mt.find(0))
                {
                    final String obj = mt.group(1);
                    final String funcPat2 = obj + "\\.set\\((" + ident + "|\\\"signature\\\"),(" + ident + ")\\(" + ident + "\\)\\)";
                    final Pattern pt2 = Pattern.compile(funcPat2);
                    final Matcher mt2 = pt2.matcher(js1);
                    func = null;
                    if (mt2.find(0))
                    {
                        func = mt2.group(2);
                    }
                    else
                    {
                        final String fp3 = "\\(([A-Za-z]+)\\(\\(0,window.decodeURIComponent";
                        pt = Pattern.compile(fp3);
                        final Matcher mt3 = pt.matcher(js1);
                        if (mt3.find(0))
                        {
                            func = mt3.group(1);
                        }
                    }
                }
            }
        }
        return func;
    }

    // Test
    public static void main(String args[])
    {
        final String rawSignature = "C98C987F29D5CC5C651A6FB867EFC316D09CDF015983.E7C61471A1569DEE963C2FB55B025B2FE63CD32929929";
        final String expRes = "923DC36EF2B520B59BF2C369EED9651A17416C7E.38C510FDC90D613CFE768BF6A156C5CC5D92F785";
        final String jsUrl = "http://s.ytimg.com/yts/jsbin/player-vflI0cIzU/de_DE/base.js";

        final String signature = new SignatureDecoder().decode(jsUrl, rawSignature);

        System.out.println("\nExpected:  " + expRes);
        System.out.println("Evaluated: " + signature);

        System.out.print("Test ");
        if (signature != null && expRes.equals(signature))
        {
            System.out.println("OK");
        }
        else
        {
            System.out.println("FAILED");
        }

    }

}
Sam Ginrich
  • 661
  • 6
  • 7