初识JavaScript逆向——以网易云音乐和招标网站为例

来自:互联网
时间:2024-03-01
阅读:

前言:需要掌握一定的JavaScript基础,能看懂基础的JavaScript代码。

我们平常在浏览网站的时候会看到许多加密的参数,如果需要知道它的原始数据,就需要知道整个加密过程,所以本篇文章就来介绍一下本人在初学逆向的时候一些笔记。
想要获取加密过程大概来说有以下两个方法:
(1)通过浏览器的initiator中的栈调用来判断,因为initiator里面记录着浏览器执行JavaScript的过程。
(2)在search中搜索关键字,例如url中的一些路径或者加密参数。
下面通过两个案例来进行讲解。

网易云音乐

url地址:https://music.163.com/#/song?id=1325905146
按F12,打开浏览器的开发者工具,切换到Network模块,在界面上点击播放按钮,会产生数据。
初识JavaScript逆向——以网易云音乐和招标网站为例
一条一条数据进行查看,找到该首音乐的具体下载url地址是哪个。查看每一个流量的响应包,看看哪个流量的响应包中带有下载地址,这里在如下的数据包中找到(本文将该数据包的url地址记为music_url,方便文章的续写)。
初识JavaScript逆向——以网易云音乐和招标网站为例
看到url的后缀是m4a,熟悉音乐格式的都知道这是个音频文件的扩展名,复制该url地址,确认一下是否可以通过该url地址进行下载。
初识JavaScript逆向——以网易云音乐和招标网站为例
确实可以,但是我们可以看到界面的传参和流量包的传参不一致。
初识JavaScript逆向——以网易云音乐和招标网站为例
这大概率就是在请求服务器的时候把我们的传参进行了加密,我们这里的目标就是去知道整个加密过程,并且通过python代码将音乐下载下来。
介绍两种方法。

通过initiator找到加密过程

选中music_url,切换到initiator。
初识JavaScript逆向——以网易云音乐和招标网站为例
栈的特点是先进后出,所以initiator中从上至下就是计算机底层调用函数的过程,最下面的最先调用。点击最后一个调用的地方。
初识JavaScript逆向——以网易云音乐和招标网站为例
跳转到如下的地方。
初识JavaScript逆向——以网易云音乐和招标网站为例
这就说明点击播放的时候,是肯定会触发该地方的,所以在这行代码打断点。只需要在该行的前面点击一下,看到变蓝了即可。
初识JavaScript逆向——以网易云音乐和招标网站为例
再次点击播放,就可以看到停在打断点的这个地方了。
初识JavaScript逆向——以网易云音乐和招标网站为例
这里要特别关注右侧的Call Stack,这就是函数调用的过程。我们将鼠标移动到arguments上,就可以看到该变量的值。
初识JavaScript逆向——以网易云音乐和招标网站为例
或者在console里面输入arguments来查看。
初识JavaScript逆向——以网易云音乐和招标网站为例
发现这个参数还是加密的,所以此处就不是加密的过程,根据Call Stack继续往下查找。
初识JavaScript逆向——以网易云音乐和招标网站为例
看该行代码的参数。
初识JavaScript逆向——以网易云音乐和招标网站为例
还是加密的,继续往下找。直到执行到如下处,出现了明文。
初识JavaScript逆向——以网易云音乐和招标网站为例
在该函数的上面都是密文,执行到该函数才是明文,说明该函数的上一个函数就是加密的过程了,着重看一下。
初识JavaScript逆向——以网易云音乐和招标网站为例
下面代码就是整个参数加密的过程。
初识JavaScript逆向——以网易云音乐和招标网站为例
查看"X6R"和"e6c"这两个参数的值。
初识JavaScript逆向——以网易云音乐和招标网站为例
参数是加密后的,但是这个url地址好像不是music_url,所在我们在该函数的开始设置断点。
初识JavaScript逆向——以网易云音乐和招标网站为例
这里要介绍下调试的三个按钮。最左边的那个按钮的作用是让程序继续执行,直到下一个断点位置或者运行完毕;中间那个按钮的作用是执行到下一行代码(不论当前行是否是函数调用);最右边的那个按钮的作用是向后走一步(如果有函数,进入函数;如果没函数或者是JavaScript原生函数,向后走一步)。
初识JavaScript逆向——以网易云音乐和招标网站为例
点击最左边的按钮,看"X6R"的值。
初识JavaScript逆向——以网易云音乐和招标网站为例
还是不对,继续放。再放了两次之后,发现"X6R"的值发生了变化。
初识JavaScript逆向——以网易云音乐和招标网站为例
跟music_url进行比较,非常相似。
初识JavaScript逆向——以网易云音乐和招标网站为例
"e6c"的值也发生了变化,出现了明文。
初识JavaScript逆向——以网易云音乐和招标网站为例
这就对了,这时就需要一步一步往下跟,看看是如何加密的,点击中间那个按钮。在执行到如下代码之前,参数还是明文。
初识JavaScript逆向——以网易云音乐和招标网站为例
执行完该行代码,参数就被加密了,说明加密过程就在这行代码上。(其实看到JSON.stringify函数,就可以猜测加密过程99%在这里)
初识JavaScript逆向——以网易云音乐和招标网站为例
着重看下该行代码。
var bVi7b = window.asrsea(JSON.stringify(i6c), bsu6o(["流泪", "强"]), bsu6o(Xo0x.md), bsu6o(["爱心", "女孩", "惊恐", "大笑"]));
有四个参数,console输出一下看看每个参数的值是什么。
初识JavaScript逆向——以网易云音乐和招标网站为例
后面三个参数都是固定的,最前面那个参数的ids就是歌的id号了,也就是url地址栏中显示的参数。再看下window.asrsea是如何实现的,鼠标移动到函数上面,会有location提示。
初识JavaScript逆向——以网易云音乐和招标网站为例
点进去定位到如下函数。
初识JavaScript逆向——以网易云音乐和招标网站为例
该函数中调用了a、b、c三个函数,就在d函数的上面。
初识JavaScript逆向——以网易云音乐和招标网站为例
找到加密过程了,接下来我们只需要把加密过程的相关函数扒下来即可。

这里介绍下如何抠代码
(1)找到入口,把入口拿下来,尝试着一个函数一个函数的去填补。过程非常曲折,直到最终的结果产生,并且和你的预期相符
(2)找到代码的边界
(3)直接找第三方库(这个需要经验)

第一种抠代码方法

先介绍一下CryptoJS库,这个库是JavaScript中的一个跟加密相关的库。需要在npm官网上进行下载。
npm官网:https://www.npmjs.com/
搜索"crypto-js",第一个就是。
初识JavaScript逆向——以网易云音乐和招标网站为例
点进去,复制安装命令即可。
初识JavaScript逆向——以网易云音乐和招标网站为例
由于默认的下载源在国外,下载速度慢,所以这里需要对其进行换源。找到个人主目录下的.npmrc文件,用记事本打开,添加上如下代码。如果没有该文件,就创建一个。

点击查看代码
registry=http://registry.npmmirror.com/

disturl=https://registry.npmmirror.com/-/binary/node/
# node-sass预编译二进制文件下载地址
sass_binary_site=https://registry.npmmirror.com/-/binary/node-sass
# sharp预编译共享库, 截止2022-09-20 sharp@0.31.0的预编译共享库并未同步到镜像, 入安装失败可切换到sharp@0.30.7使用
sharp_libvips_binary_host=https://registry.npmmirror.com/-/binary/sharp-libvips
python_mirror=https://registry.npmmirror.com/-/binary/python/
electron_mirror=https://registry.npmmirror.com/-/binary/electron/
electron_builder_binaries_mirror=https://registry.npmmirror.com/-/binary/electron-builder-binaries/
# 无特殊配置参考{pkg-name}_binary_host_mirror={mirror}
canvas_binary_host_mirror=https://registry.npmmirror.com/-/binary/canvas
node_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/sqlite3
better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3

添加完成之后,保存文件,打开终端,输入安装命令。如果出现如下错误,就说明是权限问题。
初识JavaScript逆向——以网易云音乐和招标网站为例
只需要将下载目录所有用户的权限打开即可。
初识JavaScript逆向——以网易云音乐和招标网站为例
再执行安装命令,安装成功。
初识JavaScript逆向——以网易云音乐和招标网站为例
安装成功后,会出现如下的一个文件夹和两个文件。(不要删除)
初识JavaScript逆向——以网易云音乐和招标网站为例
如果想在js文件中引入crypto-js库,只需写上一句代码即可。
var CryptoJS = require("crypto-js")
接下来就是抠代码了,只需要将加密过程的相关函数全部扒下来即可,如果提示没有哪个函数,再去源代码中找即可。下面是完整的代码。

点击查看代码
var CryptoJS = require("crypto-js");
function biFromNumber(a) {
    var c, b = new BigInt;
    for (b.isNeg = 0 > a,
    a = Math.abs(a),
    c = 0; a > 0; )
        b.digits[c++] = a & maxDigitVal,
        a >>= biRadixBits;
    return b
}

var maxDigits, ZERO_ARRAY, bigZero, bigOne, dpl10, lr10, hexatrigesimalToChar, hexToChar, highBitMasks, lowBitMasks, biRadixBase = 2, biRadixBits = 16, bitsPerDigit = biRadixBits, biRadix = 65536, biHalfRadix = biRadix >>> 1, biRadixSquared = biRadix * biRadix, maxDigitVal = biRadix - 1, maxInteger = 9999999999999998;
setMaxDigits(20),
dpl10 = 15,
lr10 = biFromNumber(1e15),
hexatrigesimalToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"),
hexToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"),
highBitMasks = new Array(0,32768,49152,57344,61440,63488,64512,65024,65280,65408,65472,65504,65520,65528,65532,65534,65535),
lowBitMasks = new Array(0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535);


function BigInt(a) {
    this.digits = "boolean" == typeof a && 1 == a ? null : ZERO_ARRAY.slice(0),
    this.isNeg = !1
}

function setMaxDigits(a) {
    maxDigits = a,
    ZERO_ARRAY = new Array(maxDigits);
    for (var b = 0; b < ZERO_ARRAY.length; b++)
        ZERO_ARRAY[b] = 0;
    bigZero = new BigInt,
    bigOne = new BigInt,
    bigOne.digits[0] = 1
}

function charToHex(a) {
    var h, b = 48, c = b + 9, d = 97, e = d + 25, f = 65, g = 90;
    return h = a >= b && c >= a ? a - b : a >= f && g >= a ? 10 + a - f : a >= d && e >= a ? 10 + a - d : 0
}
function hexToDigit(a) {
    var d, b = 0, c = Math.min(a.length, 4);
    for (d = 0; c > d; ++d)
        b <<= 4,
        b |= charToHex(a.charCodeAt(d));
    return b
}

function biFromHex(a) {
    var d, e, b = new BigInt, c = a.length;
    for (d = c,
    e = 0; d > 0; d -= 4,
    ++e)
        b.digits[e] = hexToDigit(a.substr(Math.max(d - 4, 0), Math.min(d, 4)));
    return b
}

function biHighIndex(a) {
    for (var b = a.digits.length - 1; b > 0 && 0 == a.digits[b]; )
        --b;
    return b
}

function biCopy(a) {
    var b = new BigInt(!0);
    return b.digits = a.digits.slice(0),
    b.isNeg = a.isNeg,
    b
}

function biNumBits(a) {
    var e, b = biHighIndex(a), c = a.digits[b], d = (b + 1) * bitsPerDigit;
    for (e = d; e > d - bitsPerDigit && 0 == (32768 & c); --e)
        c <<= 1;
    return e
}

function arrayCopy(a, b, c, d, e) {
    var g, h, f = Math.min(b + e, a.length);
    for (g = b,
    h = d; f > g; ++g,
    ++h)
        c[h] = a[g]
}

function biShiftLeft(a, b) {
    var e, f, g, h, c = Math.floor(b / bitsPerDigit), d = new BigInt;
    for (arrayCopy(a.digits, 0, d.digits, c, d.digits.length - c),
    e = b % bitsPerDigit,
    f = bitsPerDigit - e,
    g = d.digits.length - 1,
    h = g - 1; g > 0; --g,
    --h)
        d.digits[g] = d.digits[g] << e & maxDigitVal | (d.digits[h] & highBitMasks[e]) >>> f;
    return d.digits[0] = d.digits[g] << e & maxDigitVal,
    d.isNeg = a.isNeg,
    d
}

function biMultiplyByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, 0, c.digits, b, c.digits.length - b),
    c
}

function biCompare(a, b) {
    if (a.isNeg != b.isNeg)
        return 1 - 2 * Number(a.isNeg);
    for (var c = a.digits.length - 1; c >= 0; --c)
        if (a.digits[c] != b.digits[c])
            return a.isNeg ? 1 - 2 * Number(a.digits[c] > b.digits[c]) : 1 - 2 * Number(a.digits[c] < b.digits[c]);
    return 0
}

function biSubtract(a, b) {
    var c, d, e, f;
    if (a.isNeg != b.isNeg)
        b.isNeg = !b.isNeg,
        c = biAdd(a, b),
        b.isNeg = !b.isNeg;
    else {
        for (c = new BigInt,
        e = 0,
        f = 0; f < a.digits.length; ++f)
            d = a.digits[f] - b.digits[f] + e,
            c.digits[f] = 65535 & d,
            c.digits[f] < 0 && (c.digits[f] += biRadix),
            e = 0 - Number(0 > d);
        if (-1 == e) {
            for (e = 0,
            f = 0; f < a.digits.length; ++f)
                d = 0 - c.digits[f] + e,
                c.digits[f] = 65535 & d,
                c.digits[f] < 0 && (c.digits[f] += biRadix),
                e = 0 - Number(0 > d);
            c.isNeg = !a.isNeg
        } else
            c.isNeg = a.isNeg
    }
    return c
}

function biMultiplyDigit(a, b) {
    var c, d, e, f;
    for (result = new BigInt,
    c = biHighIndex(a),
    d = 0,
    f = 0; c >= f; ++f)
        e = result.digits[f] + a.digits[f] * b + d,
        result.digits[f] = e & maxDigitVal,
        d = e >>> biRadixBits;
    return result.digits[1 + c] = d,
    result
}

function biShiftRight(a, b) {
    var e, f, g, h, c = Math.floor(b / bitsPerDigit), d = new BigInt;
    for (arrayCopy(a.digits, c, d.digits, 0, a.digits.length - c),
    e = b % bitsPerDigit,
    f = bitsPerDigit - e,
    g = 0,
    h = g + 1; g < d.digits.length - 1; ++g,
    ++h)
        d.digits[g] = d.digits[g] >>> e | (d.digits[h] & lowBitMasks[e]) << f;
    return d.digits[d.digits.length - 1] >>>= e,
    d.isNeg = a.isNeg,
    d
}

function biDivideModulo(a, b) {
    var f, g, h, i, j, k, l, m, n, o, p, q, r, s, c = biNumBits(a), d = biNumBits(b), e = b.isNeg;
    if (d > c)
        return a.isNeg ? (f = biCopy(bigOne),
        f.isNeg = !b.isNeg,
        a.isNeg = !1,
        b.isNeg = !1,
        g = biSubtract(b, a),
        a.isNeg = !0,
        b.isNeg = e) : (f = new BigInt,
        g = biCopy(a)),
        new Array(f,g);
    for (f = new BigInt,
    g = a,
    h = Math.ceil(d / bitsPerDigit) - 1,
    i = 0; b.digits[h] < biHalfRadix; )
        b = biShiftLeft(b, 1),
        ++i,
        ++d,
        h = Math.ceil(d / bitsPerDigit) - 1;
    for (g = biShiftLeft(g, i),
    c += i,
    j = Math.ceil(c / bitsPerDigit) - 1,
    k = biMultiplyByRadixPower(b, j - h); -1 != biCompare(g, k); )
        ++f.digits[j - h],
        g = biSubtract(g, k);
    for (l = j; l > h; --l) {
        for (m = l >= g.digits.length ? 0 : g.digits[l],
        n = l - 1 >= g.digits.length ? 0 : g.digits[l - 1],
        o = l - 2 >= g.digits.length ? 0 : g.digits[l - 2],
        p = h >= b.digits.length ? 0 : b.digits[h],
        q = h - 1 >= b.digits.length ? 0 : b.digits[h - 1],
        f.digits[l - h - 1] = m == p ? maxDigitVal : Math.floor((m * biRadix + n) / p),
        r = f.digits[l - h - 1] * (p * biRadix + q),
        s = m * biRadixSquared + (n * biRadix + o); r > s; )
            --f.digits[l - h - 1],
            r = f.digits[l - h - 1] * (p * biRadix | q),
            s = m * biRadix * biRadix + (n * biRadix + o);
        k = biMultiplyByRadixPower(b, l - h - 1),
        g = biSubtract(g, biMultiplyDigit(k, f.digits[l - h - 1])),
        g.isNeg && (g = biAdd(g, k),
        --f.digits[l - h - 1])
    }
    return g = biShiftRight(g, i),
    f.isNeg = a.isNeg != e,
    a.isNeg && (f = e ? biAdd(f, bigOne) : biSubtract(f, bigOne),
    b = biShiftRight(b, i),
    g = biSubtract(b, g)),
    0 == g.digits[0] && 0 == biHighIndex(g) && (g.isNeg = !1),
    new Array(f,g)
}

function biDivide(a, b) {
    return biDivideModulo(a, b)[0]
}

function biDivideByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, b, c.digits, 0, c.digits.length - b),
    c
}

function biModuloByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, 0, c.digits, 0, b),
    c
}

function BarrettMu_modulo(a) {
    var i, b = biDivideByRadixPower(a, this.k - 1), c = biMultiply(b, this.mu), d = biDivideByRadixPower(c, this.k + 1), e = biModuloByRadixPower(a, this.k + 1), f = biMultiply(d, this.modulus), g = biModuloByRadixPower(f, this.k + 1), h = biSubtract(e, g);
    for (h.isNeg && (h = biAdd(h, this.bkplus1)),
    i = biCompare(h, this.modulus) >= 0; i; )
        h = biSubtract(h, this.modulus),
        i = biCompare(h, this.modulus) >= 0;
    return h
}

function biMultiply(a, b) {
    var d, h, i, k, c = new BigInt, e = biHighIndex(a), f = biHighIndex(b);
    for (k = 0; f >= k; ++k) {
        for (d = 0,
        i = k,
        j = 0; e >= j; ++j,
        ++i)
            h = c.digits[i] + a.digits[j] * b.digits[k] + d,
            c.digits[i] = h & maxDigitVal,
            d = h >>> biRadixBits;
        c.digits[k + e + 1] = d
    }
    return c.isNeg = a.isNeg != b.isNeg,
    c
}

function BarrettMu_multiplyMod(a, b) {
    var c = biMultiply(a, b);
    return this.modulo(c)
}

function BarrettMu_powMod(a, b) {
    var d, e, c = new BigInt;
    for (c.digits[0] = 1,
    d = a,
    e = b; ; ) {
        if (0 != (1 & e.digits[0]) && (c = this.multiplyMod(c, d)),
        e = biShiftRight(e, 1),
        0 == e.digits[0] && 0 == biHighIndex(e))
            break;
        d = this.multiplyMod(d, d)
    }
    return c
}

function BarrettMu(a) {
    this.modulus = biCopy(a),
    this.k = biHighIndex(this.modulus) + 1;
    var b = new BigInt;
    b.digits[2 * this.k] = 1,
    this.mu = biDivide(b, this.modulus),
    this.bkplus1 = new BigInt,
    this.bkplus1.digits[this.k + 1] = 1,
    this.modulo = BarrettMu_modulo,
    this.multiplyMod = BarrettMu_multiplyMod,
    this.powMod = BarrettMu_powMod
}

function RSAKeyPAIr(a, b, c) {
    this.e = biFromHex(a),
    this.d = biFromHex(b),
    this.m = biFromHex(c),
    this.chunkSize = 2 * biHighIndex(this.m),
    this.radix = 16,
    this.barrett = new BarrettMu(this.m)
}

function reverseStr(a) {
    var c, b = "";
    for (c = a.length - 1; c > -1; --c)
        b += a.charAt(c);
    return b
}

function digitToHex(a) {
    var b = 15
      , c = "";
    for (i = 0; 4 > i; ++i)
        c += hexToChar[a & b],
        a >>>= 4;
    return reverseStr(c)
}

function biToHex(a) {
    var d, b = "";
    for (biHighIndex(a),
    d = biHighIndex(a); d > -1; --d)
        b += digitToHex(a.digits[d]);
    return b
}

function encryptedString(a, b) {
    for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; )
        c[e] = b.charCodeAt(e),
        e++;
    for (; 0 != c.length % a.chunkSize; )
        c[e++] = 0;
    for (f = c.length,
    g = "",
    e = 0; f > e; e += a.chunkSize) {
        for (j = new BigInt,
        h = 0,
        i = e; i < e + a.chunkSize; ++h)
            j.digits[h] = c[i++],
            j.digits[h] += c[i++] << 8;
        k = a.barrett.powMod(j, a.e),
        l = 16 == a.radix ? biToHex(k) : biToString(k, a.radix),
        g += l + " "
    }
    return g.substring(0, g.length - 1)
}

function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
    for (d = 0; a > d; d += 1)
        e = Math.random() * b.length,
        e = Math.floor(e),
        c += b.charAt(e);
    return c
}
function b(a, b) {
    // 当你在js代码中看到了如下格式,他是一个第三方库
    var c = CryptoJS.enc.Utf8.parse(b)
      , d = CryptoJS.enc.Utf8.parse("0102030405060708")
      , e = CryptoJS.enc.Utf8.parse(a)
      , f = CryptoJS.AES.encrypt(e, c, {
        iv: d,
        mode: CryptoJS.mode.CBC
    });
    return f.toString()
}
function c(a, b, c) {
    var d, e;
    return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
}
function d(d, e, f, g) {
    var h = {}
      , i = a(16);
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}
    function e(a, b, d, e) {
        var f = {};
        return f.encText = c(a + e, b, d),
        f
    }

// 本地做测试, window is not defined 这里你用的是node环境,node里面没有window
let i0x = {
    "csrf_token": "",
    "encodeType": "aac",
    "ids": "[1325905146]",
    "level": "standard",
};

var res = d(JSON.stringify(i0x), '010001', '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7', '0CoJUm6Qyw8W8jud');
console.log(res);
// function fn(i0x) {
//     return d(JSON.stringify(i0x), '010001', '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7', '0CoJUm6Qyw8W8jud');
// }

运行结果如下,这就说明我们找到的加密代码是没有问题的。
初识JavaScript逆向——以网易云音乐和招标网站为例
接下来通过python代码来调用JavaScript代码实现下载。

点击查看代码
# 在python中完成网易云音乐的下载
from functools import partial  # 锁定参数
import subprocess

subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests

url = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token="
data = {"csrf_token": "", "encodeType": "aac", "ids": "[1325905146]", "level": "standard", }

# 把参数进行加密,得到密文,发请求
f = open("网易云.js", mode="r", encoding="utf-8")
js_code = f.read()
f.close()
js = execjs.compile(js_code)
mi = js.call("fn", data)
print(mi)

resp = requests.post(url, data={"params": mi['encText'], "encSecKey": mi['encSecKey'], }, headers={
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"})
song_url = resp.json()['data'][0]['url']
print(url)
song_resp = requests.get(song_url, headers={
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"})
with open("mymusic.m4a", mode="wb") as f:
    f.write(song_resp.content)

第二种抠代码方法

注意到d函数其实是位于一个自运行函数内的。
初识JavaScript逆向——以网易云音乐和招标网站为例
所以可以直接把这一块代码复制下来。再看c函数调用的setMaxDigits函数的位置。
初识JavaScript逆向——以网易云音乐和招标网站为例
其他函数都是跟setMaxDigits函数平行的,所以往上翻翻。
初识JavaScript逆向——以网易云音乐和招标网站为例
89行上面是一个函数的结束。
初识JavaScript逆向——以网易云音乐和招标网站为例
90行下面是一个自运行函数的开始。
初识JavaScript逆向——以网易云音乐和招标网站为例
所以这中间的代码就可以一块复制下来。

点击查看代码

function RSAKeyPair(a, b, c) {
    this.e = biFromHex(a),
    this.d = biFromHex(b),
    this.m = biFromHex(c),
    this.chunkSize = 2 * biHighIndex(this.m),
    this.radix = 16,
    this.barrett = new BarrettMu(this.m)
}
function twoDigit(a) {
    return (10 > a ? "0" : "") + String(a)
}
function encryptedString(a, b) {
    for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; )
        c[e] = b.charCodeAt(e),
        e++;
    for (; 0 != c.length % a.chunkSize; )
        c[e++] = 0;
    for (f = c.length,
    g = "",
    e = 0; f > e; e += a.chunkSize) {
        for (j = new BigInt,
        h = 0,
        i = e; i < e + a.chunkSize; ++h)
            j.digits[h] = c[i++],
            j.digits[h] += c[i++] << 8;
        k = a.barrett.powMod(j, a.e),
        l = 16 == a.radix ? biToHex(k) : biToString(k, a.radix),
        g += l + " "
    }
    return g.substring(0, g.length - 1)
}
function decryptedString(a, b) {
    var e, f, g, h, c = b.split(" "), d = "";
    for (e = 0; e < c.length; ++e)
        for (h = 16 == a.radix ? biFromHex(c[e]) : biFromString(c[e], a.radix),
        g = a.barrett.powMod(h, a.d),
        f = 0; f <= biHighIndex(g); ++f)
            d += String.fromCharCode(255 & g.digits[f], g.digits[f] >> 8);
    return 0 == d.charCodeAt(d.length - 1) && (d = d.substring(0, d.length - 1)),
    d
}
function setMaxDigits(a) {
    maxDigits = a,
    ZERO_ARRAY = new Array(maxDigits);
    for (var b = 0; b < ZERO_ARRAY.length; b++)
        ZERO_ARRAY[b] = 0;
    bigZero = new BigInt,
    bigOne = new BigInt,
    bigOne.digits[0] = 1
}
function BigInt(a) {
    this.digits = "boolean" == typeof a && 1 == a ? null : ZERO_ARRAY.slice(0),
    this.isNeg = !1
}
function biFromDecimal(a) {
    for (var d, e, f, b = "-" == a.charAt(0), c = b ? 1 : 0; c < a.length && "0" == a.charAt(c); )
        ++c;
    if (c == a.length)
        d = new BigInt;
    else {
        for (e = a.length - c,
        f = e % dpl10,
        0 == f && (f = dpl10),
        d = biFromNumber(Number(a.substr(c, f))),
        c += f; c < a.length; )
            d = biAdd(biMultiply(d, lr10), biFromNumber(Number(a.substr(c, dpl10)))),
            c += dpl10;
        d.isNeg = b
    }
    return d
}
function biCopy(a) {
    var b = new BigInt(!0);
    return b.digits = a.digits.slice(0),
    b.isNeg = a.isNeg,
    b
}
function biFromNumber(a) {
    var c, b = new BigInt;
    for (b.isNeg = 0 > a,
    a = Math.abs(a),
    c = 0; a > 0; )
        b.digits[c++] = a & maxDigitVal,
        a >>= biRadixBits;
    return b
}
function reverseStr(a) {
    var c, b = "";
    for (c = a.length - 1; c > -1; --c)
        b += a.charAt(c);
    return b
}
function biToString(a, b) {
    var d, e, c = new BigInt;
    for (c.digits[0] = b,
    d = biDivideModulo(a, c),
    e = hexatrigesimalToChar[d[1].digits[0]]; 1 == biCompare(d[0], bigZero); )
        d = biDivideModulo(d[0], c),
        digit = d[1].digits[0],
        e += hexatrigesimalToChar[d[1].digits[0]];
    return (a.isNeg ? "-" : "") + reverseStr(e)
}
function biToDecimal(a) {
    var c, d, b = new BigInt;
    for (b.digits[0] = 10,
    c = biDivideModulo(a, b),
    d = String(c[1].digits[0]); 1 == biCompare(c[0], bigZero); )
        c = biDivideModulo(c[0], b),
        d += String(c[1].digits[0]);
    return (a.isNeg ? "-" : "") + reverseStr(d)
}
function digitToHex(a) {
    var b = 15
      , c = "";
    for (i = 0; 4 > i; ++i)
        c += hexToChar[a & b],
        a >>>= 4;
    return reverseStr(c)
}
function biToHex(a) {
    var d, b = "";
    for (biHighIndex(a),
    d = biHighIndex(a); d > -1; --d)
        b += digitToHex(a.digits[d]);
    return b
}
function charToHex(a) {
    var h, b = 48, c = b + 9, d = 97, e = d + 25, f = 65, g = 90;
    return h = a >= b && c >= a ? a - b : a >= f && g >= a ? 10 + a - f : a >= d && e >= a ? 10 + a - d : 0
}
function hexToDigit(a) {
    var d, b = 0, c = Math.min(a.length, 4);
    for (d = 0; c > d; ++d)
        b <<= 4,
        b |= charToHex(a.charCodeAt(d));
    return b
}
function biFromHex(a) {
    var d, e, b = new BigInt, c = a.length;
    for (d = c,
    e = 0; d > 0; d -= 4,
    ++e)
        b.digits[e] = hexToDigit(a.substr(Math.max(d - 4, 0), Math.min(d, 4)));
    return b
}
function biFromString(a, b) {
    var g, h, i, j, c = "-" == a.charAt(0), d = c ? 1 : 0, e = new BigInt, f = new BigInt;
    for (f.digits[0] = 1,
    g = a.length - 1; g >= d; g--)
        h = a.charCodeAt(g),
        i = charToHex(h),
        j = biMultiplyDigit(f, i),
        e = biAdd(e, j),
        f = biMultiplyDigit(f, b);
    return e.isNeg = c,
    e
}
function biDump(a) {
    return (a.isNeg ? "-" : "") + a.digits.join(" ")
}
function biAdd(a, b) {
    var c, d, e, f;
    if (a.isNeg != b.isNeg)
        b.isNeg = !b.isNeg,
        c = biSubtract(a, b),
        b.isNeg = !b.isNeg;
    else {
        for (c = new BigInt,
        d = 0,
        f = 0; f < a.digits.length; ++f)
            e = a.digits[f] + b.digits[f] + d,
            c.digits[f] = 65535 & e,
            d = Number(e >= biRadix);
        c.isNeg = a.isNeg
    }
    return c
}
function biSubtract(a, b) {
    var c, d, e, f;
    if (a.isNeg != b.isNeg)
        b.isNeg = !b.isNeg,
        c = biAdd(a, b),
        b.isNeg = !b.isNeg;
    else {
        for (c = new BigInt,
        e = 0,
        f = 0; f < a.digits.length; ++f)
            d = a.digits[f] - b.digits[f] + e,
            c.digits[f] = 65535 & d,
            c.digits[f] < 0 && (c.digits[f] += biRadix),
            e = 0 - Number(0 > d);
        if (-1 == e) {
            for (e = 0,
            f = 0; f < a.digits.length; ++f)
                d = 0 - c.digits[f] + e,
                c.digits[f] = 65535 & d,
                c.digits[f] < 0 && (c.digits[f] += biRadix),
                e = 0 - Number(0 > d);
            c.isNeg = !a.isNeg
        } else
            c.isNeg = a.isNeg
    }
    return c
}
function biHighIndex(a) {
    for (var b = a.digits.length - 1; b > 0 && 0 == a.digits[b]; )
        --b;
    return b
}
function biNumBits(a) {
    var e, b = biHighIndex(a), c = a.digits[b], d = (b + 1) * bitsPerDigit;
    for (e = d; e > d - bitsPerDigit && 0 == (32768 & c); --e)
        c <<= 1;
    return e
}
function biMultiply(a, b) {
    var d, h, i, k, c = new BigInt, e = biHighIndex(a), f = biHighIndex(b);
    for (k = 0; f >= k; ++k) {
        for (d = 0,
        i = k,
        j = 0; e >= j; ++j,
        ++i)
            h = c.digits[i] + a.digits[j] * b.digits[k] + d,
            c.digits[i] = h & maxDigitVal,
            d = h >>> biRadixBits;
        c.digits[k + e + 1] = d
    }
    return c.isNeg = a.isNeg != b.isNeg,
    c
}
function biMultiplyDigit(a, b) {
    var c, d, e, f;
    for (result = new BigInt,
    c = biHighIndex(a),
    d = 0,
    f = 0; c >= f; ++f)
        e = result.digits[f] + a.digits[f] * b + d,
        result.digits[f] = e & maxDigitVal,
        d = e >>> biRadixBits;
    return result.digits[1 + c] = d,
    result
}
function arrayCopy(a, b, c, d, e) {
    var g, h, f = Math.min(b + e, a.length);
    for (g = b,
    h = d; f > g; ++g,
    ++h)
        c[h] = a[g]
}
function biShiftLeft(a, b) {
    var e, f, g, h, c = Math.floor(b / bitsPerDigit), d = new BigInt;
    for (arrayCopy(a.digits, 0, d.digits, c, d.digits.length - c),
    e = b % bitsPerDigit,
    f = bitsPerDigit - e,
    g = d.digits.length - 1,
    h = g - 1; g > 0; --g,
    --h)
        d.digits[g] = d.digits[g] << e & maxDigitVal | (d.digits[h] & highBitMasks[e]) >>> f;
    return d.digits[0] = d.digits[g] << e & maxDigitVal,
    d.isNeg = a.isNeg,
    d
}
function biShiftRight(a, b) {
    var e, f, g, h, c = Math.floor(b / bitsPerDigit), d = new BigInt;
    for (arrayCopy(a.digits, c, d.digits, 0, a.digits.length - c),
    e = b % bitsPerDigit,
    f = bitsPerDigit - e,
    g = 0,
    h = g + 1; g < d.digits.length - 1; ++g,
    ++h)
        d.digits[g] = d.digits[g] >>> e | (d.digits[h] & lowBitMasks[e]) << f;
    return d.digits[d.digits.length - 1] >>>= e,
    d.isNeg = a.isNeg,
    d
}
function biMultiplyByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, 0, c.digits, b, c.digits.length - b),
    c
}
function biDivideByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, b, c.digits, 0, c.digits.length - b),
    c
}
function biModuloByRadixPower(a, b) {
    var c = new BigInt;
    return arrayCopy(a.digits, 0, c.digits, 0, b),
    c
}
function biCompare(a, b) {
    if (a.isNeg != b.isNeg)
        return 1 - 2 * Number(a.isNeg);
    for (var c = a.digits.length - 1; c >= 0; --c)
        if (a.digits[c] != b.digits[c])
            return a.isNeg ? 1 - 2 * Number(a.digits[c] > b.digits[c]) : 1 - 2 * Number(a.digits[c] < b.digits[c]);
    return 0
}
function biDivideModulo(a, b) {
    var f, g, h, i, j, k, l, m, n, o, p, q, r, s, c = biNumBits(a), d = biNumBits(b), e = b.isNeg;
    if (d > c)
        return a.isNeg ? (f = biCopy(bigOne),
        f.isNeg = !b.isNeg,
        a.isNeg = !1,
        b.isNeg = !1,
        g = biSubtract(b, a),
        a.isNeg = !0,
        b.isNeg = e) : (f = new BigInt,
        g = biCopy(a)),
        new Array(f,g);
    for (f = new BigInt,
    g = a,
    h = Math.ceil(d / bitsPerDigit) - 1,
    i = 0; b.digits[h] < biHalfRadix; )
        b = biShiftLeft(b, 1),
        ++i,
        ++d,
        h = Math.ceil(d / bitsPerDigit) - 1;
    for (g = biShiftLeft(g, i),
    c += i,
    j = Math.ceil(c / bitsPerDigit) - 1,
    k = biMultiplyByRadixPower(b, j - h); -1 != biCompare(g, k); )
        ++f.digits[j - h],
        g = biSubtract(g, k);
    for (l = j; l > h; --l) {
        for (m = l >= g.digits.length ? 0 : g.digits[l],
        n = l - 1 >= g.digits.length ? 0 : g.digits[l - 1],
        o = l - 2 >= g.digits.length ? 0 : g.digits[l - 2],
        p = h >= b.digits.length ? 0 : b.digits[h],
        q = h - 1 >= b.digits.length ? 0 : b.digits[h - 1],
        f.digits[l - h - 1] = m == p ? maxDigitVal : Math.floor((m * biRadix + n) / p),
        r = f.digits[l - h - 1] * (p * biRadix + q),
        s = m * biRadixSquared + (n * biRadix + o); r > s; )
            --f.digits[l - h - 1],
            r = f.digits[l - h - 1] * (p * biRadix | q),
            s = m * biRadix * biRadix + (n * biRadix + o);
        k = biMultiplyByRadixPower(b, l - h - 1),
        g = biSubtract(g, biMultiplyDigit(k, f.digits[l - h - 1])),
        g.isNeg && (g = biAdd(g, k),
        --f.digits[l - h - 1])
    }
    return g = biShiftRight(g, i),
    f.isNeg = a.isNeg != e,
    a.isNeg && (f = e ? biAdd(f, bigOne) : biSubtract(f, bigOne),
    b = biShiftRight(b, i),
    g = biSubtract(b, g)),
    0 == g.digits[0] && 0 == biHighIndex(g) && (g.isNeg = !1),
    new Array(f,g)
}
function biDivide(a, b) {
    return biDivideModulo(a, b)[0]
}
function biModulo(a, b) {
    return biDivideModulo(a, b)[1]
}
function biMultiplyMod(a, b, c) {
    return biModulo(biMultiply(a, b), c)
}
function biPow(a, b) {
    for (var c = bigOne, d = a; ; ) {
        if (0 != (1 & b) && (c = biMultiply(c, d)),
        b >>= 1,
        0 == b)
            break;
        d = biMultiply(d, d)
    }
    return c
}
function biPowMod(a, b, c) {
    for (var d = bigOne, e = a, f = b; ; ) {
        if (0 != (1 & f.digits[0]) && (d = biMultiplyMod(d, e, c)),
        f = biShiftRight(f, 1),
        0 == f.digits[0] && 0 == biHighIndex(f))
            break;
        e = biMultiplyMod(e, e, c)
    }
    return d
}
function BarrettMu(a) {
    this.modulus = biCopy(a),
    this.k = biHighIndex(this.modulus) + 1;
    var b = new BigInt;
    b.digits[2 * this.k] = 1,
    this.mu = biDivide(b, this.modulus),
    this.bkplus1 = new BigInt,
    this.bkplus1.digits[this.k + 1] = 1,
    this.modulo = BarrettMu_modulo,
    this.multiplyMod = BarrettMu_multiplyMod,
    this.powMod = BarrettMu_powMod
}
function BarrettMu_modulo(a) {
    var i, b = biDivideByRadixPower(a, this.k - 1), c = biMultiply(b, this.mu), d = biDivideByRadixPower(c, this.k + 1), e = biModuloByRadixPower(a, this.k + 1), f = biMultiply(d, this.modulus), g = biModuloByRadixPower(f, this.k + 1), h = biSubtract(e, g);
    for (h.isNeg && (h = biAdd(h, this.bkplus1)),
    i = biCompare(h, this.modulus) >= 0; i; )
        h = biSubtract(h, this.modulus),
        i = biCompare(h, this.modulus) >= 0;
    return h
}
function BarrettMu_multiplyMod(a, b) {
    var c = biMultiply(a, b);
    return this.modulo(c)
}
function BarrettMu_powMod(a, b) {
    var d, e, c = new BigInt;
    for (c.digits[0] = 1,
    d = a,
    e = b; ; ) {
        if (0 != (1 & e.digits[0]) && (c = this.multiplyMod(c, d)),
        e = biShiftRight(e, 1),
        0 == e.digits[0] && 0 == biHighIndex(e))
            break;
        d = this.multiplyMod(d, d)
    }
    return c
}
var maxDigits, ZERO_ARRAY, bigZero, bigOne, dpl10, lr10, hexatrigesimalToChar, hexToChar, highBitMasks, lowBitMasks, biRadixBase = 2, biRadixBits = 16, bitsPerDigit = biRadixBits, biRadix = 65536, biHalfRadix = biRadix >>> 1, biRadixSquared = biRadix * biRadix, maxDigitVal = biRadix - 1, maxInteger = 9999999999999998;
setMaxDigits(20),
dpl10 = 15,
lr10 = biFromNumber(1e15),
hexatrigesimalToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"),
hexToChar = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"),
highBitMasks = new Array(0,32768,49152,57344,61440,63488,64512,65024,65280,65408,65472,65504,65520,65528,65532,65534,65535),
lowBitMasks = new Array(0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535);


var CryptoJS = require("crypto-js");
function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
    for (d = 0; a > d; d += 1)
        e = Math.random() * b.length,
        e = Math.floor(e),
        c += b.charAt(e);
    return c
}
function b(a, b) {
    var c = CryptoJS.enc.Utf8.parse(b)
      , d = CryptoJS.enc.Utf8.parse("0102030405060708")
      , e = CryptoJS.enc.Utf8.parse(a)
      , f = CryptoJS.AES.encrypt(e, c, {
        iv: d,
        mode: CryptoJS.mode.CBC
    });
    return f.toString()
}
function c(a, b, c) {
    var d, e;
    return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
}
function d(d, e, f, g) {
    var h = {}
      , i = a(16);
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}
function e(a, b, d, e) {
    var f = {};
    return f.encText = c(a + e, b, d),
    f
}

    let i0x = {
    "csrf_token": "",
    "encodeType": "aac",
    "ids": "[1325905146]",
    "level": "standard",
};
let r = d(JSON.stringify(i0x), '010001', '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7', '0CoJUm6Qyw8W8jud');
console.log(r);

运行结果和第一种方法运行出来的结果是一致的。
初识JavaScript逆向——以网易云音乐和招标网站为例
python的代码跟第一种方法处一致,就不重复写了。

第三种抠代码方法

看到c函数中调用的setMaxDigits、RSAKeyPair和encryptedString三个函数,有经验的人就可以猜测大概率是rsa加密,它的相关加密过程可以直接从一个网站上复制。
url地址:https://ohdave.com/rsa/
打开界面,将如下三个链接中的代码完全复制进一个js文件中即可。
初识JavaScript逆向——以网易云音乐和招标网站为例
js文件代码:

点击查看代码
var CryptoJS = require("crypto-js");

// BigInt, a suite of routines for performing multiple-precision arithmetic in
// JavaScript.
//
// Copyright 1998-2005 David Shapiro.
//
// You may use, re-use, abuse,
// copy, and modify this code to your liking, but please keep this header.
// Thanks!
//
// Dave Shapiro
// dave@ohdave.com

// IMPORTANT THING: Be sure to set maxDigits according to your precision
// needs. Use the setMaxDigits() function to do this. See comments below.
//
// Tweaked by Ian Bunning
// Alterations:
// Fix bug in function biFromHex(s) to allow
// parsing of strings of length != 0 (mod 4)

// Changes made by Dave Shapiro as of 12/30/2004:
//
// The BigInt() constructor doesn't take a string anymore. If you want to
// create a BigInt from a string, use biFromDecimal() for base-10
// representations, biFromHex() for base-16 representations, or
// biFromString() for base-2-to-36 representations.
//
// biFromArray() has been removed. Use biCopy() instead, passing a BigInt
// instead of an array.
//
// The BigInt() constructor now only constructs a zeroed-out array.
// Alternatively, if you pass <true>, it won't construct any array. See the
// biCopy() method for an example of this.
//
// Be sure to set maxDigits depending on your precision needs. The default
// zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits()
// function. So use this function to set the variable. DON'T JUST SET THE
// VALUE. USE THE FUNCTION.
//
// ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By
// precalculating the zero array, we can just use slice(0) to make copies of
// it. Presumably this calls faster native code, as opposed to setting the
// elements one at a time. I have not done any timing tests to verify this
// claim.

// Max number = 10^16 - 2 = 9999999999999998;
//               2^53     = 9007199254740992;

var biRadixBase = 2;
var biRadixBits = 16;
var bitsPerDigit = biRadixBits;
var biRadix = 1 << 16; // = 2^16 = 65536
var biHalfRadix = biRadix >>> 1;
var biRadixSquared = biRadix * biRadix;
var maxDigitVal = biRadix - 1;
var maxInteger = 9999999999999998;

// maxDigits:
// Change this to accommodate your largest number size. Use setMaxDigits()
// to change it!
//
// In general, if you're working with numbers of size N bits, you'll need 2*N
// bits of storage. Each digit holds 16 bits. So, a 1024-bit key will need
//
// 1024 * 2 / 16 = 128 digits of storage.
//

var maxDigits;
var ZERO_ARRAY;
var bigZero, bigOne;

function setMaxDigits(value)
{
	maxDigits = value;
	ZERO_ARRAY = new Array(maxDigits);
	for (var iza = 0; iza < ZERO_ARRAY.length; iza++) ZERO_ARRAY[iza] = 0;
	bigZero = new BigInt();
	bigOne = new BigInt();
	bigOne.digits[0] = 1;
}

setMaxDigits(20);

// The maximum number of digits in base 10 you can convert to an
// integer without JavaScript throwing up on you.
var dpl10 = 15;
// lr10 = 10 ^ dpl10
var lr10 = biFromNumber(1000000000000000);

function BigInt(flag)
{
	if (typeof flag == "boolean" && flag == true) {
		this.digits = null;
	}
	else {
		this.digits = ZERO_ARRAY.slice(0);
	}
	this.isNeg = false;
}

function biFromDecimal(s)
{
	var isNeg = s.charAt(0) == '-';
	var i = isNeg ? 1 : 0;
	var result;
	// Skip leading zeros.
	while (i < s.length && s.charAt(i) == '0') ++i;
	if (i == s.length) {
		result = new BigInt();
	}
	else {
		var digitCount = s.length - i;
		var fgl = digitCount % dpl10;
		if (fgl == 0) fgl = dpl10;
		result = biFromNumber(Number(s.substr(i, fgl)));
		i += fgl;
		while (i < s.length) {
			result = biAdd(biMultiply(result, lr10),
			               biFromNumber(Number(s.substr(i, dpl10))));
			i += dpl10;
		}
		result.isNeg = isNeg;
	}
	return result;
}

function biCopy(bi)
{
	var result = new BigInt(true);
	result.digits = bi.digits.slice(0);
	result.isNeg = bi.isNeg;
	return result;
}

function biFromNumber(i)
{
	var result = new BigInt();
	result.isNeg = i < 0;
	i = Math.abs(i);
	var j = 0;
	while (i > 0) {
		result.digits[j++] = i & maxDigitVal;
		i >>= biRadixBits;
	}
	return result;
}

function reverseStr(s)
{
	var result = "";
	for (var i = s.length - 1; i > -1; --i) {
		result += s.charAt(i);
	}
	return result;
}

var hexatrigesimalToChar = new Array(
 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
 'u', 'v', 'w', 'x', 'y', 'z'
);

function biToString(x, radix)
	// 2 <= radix <= 36
{
	var b = new BigInt();
	b.digits[0] = radix;
	var qr = biDivideModulo(x, b);
	var result = hexatrigesimalToChar[qr[1].digits[0]];
	while (biCompare(qr[0], bigZero) == 1) {
		qr = biDivideModulo(qr[0], b);
		digit = qr[1].digits[0];
		result += hexatrigesimalToChar[qr[1].digits[0]];
	}
	return (x.isNeg ? "-" : "") + reverseStr(result);
}

function biToDecimal(x)
{
	var b = new BigInt();
	b.digits[0] = 10;
	var qr = biDivideModulo(x, b);
	var result = String(qr[1].digits[0]);
	while (biCompare(qr[0], bigZero) == 1) {
		qr = biDivideModulo(qr[0], b);
		result += String(qr[1].digits[0]);
	}
	return (x.isNeg ? "-" : "") + reverseStr(result);
}

var hexToChar = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                          'a', 'b', 'c', 'd', 'e', 'f');

function digitToHex(n)
{
	var mask = 0xf;
	var result = "";
	for (i = 0; i < 4; ++i) {
		result += hexToChar[n & mask];
		n >>>= 4;
	}
	return reverseStr(result);
}

function biToHex(x)
{
	var result = "";
	var n = biHighIndex(x);
	for (var i = biHighIndex(x); i > -1; --i) {
		result += digitToHex(x.digits[i]);
	}
	return result;
}

function charToHex(c)
{
	var ZERO = 48;
	var NINE = ZERO + 9;
	var littleA = 97;
	var littleZ = littleA + 25;
	var bigA = 65;
	var bigZ = 65 + 25;
	var result;

	if (c >= ZERO && c <= NINE) {
		result = c - ZERO;
	} else if (c >= bigA && c <= bigZ) {
		result = 10 + c - bigA;
	} else if (c >= littleA && c <= littleZ) {
		result = 10 + c - littleA;
	} else {
		result = 0;
	}
	return result;
}

function hexToDigit(s)
{
	var result = 0;
	var sl = Math.min(s.length, 4);
	for (var i = 0; i < sl; ++i) {
		result <<= 4;
		result |= charToHex(s.charCodeAt(i))
	}
	return result;
}

function biFromHex(s)
{
	var result = new BigInt();
	var sl = s.length;
	for (var i = sl, j = 0; i > 0; i -= 4, ++j) {
		result.digits[j] = hexToDigit(s.substr(Math.max(i - 4, 0), Math.min(i, 4)));
	}
	return result;
}

function biFromString(s, radix)
{
	var isNeg = s.charAt(0) == '-';
	var istop = isNeg ? 1 : 0;
	var result = new BigInt();
	var place = new BigInt();
	place.digits[0] = 1; // radix^0
	for (var i = s.length - 1; i >= istop; i--) {
		var c = s.charCodeAt(i);
		var digit = charToHex(c);
		var biDigit = biMultiplyDigit(place, digit);
		result = biAdd(result, biDigit);
		place = biMultiplyDigit(place, radix);
	}
	result.isNeg = isNeg;
	return result;
}

function biToBytes(x)
	// Returns a string containing raw bytes.
{
	var result = "";
	for (var i = biHighIndex(x); i > -1; --i) {
		result += digitToBytes(x.digits[i]);
	}
	return result;
}

function digitToBytes(n)
	// Convert two-byte digit to string containing both bytes.
{
	var c1 = String.fromCharCode(n & 0xff);
	n >>>= 8;
	var c2 = String.fromCharCode(n & 0xff);
	return c2 + c1;
}

function biDump(b)
{
	return (b.isNeg ? "-" : "") + b.digits.join(" ");
}

function biAdd(x, y)
{
	var result;

	if (x.isNeg != y.isNeg) {
		y.isNeg = !y.isNeg;
		result = biSubtract(x, y);
		y.isNeg = !y.isNeg;
	}
	else {
		result = new BigInt();
		var c = 0;
		var n;
		for (var i = 0; i < x.digits.length; ++i) {
			n = x.digits[i] + y.digits[i] + c;
			result.digits[i] = n & 0xffff;
			c = Number(n >= biRadix);
		}
		result.isNeg = x.isNeg;
	}
	return result;
}

function biSubtract(x, y)
{
	var result;
	if (x.isNeg != y.isNeg) {
		y.isNeg = !y.isNeg;
		result = biAdd(x, y);
		y.isNeg = !y.isNeg;
	} else {
		result = new BigInt();
		var n, c;
		c = 0;
		for (var i = 0; i < x.digits.length; ++i) {
			n = x.digits[i] - y.digits[i] + c;
			result.digits[i] = n & 0xffff;
			// Stupid non-conforming modulus operation.
			if (result.digits[i] < 0) result.digits[i] += biRadix;
			c = 0 - Number(n < 0);
		}
		// Fix up the negative sign, if any.
		if (c == -1) {
			c = 0;
			for (var i = 0; i < x.digits.length; ++i) {
				n = 0 - result.digits[i] + c;
				result.digits[i] = n & 0xffff;
				// Stupid non-conforming modulus operation.
				if (result.digits[i] < 0) result.digits[i] += biRadix;
				c = 0 - Number(n < 0);
			}
			// Result is opposite sign of arguments.
			result.isNeg = !x.isNeg;
		} else {
			// Result is same sign.
			result.isNeg = x.isNeg;
		}
	}
	return result;
}

function biHighIndex(x)
{
	var result = x.digits.length - 1;
	while (result > 0 && x.digits[result] == 0) --result;
	return result;
}

function biNumBits(x)
{
	var n = biHighIndex(x);
	var d = x.digits[n];
	var m = (n + 1) * bitsPerDigit;
	var result;
	for (result = m; result > m - bitsPerDigit; --result) {
		if ((d & 0x8000) != 0) break;
		d <<= 1;
	}
	return result;
}

function biMultiply(x, y)
{
	var result = new BigInt();
	var c;
	var n = biHighIndex(x);
	var t = biHighIndex(y);
	var u, uv, k;

	for (var i = 0; i <= t; ++i) {
		c = 0;
		k = i;
		for (j = 0; j <= n; ++j, ++k) {
			uv = result.digits[k] + x.digits[j] * y.digits[i] + c;
			result.digits[k] = uv & maxDigitVal;
			c = uv >>> biRadixBits;
		}
		result.digits[i + n + 1] = c;
	}
	// Someone give me a logical xor, please.
	result.isNeg = x.isNeg != y.isNeg;
	return result;
}

function biMultiplyDigit(x, y)
{
	var n, c, uv;

	result = new BigInt();
	n = biHighIndex(x);
	c = 0;
	for (var j = 0; j <= n; ++j) {
		uv = result.digits[j] + x.digits[j] * y + c;
		result.digits[j] = uv & maxDigitVal;
		c = uv >>> biRadixBits;
	}
	result.digits[1 + n] = c;
	return result;
}

function arrayCopy(src, srcStart, dest, destStart, n)
{
	var m = Math.min(srcStart + n, src.length);
	for (var i = srcStart, j = destStart; i < m; ++i, ++j) {
		dest[j] = src[i];
	}
}

var highBitMasks = new Array(0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800,
                             0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0,
                             0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF);

function biShiftLeft(x, n)
{
	var digitCount = Math.floor(n / bitsPerDigit);
	var result = new BigInt();
	arrayCopy(x.digits, 0, result.digits, digitCount,
	          result.digits.length - digitCount);
	var bits = n % bitsPerDigit;
	var rightBits = bitsPerDigit - bits;
	for (var i = result.digits.length - 1, i1 = i - 1; i > 0; --i, --i1) {
		result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) |
		                   ((result.digits[i1] & highBitMasks[bits]) >>>
		                    (rightBits));
	}
	result.digits[0] = ((result.digits[i] << bits) & maxDigitVal);
	result.isNeg = x.isNeg;
	return result;
}

var lowBitMasks = new Array(0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
                            0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
                            0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF);

function biShiftRight(x, n)
{
	var digitCount = Math.floor(n / bitsPerDigit);
	var result = new BigInt();
	arrayCopy(x.digits, digitCount, result.digits, 0,
	          x.digits.length - digitCount);
	var bits = n % bitsPerDigit;
	var leftBits = bitsPerDigit - bits;
	for (var i = 0, i1 = i + 1; i < result.digits.length - 1; ++i, ++i1) {
		result.digits[i] = (result.digits[i] >>> bits) |
		                   ((result.digits[i1] & lowBitMasks[bits]) << leftBits);
	}
	result.digits[result.digits.length - 1] >>>= bits;
	result.isNeg = x.isNeg;
	return result;
}

function biMultiplyByRadixPower(x, n)
{
	var result = new BigInt();
	arrayCopy(x.digits, 0, result.digits, n, result.digits.length - n);
	return result;
}

function biDivideByRadixPower(x, n)
{
	var result = new BigInt();
	arrayCopy(x.digits, n, result.digits, 0, result.digits.length - n);
	return result;
}

function biModuloByRadixPower(x, n)
{
	var result = new BigInt();
	arrayCopy(x.digits, 0, result.digits, 0, n);
	return result;
}

function biCompare(x, y)
{
	if (x.isNeg != y.isNeg) {
		return 1 - 2 * Number(x.isNeg);
	}
	for (var i = x.digits.length - 1; i >= 0; --i) {
		if (x.digits[i] != y.digits[i]) {
			if (x.isNeg) {
				return 1 - 2 * Number(x.digits[i] > y.digits[i]);
			} else {
				return 1 - 2 * Number(x.digits[i] < y.digits[i]);
			}
		}
	}
	return 0;
}

function biDivideModulo(x, y)
{
	var nb = biNumBits(x);
	var tb = biNumBits(y);
	var origYIsNeg = y.isNeg;
	var q, r;
	if (nb < tb) {
		// |x| < |y|
		if (x.isNeg) {
			q = biCopy(bigOne);
			q.isNeg = !y.isNeg;
			x.isNeg = false;
			y.isNeg = false;
			r = biSubtract(y, x);
			// Restore signs, 'cause they're references.
			x.isNeg = true;
			y.isNeg = origYIsNeg;
		} else {
			q = new BigInt();
			r = biCopy(x);
		}
		return new Array(q, r);
	}

	q = new BigInt();
	r = x;

	// Normalize Y.
	var t = Math.ceil(tb / bitsPerDigit) - 1;
	var lambda = 0;
	while (y.digits[t] < biHalfRadix) {
		y = biShiftLeft(y, 1);
		++lambda;
		++tb;
		t = Math.ceil(tb / bitsPerDigit) - 1;
	}
	// Shift r over to keep the quotient constant. We'll shift the
	// remainder back at the end.
	r = biShiftLeft(r, lambda);
	nb += lambda; // Update the bit count for x.
	var n = Math.ceil(nb / bitsPerDigit) - 1;

	var b = biMultiplyByRadixPower(y, n - t);
	while (biCompare(r, b) != -1) {
		++q.digits[n - t];
		r = biSubtract(r, b);
	}
	for (var i = n; i > t; --i) {
    var ri = (i >= r.digits.length) ? 0 : r.digits[i];
    var ri1 = (i - 1 >= r.digits.length) ? 0 : r.digits[i - 1];
    var ri2 = (i - 2 >= r.digits.length) ? 0 : r.digits[i - 2];
    var yt = (t >= y.digits.length) ? 0 : y.digits[t];
    var yt1 = (t - 1 >= y.digits.length) ? 0 : y.digits[t - 1];
		if (ri == yt) {
			q.digits[i - t - 1] = maxDigitVal;
		} else {
			q.digits[i - t - 1] = Math.floor((ri * biRadix + ri1) / yt);
		}

		var c1 = q.digits[i - t - 1] * ((yt * biRadix) + yt1);
		var c2 = (ri * biRadixSquared) + ((ri1 * biRadix) + ri2);
		while (c1 > c2) {
			--q.digits[i - t - 1];
			c1 = q.digits[i - t - 1] * ((yt * biRadix) | yt1);
			c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2);
		}

		b = biMultiplyByRadixPower(y, i - t - 1);
		r = biSubtract(r, biMultiplyDigit(b, q.digits[i - t - 1]));
		if (r.isNeg) {
			r = biAdd(r, b);
			--q.digits[i - t - 1];
		}
	}
	r = biShiftRight(r, lambda);
	// Fiddle with the signs and stuff to make sure that 0 <= r < y.
	q.isNeg = x.isNeg != origYIsNeg;
	if (x.isNeg) {
		if (origYIsNeg) {
			q = biAdd(q, bigOne);
		} else {
			q = biSubtract(q, bigOne);
		}
		y = biShiftRight(y, lambda);
		r = biSubtract(y, r);
	}
	// Check for the unbelievably stupid degenerate case of r == -0.
	if (r.digits[0] == 0 && biHighIndex(r) == 0) r.isNeg = false;

	return new Array(q, r);
}

function biDivide(x, y)
{
	return biDivideModulo(x, y)[0];
}

function biModulo(x, y)
{
	return biDivideModulo(x, y)[1];
}

function biMultiplyMod(x, y, m)
{
	return biModulo(biMultiply(x, y), m);
}

function biPow(x, y)
{
	var result = bigOne;
	var a = x;
	while (true) {
		if ((y & 1) != 0) result = biMultiply(result, a);
		y >>= 1;
		if (y == 0) break;
		a = biMultiply(a, a);
	}
	return result;
}

function biPowMod(x, y, m)
{
	var result = bigOne;
	var a = x;
	var k = y;
	while (true) {
		if ((k.digits[0] & 1) != 0) result = biMultiplyMod(result, a, m);
		k = biShiftRight(k, 1);
		if (k.digits[0] == 0 && biHighIndex(k) == 0) break;
		a = biMultiplyMod(a, a, m);
	}
	return result;
}

// BarrettMu, a class for performing Barrett modular reduction computations in
// JavaScript.
//
// Requires BigInt.js.
//
// Copyright 2004-2005 David Shapiro.
//
// You may use, re-use, abuse, copy, and modify this code to your liking, but
// please keep this header.
//
// Thanks!
//
// Dave Shapiro
// dave@ohdave.com

function BarrettMu(m)
{
	this.modulus = biCopy(m);
	this.k = biHighIndex(this.modulus) + 1;
	var b2k = new BigInt();
	b2k.digits[2 * this.k] = 1; // b2k = b^(2k)
	this.mu = biDivide(b2k, this.modulus);
	this.bkplus1 = new BigInt();
	this.bkplus1.digits[this.k + 1] = 1; // bkplus1 = b^(k+1)
	this.modulo = BarrettMu_modulo;
	this.multiplyMod = BarrettMu_multiplyMod;
	this.powMod = BarrettMu_powMod;
}

function BarrettMu_modulo(x)
{
	var q1 = biDivideByRadixPower(x, this.k - 1);
	var q2 = biMultiply(q1, this.mu);
	var q3 = biDivideByRadixPower(q2, this.k + 1);
	var r1 = biModuloByRadixPower(x, this.k + 1);
	var r2term = biMultiply(q3, this.modulus);
	var r2 = biModuloByRadixPower(r2term, this.k + 1);
	var r = biSubtract(r1, r2);
	if (r.isNeg) {
		r = biAdd(r, this.bkplus1);
	}
	var rgtem = biCompare(r, this.modulus) >= 0;
	while (rgtem) {
		r = biSubtract(r, this.modulus);
		rgtem = biCompare(r, this.modulus) >= 0;
	}
	return r;
}

function BarrettMu_multiplyMod(x, y)
{
	/*
	x = this.modulo(x);
	y = this.modulo(y);
	*/
	var xy = biMultiply(x, y);
	return this.modulo(xy);
}

function BarrettMu_powMod(x, y)
{
	var result = new BigInt();
	result.digits[0] = 1;
	var a = x;
	var k = y;
	while (true) {
		if ((k.digits[0] & 1) != 0) result = this.multiplyMod(result, a);
		k = biShiftRight(k, 1);
		if (k.digits[0] == 0 && biHighIndex(k) == 0) break;
		a = this.multiplyMod(a, a);
	}
	return result;
}

/*
* Copyright (c) 2015 Eric Wilde.
* Copyright 1998-2015 David Shapiro.
*
* RSA.js is a suite of routines for performing RSA public-key computations
* in JavaScript.  The cryptographic functions herein are used for encoding
* and decoding strings to be sent over unsecure channels.
*
* To use these routines, a pair of public/private keys is created through a
* number of means (OpenSSL tools on Linux/Unix, Dave Shapiro's
* RSAKeyGenerator program on Windows).  These keys are passed to RSAKeyPair
* as hexadecimal strings to create an encryption key object.  This key object
* is then used with encryptedString to encrypt blocks of plaintext using the
* public key.  The resulting cyphertext blocks can be decrypted with
* decryptedString.
*
* Note that the cryptographic functions herein are complementary to those
* found in CryptoFuncs.php and CryptoFuncs.pm.  Hence, encrypted messages may
* be sent between programs written in any of those languages.  The most
* useful, of course is to send messages encrypted by a Web page using RSA.js
* to a PHP or Perl script running on a Web servitron.
*
* Also, the optional padding flag may be specified on the call to
* encryptedString, in which case blocks of cyphertext that are compatible
* with real crypto libraries such as OpenSSL or Microsoft will be created.
* These blocks of cyphertext can then be sent to Web servitron that uses one
* of these crypto libraries for decryption.  This allows messages encrypted
* with longer keys to be decrypted quickly on the Web server as well as
* making for more secure communications when a padding algorithm such as
* PKCS1v1.5 is used.
*
* These routines require BigInt.js and Barrett.js.
*/

/*****************************************************************************/

/*
* Modifications
* -------------
*
* 2014 Jan 11  E. Wilde       Add optional padding flag to encryptedString
*                             for compatibility with real crypto libraries
*                             such as OpenSSL or Microsoft.  Add PKCS1v1.5
*                             padding.
*
* 2015 Jan 5  D. Shapiro      Add optional encoding flag for encryptedString
*                             and encapsulate padding and encoding constants
*                             in RSAAPP object.
*
* Original Code
* -------------
*
* Copyright 1998-2005 David Shapiro.
*
* You may use, re-use, abuse, copy, and modify this code to your liking, but
* please keep this header.
*
* Thanks!
*
* Dave Shapiro
* dave@ohdave.com
*/

/*****************************************************************************/

var RSAAPP = {};

RSAAPP.NoPadding = "NoPadding";
RSAAPP.PKCS1Padding = "PKCS1Padding";
RSAAPP.RawEncoding = "RawEncoding";
RSAAPP.NumericEncoding = "NumericEncoding"

/*****************************************************************************/

function RSAKeyPair(encryptionExponent, decryptionExponent, modulus, keylen)
/*
* encryptionExponent                    The encryption exponent (i.e. public
*                                       encryption key) to be used for
*                                       encrypting messages.  If you aren't
*                                       doing any encrypting, a dummy
*                                       exponent such as "10001" can be
*                                       passed.
*
* decryptionExponent                    The decryption exponent (i.e. private
*                                       decryption key) to be used for
*                                       decrypting messages.  If you aren't
*                                       doing any decrypting, a dummy
*                                       exponent such as "10001" can be
*                                       passed.
*
* modulus                               The modulus to be used both for
*                                       encrypting and decrypting messages.
*
* keylen                                The optional length of the key, in
*                                       bits.  If omitted, RSAKeyPair will
*                                       attempt to derive a key length (but,
*                                       see the notes below).
*
* returns                               The "new" object creator returns an
*                                       instance of a key object that can be
*                                       used to encrypt/decrypt messages.
*
* This routine is invoked as the first step in the encryption or decryption
* process to take the three numbers (expressed as hexadecimal strings) that
* are used for RSA asymmetric encryption/decryption and turn them into a key
* object that can be used for encrypting and decrypting.
*
* The key object is created thusly:
*
*      RSAKey = new RSAKeyPair("ABC12345", 10001, "987654FE");
*
* or:
*
*      RSAKey = new RSAKeyPair("ABC12345", 10001, "987654FE", 64);
*
* Note that RSAKeyPair will try to derive the length of the key that is being
* used, from the key itself.  The key length is especially useful when one of
* the padding options is used and/or when the encrypted messages created by
* the routine encryptedString are exchanged with a real crypto library such
* as OpenSSL or Microsoft, as it determines how many padding characters are
* appended.
*
* Usually, RSAKeyPair can determine the key length from the modulus of the
* key but this doesn't always work properly, depending on the actual value of
* the modulus.  If you are exchanging messages with a real crypto library,
* such as OpenSSL or Microsoft, that depends on the fact that the blocks
* being passed to it are properly padded, you'll want the key length to be
* set properly.  If that's the case, of if you just want to be sure, you
* should specify the key length that you used to generated the key, in bits
* when this routine is invoked.
*/
{
/*
* Convert from hexadecimal and save the encryption/decryption exponents and
* modulus as big integers in the key object.
*/
this.e = biFromHex(encryptionExponent);
this.d = biFromHex(decryptionExponent);
this.m = biFromHex(modulus);
/*
* Using big integers, we can represent two bytes per element in the big
* integer array, so we calculate the chunk size as:
*
*      chunkSize = 2 * (number of digits in modulus - 1)
*
* Since biHighIndex returns the high index, not the number of digits, the
* number 1 has already been subtracted from its answer.
*
* However, having said all this, "User Knows Best".  If our caller passes us
* a key length (in bits), we'll treat it as gospel truth.
*/
if (typeof(keylen) != 'number') { this.chunkSize = 2 * biHighIndex(this.m); }
else { this.chunkSize = keylen / 8; }

this.radix = 16;
/*
* Precalculate the stuff used for Barrett modular reductions.
*/
this.barrett = new BarrettMu(this.m);
}

/*****************************************************************************/

function encryptedString(key, s, pad, encoding)
/*
* key                                   The previously-built RSA key whose
*                                       public key component is to be used to
*                                       encrypt the plaintext string.
*
* s                                     The plaintext string that is to be
*                                       encrypted, using the RSA assymmetric
*                                       encryption method.
*
* pad                                   The optional padding method to use
*                                       when extending the plaintext to the
*                                       full chunk size required by the RSA
*                                       algorithm.  To maintain compatibility
*                                       with other crypto libraries, the
*                                       padding method is described by a
*                                       string.  The default, if not
*                                       specified is "OHDave".  Here are the
*                                       choices:
*
*                                         OHDave - this is the original
*                                           padding method employed by Dave
*                                           Shapiro and Rob Saunders.  If
*                                           this method is chosen, the
*                                           plaintext can be of any length.
*                                           It will be padded to the correct
*                                           length with zeros and then broken
*                                           up into chunks of the correct
*                                           length before being encrypted.
*                                           The resultant cyphertext blocks
*                                           will be separated by blanks.
*
*                                           Note that the original code by
*                                           Dave Shapiro reverses the byte
*                                           order to little-endian, as the
*                                           plaintext is encrypted.  If
*                                           either these JavaScript routines
*                                           or one of the complementary
*                                           PHP/Perl routines derived from
*                                           this code is used for decryption,
*                                           the byte order will be reversed
*                                           again upon decryption so as to
*                                           come out correctly.
*
*                                           Also note that this padding
*                                           method is claimed to be less
*                                           secure than PKCS1Padding.
*
*                                         NoPadding - this method truncates
*                                           the plaintext to the length of
*                                           the RSA key, if it is longer.  If
*                                           its length is shorter, it is
*                                           padded with zeros.  In either
*                                           case, the plaintext string is
*                                           reversed to preserve big-endian
*                                           order before it is encrypted to
*                                           maintain compatibility with real
*                                           crypto libraries such as OpenSSL
*                                           or Microsoft.  When the
*                                           cyphertext is to be decrypted
*                                           by a crypto library, the
*                                           library routine's RSAAPP.NoPadding
*                                           flag, or its equivalent, should
*                                           be used.
*
*                                           Note that this padding method is
*                                           claimed to be less secure than
*                                           PKCS1Padding.
*
*                                         PKCS1Padding - the PKCS1v1.5
*                                           padding method (as described in
*                                           RFC 2313) is employed to pad the
*                                           plaintext string.  The plaintext
*                                           string must be no longer than the
*                                           length of the RSA key minus 11,
*                                           since PKCS1v1.5 requires 3 bytes
*                                           of overhead and specifies a
*                                           minimum pad of 8 bytes.  The
*                                           plaintext string is padded with
*                                           randomly-generated bytes and then
*                                           its order is reversed to preserve
*                                           big-endian order before it is
*                                           encrypted to maintain
*                                           compatibility with real crypto
*                                           libraries such as OpenSSL or
*                                           Microsoft.  When the cyphertext
*                                           is to be decrypted by a crypto
*                                           library, the library routine's
*                                           RSAAPP.PKCS1Padding flag, or its
*                                           equivalent, should be used.
*
* encoding                              The optional encoding scheme to use
*                                       for the return value. If ommitted,
*                                       numeric encoding will be used.
*
*                                           RawEncoding - The return value
*                                           is given as its raw value.
*                                           This is the easiest method when
*                                           interoperating with server-side
*                                           OpenSSL, as no additional conversion
*                                           is required. Use the constant
*                                           RSAAPP.RawEncoding for this option.
*
*                                           NumericEncoding - The return value
*                                           is given as a number in hexadecimal.
*                                           Perhaps useful for debugging, but
*                                           will need to be translated back to
*                                           its raw equivalent (e.g. using
*                                           PHP's hex2bin) before using with
*                                           OpenSSL. Use the constant
*                                           RSAAPP.NumericEncoding for this option.
*
* returns                               The cyphertext block that results
*                                       from encrypting the plaintext string
*                                       s with the RSA key.
*
* This routine accepts a plaintext string that is to be encrypted with the
* public key component of the previously-built RSA key using the RSA
* assymmetric encryption method.  Before it is encrypted, the plaintext
* string is padded to the same length as the encryption key for proper
* encryption.
*
* Depending on the padding method chosen, an optional header with block type
* is prepended, the plaintext is padded using zeros or randomly-generated
* bytes, and then the plaintext is possibly broken up into chunks.
*
* Note that, for padding with zeros, this routine was altered by Rob Saunders
* (rob@robsaunders.net). The new routine pads the string after it has been
* converted to an array. This fixes an incompatibility with Flash MX's
* ActionScript.
*
* The various padding schemes employed by this routine, and as presented to
* RSA for encryption, are shown below.  Note that the RSA encryption done
* herein reverses the byte order as encryption is done:
*
*      Plaintext In
*      ------------
*
*      d5 d4 d3 d2 d1 d0
*
*      OHDave
*      ------
*
*      d5 d4 d3 d2 d1 d0 00 00 00 /.../ 00 00 00 00 00 00 00 00
*
*      NoPadding
*      ---------
*
*      00 00 00 00 00 00 00 00 00 /.../ 00 00 d0 d1 d2 d3 d4 d5
*
*      PKCS1Padding
*      ------------
*
*      d0 d1 d2 d3 d4 d5 00 p0 p1 /.../ p2 p3 p4 p5 p6 p7 02 00
*                            \------------  ------------/
*                                         \/
*                             Minimum 8 bytes pad length
*/
{
var a = new Array();                    // The usual Alice and Bob stuff
var sl = s.length;                      // Plaintext string length
var i, j, k;                            // The usual Fortran index stuff
var padtype;                            // Type of padding to do
var encodingtype;                       // Type of output encoding
var rpad;                               // Random pad
var al;                                 // Array length
var result = "";                        // Cypthertext result
var block;                              // Big integer block to encrypt
var crypt;                              // Big integer result
var text;                               // Text result
/*
* Figure out the padding type.
*/
if (typeof(pad) == 'string') {
  if (pad == RSAAPP.NoPadding) { padtype = 1; }
  else if (pad == RSAAPP.PKCS1Padding) { padtype = 2; }
  else { padtype = 0; }
}
else { padtype = 0; }
/*
* Determine encoding type.
*/
if (typeof(encoding) == 'string' && encoding == RSAAPP.RawEncoding) {
	encodingtype = 1;
}
else { encodingtype = 0; }

/*
* If we're not using Dave's padding method, we need to truncate long
* plaintext blocks to the correct length for the padding method used:
*
*       NoPadding    - key length
*       PKCS1Padding - key length - 11
*/
if (padtype == 1) {
  if (sl > key.chunkSize) { sl = key.chunkSize; }
}
else if (padtype == 2) {
  if (sl > (key.chunkSize-11)) { sl = key.chunkSize - 11; }
}
/*
* Convert the plaintext string to an array of characters so that we can work
* with individual characters.
*
* Note that, if we're talking to a real crypto library at the other end, we
* reverse the plaintext order to preserve big-endian order.
*/
i = 0;

if (padtype == 2) { j = sl - 1; }
else { j = key.chunkSize - 1; }

while (i < sl) {
  if (padtype) { a[j] = s.charCodeAt(i); }
  else { a[i] = s.charCodeAt(i); }

  i++; j--;
}
/*
* Now is the time to add the padding.
*
* If we're doing PKCS1v1.5 padding, we pick up padding where we left off and
* pad the remainder of the block.  Otherwise, we pad at the front of the
* block.  This gives us the correct padding for big-endian blocks.
*
* The padding is either a zero byte or a randomly-generated non-zero byte.
*/
if (padtype == 1) { i = 0; }

j = key.chunkSize - (sl % key.chunkSize);

while (j > 0) {
  if (padtype == 2) {
    rpad = Math.floor(Math.random() * 256);

    while (!rpad) { rpad = Math.floor(Math.random() * 256); }

    a[i] = rpad;
  }
  else { a[i] = 0; }

  i++; j--;
}
/*
* For PKCS1v1.5 padding, we need to fill in the block header.
*
* According to RFC 2313, a block type, a padding string, and the data shall
* be formatted into the encryption block:
*
*      EncrBlock = 00 || BlockType || PadString || 00 || Data
*
* The block type shall be a single octet indicating the structure of the
* encryption block. For this version of the document it shall have value 00,
* 01, or 02. For a private-key operation, the block type shall be 00 or 01.
* For a public-key operation, it shall be 02.
*
* The padding string shall consist of enough octets to pad the encryption
* block to the length of the encryption key.  For block type 00, the octets
* shall have value 00; for block type 01, they shall have value FF; and for
* block type 02, they shall be pseudorandomly generated and nonzero.
*
* Note that in a previous step, we wrote padding bytes into the first three
* bytes of the encryption block because it was simpler to do so.  We now
* overwrite them.
*/
if (padtype == 2)
  {
  a[sl] = 0;
  a[key.chunkSize-2] = 2;
  a[key.chunkSize-1] = 0;
  }
/*
* Carve up the plaintext and encrypt each of the resultant blocks.
*/
al = a.length;

for (i = 0; i < al; i += key.chunkSize) {
  /*
  * Get a block.
  */
  block = new BigInt();

  j = 0;

  for (k = i; k < (i+key.chunkSize); ++j) {
    block.digits[j] = a[k++];
    block.digits[j] += a[k++] << 8;
  }
  /*
  * Encrypt it, convert it to text, and append it to the result.
  */
  crypt = key.barrett.powMod(block, key.e);
  if (encodingtype == 1) {
	  text = biToBytes(crypt);
  }
  else {
	  text = (key.radix == 16) ? biToHex(crypt) : biToString(crypt, key.radix);
  }
  result += text;
}
/*
* Return the result, removing the last space.
*/
//result = (result.substring(0, result.length - 1));
return result;
}

/*****************************************************************************/

function decryptedString(key, c)
/*
* key                                   The previously-built RSA key whose
*                                       private key component is to be used
*                                       to decrypt the cyphertext string.
*
* c                                     The cyphertext string that is to be
*                                       decrypted, using the RSA assymmetric
*                                       encryption method.
*
* returns                               The plaintext block that results from
*                                       decrypting the cyphertext string c
*                                       with the RSA key.
*
* This routine is the complementary decryption routine that is meant to be
* used for JavaScript decryption of cyphertext blocks that were encrypted
* using the OHDave padding method of the encryptedString routine (in this
* module).  It can also decrypt cyphertext blocks that were encrypted by
* RSAEncode (in CryptoFuncs.pm or CryptoFuncs.php) so that encrypted
* messages can be sent of insecure links (e.g. HTTP) to a Web page.
*
* It accepts a cyphertext string that is to be decrypted with the public key
* component of the previously-built RSA key using the RSA assymmetric
* encryption method.  Multiple cyphertext blocks are broken apart, if they
* are found in c, and each block is decrypted.  All of the decrypted blocks
* are concatenated back together to obtain the original plaintext string.
*
* This routine assumes that the plaintext was padded to the same length as
* the encryption key with zeros.  Therefore, it removes any zero bytes that
* are found at the end of the last decrypted block, before it is appended to
* the decrypted plaintext string.
*
* Note that the encryptedString routine (in this module) works fairly quickly
* simply by virtue of the fact that the public key most often chosen is quite
* short (e.g. 0x10001).  This routine does not have that luxury.  The
* decryption key that it must employ is the full key length.  For long keys,
* this can result in serious timing delays (e.g. 7-8 seconds to decrypt using
* 2048 bit keys on a reasonably fast machine, under the Firefox Web browser).
*
* If you intend to send encrypted messagess to a JavaScript program running
* under a Web browser, you might consider using shorter keys to keep the
* decryption times low.  Alternately, a better scheme is to generate a random
* key for use by a symmetric encryption algorithm and transmit it to the
* other end, after encrypting it with encryptedString.  The other end can use
* a real crypto library (e.g. OpenSSL or Microsoft) to decrypt the key and
* then use it to encrypt all of the messages (with a symmetric encryption
* algorithm such as Twofish or AES) bound for the JavaScript program.
* Symmetric decryption is orders of magnitude faster than asymmetric and
* should yield low decryption times, even when executed in JavaScript.
*
* Also note that only the OHDave padding method (e.g. zeros) is supported by
* this routine *AND* that this routine expects little-endian cyphertext, as
* created by the encryptedString routine (in this module) or the RSAEncode
* routine (in either CryptoFuncs.pm or CryptoFuncs.php).  You can use one of
* the real crypto libraries to create cyphertext that can be decrypted by
* this routine, if you reverse the plaintext byte order first and then
* manually pad it with zero bytes.  The plaintext should then be encrypted
* with the NoPadding flag or its equivalent in the crypto library of your
* choice.
*/
{
var blocks = c.split(" ");              // Multiple blocks of cyphertext
var b;                                  // The usual Alice and Bob stuff
var i, j;                               // The usual Fortran index stuff
var bi;                                 // Cyphertext as a big integer
var result = "";                        // Plaintext result
/*
* Carve up the cyphertext into blocks.
*/
for (i = 0; i < blocks.length; ++i) {
  /*
  * Depending on the radix being used for the key, convert this cyphertext
  * block into a big integer.
  */
  if (key.radix == 16) { bi = biFromHex(blocks[i]); }
  else { bi = biFromString(blocks[i], key.radix); }
  /*
  * Decrypt the cyphertext.
  */
  b = key.barrett.powMod(bi, key.d);
  /*
  * Convert the decrypted big integer back to the plaintext string.  Since
  * we are using big integers, each element thereof represents two bytes of
  * plaintext.
  */
  for (j = 0; j <= biHighIndex(b); ++j) {
    result += String.fromCharCode(b.digits[j] & 255, b.digits[j] >> 8);
  }
}
/*
* Remove trailing null, if any.
*/
if (result.charCodeAt(result.length - 1) == 0) {
  result = result.substring(0, result.length - 1);
}
/*
* Return the plaintext.
*/
return (result);
}

function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
    for (d = 0; a > d; d += 1)
        e = Math.random() * b.length,
        e = Math.floor(e),
        c += b.charAt(e);
    return c
}
function b(a, b) {
    var c = CryptoJS.enc.Utf8.parse(b)
      , d = CryptoJS.enc.Utf8.parse("0102030405060708")
      , e = CryptoJS.enc.Utf8.parse(a)
      , f = CryptoJS.AES.encrypt(e, c, {
        iv: d,
        mode: CryptoJS.mode.CBC
    });
    return f.toString()
}
function c(a, b, c) {
    var d, e;
    // 也是一个第三方库,但是该库不在npm中
    return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
}
function d(d, e, f, g) {
    var h = {}
      , i = a(16);
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}
function e(a, b, d, e) {
    var f = {};
    return f.encText = c(a + e, b, d),
    f
}

    let i0x = {
    "csrf_token": "",
    "encodeType": "aac",
    "ids": "[1325905146]",
    "level": "standard",
};
let r = d(JSON.stringify(i0x), '010001', '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7', '0CoJUm6Qyw8W8jud');
console.log(r);

运行结果和上面两个方法一致。
初识JavaScript逆向——以网易云音乐和招标网站为例
python代码也一致。

通过搜索关键字找到加密过程

点击开发者工具右上角的三个点,再点击search,可以实现全局搜索。
初识JavaScript逆向——以网易云音乐和招标网站为例
搜索music_url中的关键字,先搜索"v1",结果有很多,不好判断。
初识JavaScript逆向——以网易云音乐和招标网站为例
再搜索"url/v1",只有一处结果了。
初识JavaScript逆向——以网易云音乐和招标网站为例
点进去,在该文件中搜索"url/v1",也只有一处。
初识JavaScript逆向——以网易云音乐和招标网站为例
所以就可以在该行打断点进行调试,也是跳入如下的函数,跟用第一种方法找加密过程是一样的。
初识JavaScript逆向——以网易云音乐和招标网站为例
只不过这种方法比第一种方法快点。但是没有哪种方法比哪种方法好的说法。

某招标网站

url地址:https://ctbpsp.com/
老样子,找到network模块,刷新界面,看触发了哪些数据包。
初识JavaScript逆向——以网易云音乐和招标网站为例
不要一个一个流量包找,直接找类型为XHR/Fetch的就可以了。
初识JavaScript逆向——以网易云音乐和招标网站为例
这里只有一条,看他的响应内容也是一串加密字符串。
初识JavaScript逆向——以网易云音乐和招标网站为例
找它的加密过程。查看initiators,发现有Promise对象,且出现了async关键字,是异步加载。
初识JavaScript逆向——以网易云音乐和招标网站为例
大概率采用的是axios,加密过程很有可能是在拦截器中实现的,所以直接全局搜索"interceptors"。
初识JavaScript逆向——以网易云音乐和招标网站为例
总共有7处,一处一处查看。
第一个文件的两处如下。
初识JavaScript逆向——以网易云音乐和招标网站为例
第二个文件的三处如下。
初识JavaScript逆向——以网易云音乐和招标网站为例
第三个文件的两处如下。
初识JavaScript逆向——以网易云音乐和招标网站为例
第二个文件只是对interceptors进行定义,不是调用的地方,第二处直接忽略。
第一个文件的响应拦截器处出现如下代码,大概率C就是加密函数了。
初识JavaScript逆向——以网易云音乐和招标网站为例
在该行打断点,进行调试。发现这里打断点打不上,一打断点就直接打到了第三个文件里的响应拦截器的地方。试了几次都是这样,说明第三个文件里的才是真正的加密代码。
初识JavaScript逆向——以网易云音乐和招标网站为例
可以明显的看到有个"decryptByDES"函数,这就是DES解密函数了,找到该函数的实现代码。
初识JavaScript逆向——以网易云音乐和招标网站为例
将该代码抠出来,用加密的参数进行测试。

点击查看代码
var CryptoJS = require("crypto-js");

function decryptByDES(ciphertext) {
    var keyHex = CryptoJS.enc.Utf8.parse("1qaz@wsx3e");
    //  ctpstp@custominfo!@#qweASD
    // direct decrypt ciphertext
    var decrypted = CryptoJS.DES.decrypt({
            ciphertext: CryptoJS.enc.Base64.parse(ciphertext),
        },
        keyHex, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7,
        }
    );
    return decrypted.toString(CryptoJS.enc.Utf8);
}

// 本地测试
var s = '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'
let res = decryptByDES(s);
console.log(res);

运行结果如下。

点击查看代码
{"success":true,"data":{"dataList":[{"bulletinID":"2127d78a-cc63-4946-a3fc-667ed8f7f3b4","tenderProjectName":"上海浦东发展银行股份有限公司信用卡中心2024年度国产数据库维保采购项目","bidSectionName":null,"noticeName":"上海浦东发展银行股份有限公司信用卡中心2024年度国产数据库维保采购项目 招标公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-26 13:30:00","notieIndustriestName":"信息电子","reginProvince":"上海市","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"310000","regionName":"上海市 上海市","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179240.html","superviseDeptName":"/","leftOpenBidDay":24,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"上海浦东发展银行股份有限公司信用卡中心","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":false,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false},{"bulletinID":"6bcbb909-45e1-498a-8475-16505c4c1062","tenderProjectName":"中国邮政集团有限公司内蒙古自治区分公司全区邮资票品库安防达标改造-UPS项目","bidSectionName":null,"noticeName":"中国邮政集团有限公司内蒙古自治区分公司全区邮资票品库安防达标改造-UPS项目招标公告","noticeMedia":"内蒙古招标投标网","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-22 14:30:00","notieIndustriestName":"其他","reginProvince":"内蒙古自治区","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"150000","regionName":"内蒙古自治区 内蒙古自治区","noticeSendTime":"2024-03-01","bulletinSource":"内蒙古招标投标网","noticeUrl":"http://zbgg.nmgztb.com.cn/biddingBulletin/2024-03-01/563437.html","superviseDeptName":"中国邮政集团有限公司内蒙古自治区分公司","leftOpenBidDay":20,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"中国邮政集团有限公司内蒙古自治区分公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false},{"bulletinID":"dc5bcc1e-d082-4634-a71a-13fd731b642e","tenderProjectName":"虹口港驳岸景观绿化整修项目","bidSectionName":null,"noticeName":"虹口港驳岸景观绿化整修项目招标公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-13 14:00:00","notieIndustriestName":"园林绿化","reginProvince":"上海市","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"310109","regionName":"上海市 市辖区","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179266.html","superviseDeptName":"/","leftOpenBidDay":11,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"上海市虹口区市政和水务管理中心","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":false,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":true,"subcription":false},{"bulletinID":"5f30d33f-372e-4489-ad1e-a949719846a6","tenderProjectName":"中国黄金集团建设有限公司矿业分公司乌努格吐山铜钼矿低铜废石环境污染预防及资源综合回收利用工程","bidSectionName":null,"noticeName":"中国黄金集团建设有限公司矿业分公司乌努格吐山铜钼矿低铜废石环境污染预防及资源综合回收利用工程钢结构制安及彩板维护工程专业分包(标段一)招标公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-08 14:00:00","notieIndustriestName":"矿产冶金","reginProvince":"内蒙古自治区","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"150000","regionName":"内蒙古自治区 内蒙古自治区","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179273.html","superviseDeptName":"中国黄金集团建设有限公司相关部门","leftOpenBidDay":6,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"中国黄金集团建设有限公司矿业分公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":false,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false},{"bulletinID":"03568039-a27f-45df-afff-e8e462c3017c","tenderProjectName":"云南省安宁监狱(企业)2024年-2025年水电维修材料采购","bidSectionName":null,"noticeName":"云南省安宁监狱(企业)2024年-2025年水电维修材料采购竞争性磋商公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-15 10:00:00","notieIndustriestName":"其他","reginProvince":"云南省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"530000","regionName":"云南省 云南省","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179264.html","superviseDeptName":"云南省安宁监狱审计科,安宁监狱纪委监察室","leftOpenBidDay":13,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"云南省安宁监狱、云南金马集团光明实业有限责任公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":true,"subcription":false},{"bulletinID":"5822fab0-ab8c-46fb-b234-1f7a32f3aadc","tenderProjectName":"昆明滇池水务4家子公司股权转让资产评估服务机构选聘","bidSectionName":null,"noticeName":"昆明滇池水务4家子公司股权转让资产评估服务机构选聘竞争性谈判公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-12 14:30:00","notieIndustriestName":"商业服务","reginProvince":"云南省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"530100","regionName":"云南省 昆明市","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179274.html","superviseDeptName":"/","leftOpenBidDay":10,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"昆明滇池水务股份有限公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":true,"subcription":false},{"bulletinID":"882ecfd8-da7d-4d54-9959-8d902e915d6d","tenderProjectName":"松花江水上旅游大通道—松花江源生态文化旅游建设项目(松江湖岸山村建设项目一标段)监理","bidSectionName":null,"noticeName":"松花江水上旅游大通道—松花江源生态文化旅游建设项目(松江湖岸山村建设项目一标段)监理-招标公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-25 13:30:00","notieIndustriestName":"其他","reginProvince":"吉林省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"220000","regionName":"吉林省 吉林省","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179277.html","superviseDeptName":"靖宇县建设工程招投标管理办公室","leftOpenBidDay":23,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"白山松花江商业运营管理有限公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":true,"subcription":false},{"bulletinID":"9ff8b973-5d64-47b6-8cf8-d4e7c9c0f5de","tenderProjectName":"聊城市公共资源交易中心证照“三联办”信息系统升级项目","bidSectionName":null,"noticeName":"聊城市公共资源交易中心证照“三联办”信息系统升级项目采购公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-19 14:00:00","notieIndustriestName":"商业服务","reginProvince":"山东省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"371500","regionName":"山东省 聊城市","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179255.html","superviseDeptName":"中国银行股份有限公司聊城分行","leftOpenBidDay":17,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"中国银行股份有限公司聊城分行","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":false,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false},{"bulletinID":"81431cb1-6b13-4f7b-8fd0-e5a19dd63a74","tenderProjectName":"2024年3个月液体葡萄糖采购项目(第三次)","bidSectionName":null,"noticeName":"浙江省成套招标代理有限公司关于杭州市环境集团有限公司2024年3个月液体葡萄糖采购项目(第三次)的公开招标公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-21 14:00:00","notieIndustriestName":"化学工业","reginProvince":"浙江省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"330100","regionName":"浙江省 杭州市","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179319.html","superviseDeptName":"杭州市环境集团有限公司纪检监察室;郑先生,13336041895","leftOpenBidDay":19,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"杭州市环境集团有限公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false},{"bulletinID":"88cadd3d-a3cf-4bff-a22e-5b84a668a5b9","tenderProjectName":"中央储备粮临沂直属库有限公司新库区项目沉降观测项目","bidSectionName":null,"noticeName":"中央储备粮临沂直属库有限公司新库区项目沉降观测项目竞争性磋商采购公告","noticeMedia":"中国招标投标公共服务平台","bulletinTypeName":"招标公告","docGetEndTime":null,"bidDocRefferEndTime":null,"bidOpenTime":"2024-03-14 09:30:00","notieIndustriestName":"商业服务","reginProvince":"山东省","serverPlat":null,"tradePlat":null,"dataPlat":null,"regionCode":"370000","regionName":"山东省 山东省","noticeSendTime":"2024-03-01","bulletinSource":"发布工具","noticeUrl":"https://bulletin.cebpubservice.com/biddingBulletin/2024-03-01/12179251.html","superviseDeptName":"/","leftOpenBidDay":12,"platformCode":null,"platformName":null,"commentCount":0,"hot":0,"viewCount":0,"favCount":0,"subscriptionCount":0,"tenderBidder":"中央储备粮临沂直属库有限公司","tenderAgency":"","isNew":0,"grade":null,"dataSource":"0","potentialBidderDetails":true,"classifyName":null,"classifyCode":null,"isBulletinChain":"0","upBulletinStatus":0,"winPrice":null,"winBidder":"","bulletinUUID":null,"vipWinBidder":false,"fav":false,"similarProjectsBidInfo":false,"subcription":false}],"totalPage":2435375,"currentPage":1,"totalCount":24353746,"pageSize":10},"errorMessage":""}

得到该页面显示的数据内容,这样就可以用python代码来获取页面内容了。

点击查看代码
from functools import partial  # 锁定参数
import subprocess

subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests

url = "https://ctbpsp.com/cutominfoapi/recommand/type/5/pagesize/10/currentpage/1"
resp = requests.get(url, headers={"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
                                                "like Gecko) Chrome/122.0.0.0 Safari/537.36"})
f = open("某招标网站.js", mode="r", encoding="utf-8")
js_code = f.read()
f.close()

js = execjs.compile(js_code)
result = js.call("decryptByDES", resp.text.strip('"'))
print(result)

总结:逆向入门还是有点难的,需要多练。

返回顶部
顶部