From 9b191be6d6f469f74148d9e241ab3a0dd6f36ea1 Mon Sep 17 00:00:00 2001 From: PAN Lei Date: Wed, 13 Aug 2025 08:58:09 +0200 Subject: [PATCH] added jspl_encoder_wrapper.py --- captcha/__init__.py | 0 captcha/jspl_encoder.js | 439 ++++++++++++++++++++++++++++++++ captcha/jspl_encoder_wrapper.py | 187 ++++++++++++++ 3 files changed, 626 insertions(+) create mode 100644 captcha/__init__.py create mode 100644 captcha/jspl_encoder.js create mode 100644 captcha/jspl_encoder_wrapper.py diff --git a/captcha/__init__.py b/captcha/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/captcha/jspl_encoder.js b/captcha/jspl_encoder.js new file mode 100644 index 0000000..ce143a0 --- /dev/null +++ b/captcha/jspl_encoder.js @@ -0,0 +1,439 @@ +// JSPL的计算过程总结: +// +// 输入: 传入的会话ID或相关数据 +// 哈希处理: 使用DJB2算法变种对输入进行哈希 +// 种子生成: 结合时间戳和常量生成加密种子 +// 数据收集: 收集各种浏览器指纹和环境信息 +// 加密处理: 使用自定义的流加密算法对数据进行加密 +// Base64编码: 最终使用自定义字符集进行Base64编码 +// 这个函数生成的第三个返回值就是用于JSPL参数的加密字符串 +var zn = (function () { + // 检查是否已经初始化 + // if (Pn) { + // return Nn; + // } + + Pn = true; + var n = "unknown"; // 默认返回值 + var constantSeed = 11027890091; // 常量种子 + var o = true; // 状态标志 + + // 字符串哈希函数 - DJB2算法的变种 + function djb2HashString(str) { + if (!str) { + return n; + } + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = (hash << 8) - hash + str.charCodeAt(i) | 0; + } + return hash === 0 ? n : hash; + } + + // 映射函数 - 用于Base64编码的自定义字符集 + function mapChar(n) { + if (n > 37) { + return 59 + n; + } else if (n > 11) { + return 53 + n; + } else if (n > 1) { + return 46 + n; + } else { + return n * 50 + 45; + } + } + + // 线性同余生成器 - 用于生成伪随机数 + function lcg(n) { + return (n = (n ^= n << 13) ^ n >> 17) ^ n << 5; + } + + // 加密随机数生成器 + function createRandomGenerator(seed, initialValue) { + var state = seed; + var counter = -1; + var value = initialValue; + var firstCall = o; + o = false; + var buffer = null; + + return function (peek) { + var result; + if (buffer !== null) { + result = buffer; + buffer = null; + } else { + if (++counter > 2) { + state = lcg(state); + counter = 0; + } + result = ((result = state >> 16 - counter * 8) ^ (firstCall ? --value : 0)) & 255; + if (peek) { + buffer = result; + } + } + return result; + }; + } + + // 主加密函数 + return Nn = function (inputString, additionalData) { + // console.log("inputString:" + inputString) + // console.log("additionalData:" + additionalData) + // 混合输入数据生成种子 + var seed = constantSeed ^ djb2HashString(inputString) ^ additionalData; + + // 生成基于时间的随机数 + var timeBasedRandom = lcg(lcg(Date.now() >> 3 ^ constantSeed) * constantSeed); + + // 创建随机数生成器 + var randomGenerator = createRandomGenerator(seed, timeBasedRandom); + + // 存储加密数据的数组 + var encryptedData = []; + var isFirstCall = true; + var callCounter = 0; + + // UTF-8编码和加密函数 + function utf8EncodeAndEncrypt(str) { + var bytes = []; + var byteIndex = 0; + + // UTF-8编码 + for (var i = 0; i < str.length; i++) { + var charCode = str.charCodeAt(i); + if (charCode < 128) { + bytes[byteIndex++] = charCode; + } else { + if (charCode < 2048) { + bytes[byteIndex++] = charCode >> 6 | 192; + } else { + if ((charCode & 64512) == 55296 && i + 1 < str.length && (str.charCodeAt(i + 1) & 64512) == 56320) { + charCode = 65536 + ((charCode & 1023) << 10) + (str.charCodeAt(++i) & 1023); + bytes[byteIndex++] = charCode >> 18 | 240; + bytes[byteIndex++] = charCode >> 12 & 63 | 128; + } else { + bytes[byteIndex++] = charCode >> 12 | 224; + } + bytes[byteIndex++] = charCode >> 6 & 63 | 128; + } + bytes[byteIndex++] = charCode & 63 | 128; + } + } + + // XOR加密 + for (var j = 0; j < bytes.length; j++) { + bytes[j] ^= randomGenerator(); + } + return bytes; + } + + // JSON序列化函数 + function safeJsonStringify(obj) { + try { + return JSON.stringify(obj); + } catch (e) { + } + } + + // 数据收集和加密函数 + function collectAndEncrypt(key, value) { + // console.log("key:" + key) + // console.log("value:" + value) + if (!(typeof key != "string" || key.length == 0 || value && ["number", "string", "boolean"].indexOf(typeof value) == -1)) { + let serializedKey = safeJsonStringify(key); + // console.log("serializedKey:" + serializedKey) + value = safeJsonStringify(value); + // console.log("serializedValue:" + value) + + if (key && value !== undefined && key !== "xt1") { + // 添加分隔符和加密数据 + encryptedData.push(randomGenerator() ^ (encryptedData.length ? 44 : 123)); + Array.prototype.push.apply(encryptedData, utf8EncodeAndEncrypt(serializedKey)); + encryptedData.push(randomGenerator() ^ 58); + Array.prototype.push.apply(encryptedData, utf8EncodeAndEncrypt(value)); + + // 第一次调用时添加额外数据 + if (isFirstCall) { + isFirstCall = false; + // var hsv = typeof window._hsv == "string" && window._hsv.length > 0 || + // typeof window._hsv == "number" && !isNaN(window._hsv) ? window._hsv : 33; + //todo hsv 怎么拿到 + var hsv = 33; + collectAndEncrypt("r3n", hsv); + } + } + } + } + + var processedKeys = new Set(); + + // 返回三个函数的数组 + return [ + // 1. 主要的数据收集函数 + collectAndEncrypt, + + // 2. 带去重保护的数据收集函数 + function (key, value) { + if (!processedKeys.has(key)) { + processedKeys.add(key); + collectAndEncrypt(key, value); + } + }, + + // 3. 最终生成加密字符串的函数 + function (finalKey) { + var finalRandomGen = createRandomGenerator(djb2HashString(finalKey) ^ 1809053797, timeBasedRandom); + collectAndEncrypt("bpc", ++callCounter); + + // 对收集的数据进行最终加密 + var finalEncrypted = []; + for (var i = 0; i < encryptedData.length; i++) { + finalEncrypted.push(encryptedData[i] ^ finalRandomGen()); + } + + // 添加结束标记 + finalEncrypted.push(randomGenerator(true) ^ 125 ^ finalRandomGen()); + + // Base64编码 + var resultBytes = finalEncrypted; + var resultChars = []; + var baseValue = timeBasedRandom; + + for (var pos = 0; pos < resultBytes.length;) { + var triplet = (--baseValue & 255 ^ resultBytes[pos++]) << 16 | + (--baseValue & 255 ^ resultBytes[pos++]) << 8 | + --baseValue & 255 ^ resultBytes[pos++]; + + resultChars.push(String.fromCharCode(mapChar(triplet >> 18 & 63))); + resultChars.push(String.fromCharCode(mapChar(triplet >> 12 & 63))); + resultChars.push(String.fromCharCode(mapChar(triplet >> 6 & 63))); + resultChars.push(String.fromCharCode(mapChar(triplet & 63))); + } + + // 处理剩余字节 + if (resultBytes.length % 3) { + resultChars.length -= 3 - (resultBytes.length % 3); + } + + return resultChars.join(""); + } + ]; + }; +})(); + +// 创建一个工具函数来生成JSPL +function createJSPLGenerator(dateTimeStamp) { + return { + generate: function (sessionData, fingerprintData) { + // let dateTimeStamp = Math.floor(Date.now() / 1000) + // console.log("dateTimeStamp : " + dateTimeStamp) + // 使用会话数据作为输入 + var functions = zn(sessionData, dateTimeStamp); + var collect = functions[1]; // 收集函数 + var generate = functions[2]; //加密函数 + + // 收集指纹数据 + for (var key in fingerprintData) { + if (fingerprintData.hasOwnProperty(key)) { + collect(key, fingerprintData[key]); + } + } + + // 生成并返回JSPL + return generate("jspl"); + } + }; +} + +// print process.argv +process.argv.forEach(function (val, index, array) { + console.log(index + ': ' + val); +}); +var fingerprint = undefined +if (process.argv[2] != undefined) { + fingerprint = JSON.parse(process.argv[2]) +} +// main +// let dateTimeStamp = Math.floor(Date.now() / 1000) +let dateTimeStamp = 1754908260 + +// console.log(dateTimeStamp) +// 使用示例 +let jsplGenerator = createJSPLGenerator(dateTimeStamp); + +// fingerprint = { +// "nddc": 1, +// "r3n": 33, +// "exp8": 0, +// "uid": null, +// "bci": true, +// "bcl": 0.8, +// "bct": "Infinity", +// "bdt": "Infinity", +// "dp0": false, +// "ucdv": false, +// "wdifrm": false, +// "iwgl": "undefined", +// "npmtm": false, +// "wdif": false, +// "wdifpnh": 2800984568, +// "lg": "fr-FR", +// "isb": false, +// "idp": true, +// "crt": 0, +// "vnd": "Google Inc.", +// "bid": "NA", +// "med": "defined", +// "pltod": false, +// "csssp": "", +// "awe": false, +// "phe": false, +// "dat": false, +// "nm": false, +// "geb": false, +// "sqt": false, +// "pf": "MacIntel", +// "hc": 8, +// "br_oh": 745, +// "br_ow": 393, +// "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36", +// "wbd": false, +// "ts_mtp": 5, +// "mob": true, +// "lgs": ["fr-FR", "en-US", "zh-CN", "zh", "fr", "en"], +// "dvm": 4, +// "hcovdr": false, +// "plovdr": false, +// "ftsovdr": false, +// "orf": "debug", +// "ttst": 0.8517814137469564, +// "br_w": 393, +// "br_h": 745, +// "br_iw": 513, +// "br_ih": 847, +// "ars_w": 393, +// "ars_h": 876, +// "rs_w": 393, +// "rs_h": 876, +// "rs_cd": 24, +// "pr": 2.75, +// "so": "portrait-primary", +// "vco": "", +// "vcots": false, +// "vch": "probably", +// "vchts": true, +// "vcw": "probably", +// "vcwts": true, +// "vc3": "maybe", +// "vc3ts": false, +// "vcmp": "", +// "vcmpts": false, +// "vcq": "", +// "vcqts": false, +// "vc1": "probably", +// "vc1ts": true, +// "cssS": "3.93,0.39,12.24,9.75,5.64,10.98,13.46,2.19,6.51", +// "css0": "7,75,0", +// "css1": "9.74354,0.354646,-0.00922698,0.000685511,0.203723,-5.61673,-0.755238,0.0561098,-0.0279023,0.481607,-10.9534,0.813777,-0.375565,6.48243,-147.433,11.9534", +// "cssH": "15px", +// "plu": "", +// "plgod": false, +// "plg": 0, +// "plgne": "NA", +// "plgre": "NA", +// "plgof": "NA", +// "plggt": "NA", +// "mmt": "empty", +// "bchk": "3223aeb6721e0d0917e792818d193ac88dcd62fad5cad7bf7a2b2b473ecf58ee60f018dbdb1a5832e8dc6528387b0745971dbcd82387261e9a4e3f", +// "nt_tcp": 0, +// "nt_dns": 0, +// "nt_rd": 0, +// "nt_irt": -29.600000008940697, +// "nt_rt": 1129, +// "nt_tls": 6.700000002980232, +// "nt_ttf": 1166.7000000029802, +// "nt_swt": 22.900000005960464, +// "nt_csd": 411060, +// "nt_nhp": "h2", +// "nt_rdc": 0, +// "nt_it": "navigation", +// "nt_prs": 6.700000002980232, +// "nt_esc": 0, +// "nt_ttrd": null, +// "nt_le": 3.0999999940395355, +// "nt_dcle": 141.79999999701977, +// "nt_di": 1415.2000000029802, +// "nt_dc": 2862.6000000089407, +// "ccsT": "Error\nat S (https://d.digital.hermes/tags.js:173:22)\nat Un.C (https://d.digital.hermes/tags.js:1252:41\nat nrWrapper (https://www.hermes.com/fr/fr/category/femme/Sacs%20et%20pochettes%20pour%20Femme%20_%20Herme%CC%80s%20France.html:7:13468)", +// "ccsB": "tags.js:1252:41 at nrWrapper (sac-p-tit-arcon-H085871CKAO.html:7:13468)", +// "ccsH": "1050544242", +// "ccsV": ",993b46baf0942a343b7e6b02fa3f8eef64727f077d3b0055af56e6994dcaf046", +// "muev": false, +// "pro_t": true, +// "wglo": true, +// "prso": true, +// "wbst": true, +// "psn": true, +// "edp": true, +// "addt": true, +// "wsdc": true, +// "ccsr": true, +// "nuad": true, +// "bcda": true, +// "idn": true, +// "capi": true, +// "svde": true, +// "vpbq": true, +// "mq": "aptr:coarse,ahvr:none", +// "aco": "probably", +// "acots": false, +// "acmp": "probably", +// "acmpts": true, +// "acw": "probably", +// "acwts": false, +// "acma": "maybe", +// "acmats": false, +// "acaa": "probably", +// "acaats": true, +// "ac3": "", +// "ac3ts": false, +// "acf": "probably", +// "acfts": false, +// "acmp4": "maybe", +// "acmp4ts": false, +// "acmp3": "probably", +// "acmp3ts": false, +// "acwm": "maybe", +// "acwmts": false, +// "ocpt": false, +// "ckwa": false, +// "spwn": false, +// "emt": false, +// "bfr": false, +// "tz": -120, +// "hdn": false, +// "xt1": false, +// "cdhf": false, +// "eva": 33, +// "cokys": ",loadTimes,csi,app", +// "ecpc": false, +// "nhi": ",64,true,Nexus 5,Android,6.0,138.0.7204.184,false", +// "k_lyts": 48, +// "k_lytk": "kg20va`l\u00a7'w8mh.71pdfoqcn[zy365x/\\,-4bt9siu=j;r]e", +// "emd": "k:ai,vi,ao", +// "wwl": false, +// "glvd": "Google Inc. (ARM)", +// "glrd": "ANGLE (ARM,Mali-G57 MC2,OpenGL ES 3.2)", +// "tzp": "Europe/Paris", +// "isf": true, +// "isf2": false, +// "dt": false, +// "fph": 416468867, +// "jset": 1754750959 +// } + +// console.log("received fringerprint is :"+fingerprint) +// console.log(fingerprint) +let jspl = jsplGenerator.generate("user_session_12345", fingerprint); +console.log(jspl); \ No newline at end of file diff --git a/captcha/jspl_encoder_wrapper.py b/captcha/jspl_encoder_wrapper.py new file mode 100644 index 0000000..508cebf --- /dev/null +++ b/captcha/jspl_encoder_wrapper.py @@ -0,0 +1,187 @@ +import json +import os +import subprocess + + +def encrpte_to_jspl(fingerprint): + dir = os.path.dirname(__file__) + # print(fingerprint_json) + p = subprocess.Popen(['node', dir + '/jspl_encoder.js', fingerprint_json], stdout=subprocess.PIPE) + encrypted_fingerprint = p.stdout.read() + _result = encrypted_fingerprint.decode('utf-8') + return _result + + +if __name__ == '__main__': + fingerprint = { + "nddc": 1, + "r3n": 33, + "exp8": 0, + "uid": None, + "bci": True, + "bcl": 0.8, + "bct": "Infinity", + "bdt": "Infinity", + "dp0": False, + "ucdv": False, + "wdifrm": False, + "iwgl": "undefined", + "npmtm": False, + "wdif": False, + "wdifpnh": 2800984568, + "lg": "fr-FR", + "isb": False, + "idp": True, + "crt": 0, + "vnd": "Google Inc.", + "bid": "NA", + "med": "defined", + "pltod": False, + "csssp": "", + "awe": False, + "phe": False, + "dat": False, + "nm": False, + "geb": False, + "sqt": False, + "pf": "MacIntel", + "hc": 8, + "br_oh": 745, + "br_ow": 393, + "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36", + "wbd": False, + "ts_mtp": 5, + "mob": True, + "lgs": ["fr-FR", "en-US", "zh-CN", "zh", "fr", "en"], + "dvm": 4, + "hcovdr": False, + "plovdr": False, + "ftsovdr": False, + "orf": "debug", + "ttst": 0.8517814137469564, + "br_w": 393, + "br_h": 745, + "br_iw": 513, + "br_ih": 847, + "ars_w": 393, + "ars_h": 876, + "rs_w": 393, + "rs_h": 876, + "rs_cd": 24, + "pr": 2.75, + "so": "portrait-primary", + "vco": "", + "vcots": False, + "vch": "probably", + "vchts": True, + "vcw": "probably", + "vcwts": True, + "vc3": "maybe", + "vc3ts": False, + "vcmp": "", + "vcmpts": False, + "vcq": "", + "vcqts": False, + "vc1": "probably", + "vc1ts": True, + "cssS": "3.93,0.39,12.24,9.75,5.64,10.98,13.46,2.19,6.51", + "css0": "7,75,0", + "css1": "9.74354,0.354646,-0.00922698,0.000685511,0.203723,-5.61673,-0.755238,0.0561098,-0.0279023,0.481607,-10.9534,0.813777,-0.375565,6.48243,-147.433,11.9534", + "cssH": "15px", + "plu": "", + "plgod": False, + "plg": 0, + "plgne": "NA", + "plgre": "NA", + "plgof": "NA", + "plggt": "NA", + "mmt": "empty", + "bchk": "3223aeb6721e0d0917e792818d193ac88dcd62fad5cad7bf7a2b2b473ecf58ee60f018dbdb1a5832e8dc6528387b0745971dbcd82387261e9a4e3f", + "nt_tcp": 0, + "nt_dns": 0, + "nt_rd": 0, + "nt_irt": -29.600000008940697, + "nt_rt": 1129, + "nt_tls": 6.700000002980232, + "nt_ttf": 1166.7000000029802, + "nt_swt": 22.900000005960464, + "nt_csd": 411060, + "nt_nhp": "h2", + "nt_rdc": 0, + "nt_it": "navigation", + "nt_prs": 6.700000002980232, + "nt_esc": 0, + "nt_ttrd": None, + "nt_le": 3.0999999940395355, + "nt_dcle": 141.79999999701977, + "nt_di": 1415.2000000029802, + "nt_dc": 2862.6000000089407, + "ccsT": "Error\nat S (https://d.digital.hermes/tags.js:173:22)\nat Un.C (https://d.digital.hermes/tags.js:1252:41\nat nrWrapper (https://www.hermes.com/fr/fr/category/femme/Sacs%20et%20pochettes%20pour%20Femme%20_%20Herme%CC%80s%20France.html:7:13468)", + "ccsB": "tags.js:1252:41 at nrWrapper (sac-p-tit-arcon-H085871CKAO.html:7:13468)", + "ccsH": "1050544242", + "ccsV": ",993b46baf0942a343b7e6b02fa3f8eef64727f077d3b0055af56e6994dcaf046", + "muev": False, + "pro_t": True, + "wglo": True, + "prso": True, + "wbst": True, + "psn": True, + "edp": True, + "addt": True, + "wsdc": True, + "ccsr": True, + "nuad": True, + "bcda": True, + "idn": True, + "capi": True, + "svde": True, + "vpbq": True, + "mq": "aptr:coarse,ahvr:none", + "aco": "probably", + "acots": False, + "acmp": "probably", + "acmpts": True, + "acw": "probably", + "acwts": False, + "acma": "maybe", + "acmats": False, + "acaa": "probably", + "acaats": True, + "ac3": "", + "ac3ts": False, + "acf": "probably", + "acfts": False, + "acmp4": "maybe", + "acmp4ts": False, + "acmp3": "probably", + "acmp3ts": False, + "acwm": "maybe", + "acwmts": False, + "ocpt": False, + "ckwa": False, + "spwn": False, + "emt": False, + "bfr": False, + "tz": -120, + "hdn": False, + "xt1": False, + "cdhf": False, + "eva": 33, + "cokys": ",loadTimes,csi,app", + "ecpc": False, + "nhi": ",64,true,Nexus 5,Android,6.0,138.0.7204.184,false", + "k_lyts": 48, + "k_lytk": "kg20va`l\u00a7'w8mh.71pdfoqcn[zy365x/\\,-4bt9siu=j;r]e", + "emd": "k:ai,vi,ao", + "wwl": False, + "glvd": "Google Inc. (ARM)", + "glrd": "ANGLE (ARM,Mali-G57 MC2,OpenGL ES 3.2)", + "tzp": "Europe/Paris", + "isf": True, + "isf2": False, + "dt": False, + "fph": 416468867, + "jset": 1754750959 + } + fingerprint_json = json.dumps(fingerprint) + print(encrpte_to_jspl(fingerprint_json))