某能源网站加密接口分析

一、登录

1.1 说明信息

需要登录以获取 jwt 和 Security_code,访问接口最少保留4项headers,其中 Authorization 为固定值。

header 备注
Authorization YKC-AUTHORIZATION 此项不对则提示:开发商标识认证失败
Cipher-Type 2 值1默认密钥,值2新计算密钥。此项不对可能提示:客户端版本过低
Head_token 1080,x.y.z 此项不对则提示: 未登录或登录过期
Security_code 1722561172319 与 Head_token 配合使用,提示亦同。

1.2 通过 sso 登录步骤

  1. GET ssoLogin 获取初始 Cookie,并跟踪 302 重定向获取跳转地址;
  2. POST 统一身份账户密码(密码用base64编码)以及OTP到上一请求重定向到的地址,获取 SSO 的 token;
  3. POST api/basic-auth-mos/SSO/SAML2/POST 获取 accessToken(jwt格式)
  4. GET auth/toLogin 获取新能源网站的 accessToken
  5. GET toSSOLogin 换取新能源网站的 token
  6. POST exchangeWebKey 发送新sm4-key到服务器进行认证,后续加密用此key

二、访问接口获取数据

2.1 payload

payload 最少保留以下参数(header 和 body 为空时不能删除键)。

1
2
3
4
{
    "header": {},
    "body": {},
}

2.2 生成 nonce

4 位 ascii 字符。

新能源系统的生成算法为:Math.random().toString(24).substr(2, 4),即随机小数按24进制形式转字符串,然后取紧跟小数点后4位。

2.3 计算 sign

payload.body + timestamp + nonce,然后计算sm3

比如:{}1722581739000aokksm3d00f9525c288e524d343fc02d38739cd8d2fa6ac470685af4f1776e6e375f4d1

拼接时,如果payload中有参数,应当使用双引号并去空格,推荐使用:json.dumps(payload_body, separators=(',', ':'))
经测试,服务端未校验 sign

2.4 sm4 key 的生成和认证

  1. 随机生成 32 位16进制的数字新 sm4-key
  2. 使用 #1.2章节步骤5请求响应中的 pubKey,通过 sm2 加密算法加密新 sm4-key
  3. 和上述响应中的 accountIdsecurityCode 一起发送给服务器进行认证
  4. 后续加密使用新 sm4-key

2.5 payload 的加密

  • 未认证新密钥前,使用 86C63180C2806ED1F47B859DE501215B,通过 sm4 加密,Cipher-Type1
  • 认证新密钥后,使用新 sm4-key,通过 sm4 加密,Cipher-Type2

X. 新能源系统JS中常量密钥

1
2
3
4
5
6
ACCESSKEY: webbxxxxxxxxxxxhqmg
secret: xgxxxxxxxxxxx5j
pubKey: 86C63180C2806ED1F47B859DE501215B
iv: whxxxxxxxxxxxx87
store_secret: 86C63180C2806ED1F47B859DE501215B
chrsz: 0x8

Y. 完整 payload 样例

payload 中换行(格式化)后再加密不影响服务端识别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
    "header": {
        "version": "1",
        "token": "eyJhbGciOiJIUzI1NiJ9.xxx.yyy",
        "isSecurity": "",
        "security": "",
        "businessID": "030c499a-1664-4734-93d1-0451b03550a5",
        "isSync": "0",
        "expendTime": "",
        "resultCode": "",
        "resultDesc": "",
        "accessKey": "webbxxxxxxxxxxxhqmg",
        "timestamp": 1722579933000,
        "nonce": "nwf9",
        "sign": "fcf04e821cfdf1e88f0bb3ea938d1875a0fbc8106a109ec1588923fbaf0017b4"
    },
    "body": "{\"gunName\":\"\",\"zshDistricts\":[],\"startTime\":\"2024-08-02\",\"endTime\":\"2024-08-02\"}",
    "pageIndex": 1,
    "pageSize": 10
}

在浏览器访问新能源系统时,JS会将认证后的新密钥使用默认密钥通过SM4加密后存放在 session storage 中,变量名为:randomkey
在分析取数接口时,可提取该 randomkey,使用默认密钥通过SM4解密发送的payload以获取明文。

Z. JS中相关函数摘录

新 sm4-key 的 sm2 加密:

1
2
3
4
'encryptionRse': function(_0x572641, _0x58f843) {
    var _0x321fea = _0x25bcb6;
    return _0x33c5cb['a']['sm2'][_0x321fea('0x3f')](_0x572641, _0x58f843);
},

相关加密函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
'getRandomNum': function(_0xfc3874) {
    return function() {
        var _0x383b0b = a286_0x490b;
        return Object(_0x4c9909['b'])()[_0x383b0b('0xc3d')](0x24)[_0x383b0b('0xbbc')](0x2, 0x4);
    }
    ;
},
'getTimestamp': function(_0xc8dfef) {
    return function() {
        var _0x3ac05f = a286_0x490b;
        return Date[_0x3ac05f('0xb88')](new Date());
    }
    ;
},
'getSign': function(_0x2d4f91, _0x21e6d5) {
    return function(_0x4bd413, _0x40d01d, _0xa973ea) {
        var _0x136acf = _0x4bd413 + _0x40d01d + _0xa973ea;
        return _0x22731c['a']['sm3'](_0x136acf);
    }
    ;
},
'getMd5': function(_0x173feb) {
    return function(_0x4bd22b) {
        var _0x3d6d89 = a286_0x490b;
        return _0x461b01['a'][_0x3d6d89('0x19e')](_0x4bd22b)[_0x3d6d89('0xc3d')]();
    }
    ;
},
'doAesEncode': function(_0x2d0622, _0x2048ea) {
    return function(_0x50325a, _0x586f87) {
        var _0x1092cc = a286_0x490b
            , _0x5714b3 = _0x2d0622['pubKey'];
        if (!_0x586f87) {
            var _0x2a00b7 = window[_0x1092cc('0x654')][_0x1092cc('0x64b')](_0x1092cc('0x985')) || ''
                , _0x15d2b5 = _0x2a00b7 && _0x2048ea[_0x1092cc('0xb8d')](JSON[_0x1092cc('0xb88')](_0x2a00b7)[_0x1092cc('0x5e9')]);
            _0x5714b3 = _0x15d2b5;
        }
        return _0x22731c['a'][_0x1092cc('0x755')]['encrypt'](_0x50325a, _0x5714b3);
    }
    ;
},
'storeEncrypt': function(_0x15424e) {
    return function(_0x558d0d) {
        var _0x3335b8 = a286_0x490b;
        return _0x22731c['a'][_0x3335b8('0x755')][_0x3335b8('0x77d')](_0x558d0d, _0x15424e[_0x3335b8('0x19')]);
    }
    ;
},
'storeDecrypt': function(_0x2cd1a0) {
    return function(_0x9144ce) {
        var _0x4c412a = a286_0x490b;
        return _0x22731c['a']['sm4'][_0x4c412a('0x7c')](_0x9144ce, _0x2cd1a0[_0x4c412a('0x19')]);
    }
    ;
}
// 此函数用于计算设备指纹,返回一个整数比如:1366026294
// 此整数作为 uniqueId 参数传递
getFingerprint: function() {
    var u = "|"
    , p = a.ua
    , h = this.getScreenPrint()
    , v = this.getPlugins()
    , g = this.getFonts()
    , m = this.isLocalStorage()
    , y = this.isSessionStorage()
    , _ = this.getTimeZone()
    , b = this.getLanguage()
    , S = this.getSystemLanguage()
    , C = this.isCookie()
    , k = this.getCanvasPrint();
    return l(p + u + h + u + v + u + g + u + m + u + y + u + _ + u + b + u + S + u + C + u + k, 256)
}
updatedupdated2026-02-052026-02-05