简介
国密算法,即国家商用密码算法。是由国家密码管理局认定和公布的密码算法标准及其应用规范,其中部分密码算法已经成为国际标准。如SM系列密码,SM代表商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。 ——国密算法介绍
例如SM4:我国SM4分组密码算法正式成为ISO/IEC国际标准
这里常用的为SM2
和SM4
算法
SM2 简介
它是基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密,用于替换RSA/DH/ECDSA/ECDH等国际算法 。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。 SM2采用的是ECC 256位的一种,其安全强度比RSA 2048位高,且运算速度快于RSA。 ——国密算法介绍
JS实现 JS已有较为成熟的实现库,这里推荐sm-crypto
,可实现SM2
、SM3
和SM4
上面链接的NPM库间接已给出示例,这里直接照搬: 密钥对
const sm2 = require ('sm-crypto' ).sm2 let keypair = sm2.generateKeyPairHex () publicKey = keypair.publicKey privateKey = keypair.privateKey const compressedPublicKey = sm2.compressPublicKeyHex (publicKey) sm2.comparePublicKeyHex (publicKey, compressedPublicKey) let keypair2 = sm2.generateKeyPairHex ('123123123123123' )let keypair3 = sm2.generateKeyPairHex (256 , SecureRandom )let verifyResult = sm2.verifyPublicKey (publicKey) verifyResult = sm2.verifyPublicKey (compressedPublicKey)
加解密
const cipherMode = 1 let encryptData = sm2.doEncrypt (msgString, publicKey, cipherMode) let decryptData = sm2.doDecrypt (encryptData, privateKey, cipherMode) encryptData = sm2.doEncrypt (msgArray, publicKey, cipherMode) decryptData = sm2.doDecrypt (encryptData, privateKey, cipherMode, {output : 'array' })
来测试一下:
const sm2 = require ('sm-crypto' ).sm2 let keypair = sm2.generateKeyPairHex () publicKey = keypair.publicKey privateKey = keypair.privateKey const compressedPublicKey = sm2.compressPublicKeyHex (publicKey) sm2.comparePublicKeyHex (publicKey, compressedPublicKey) let verifyResult = sm2.verifyPublicKey (publicKey) verifyResult = sm2.verifyPublicKey (compressedPublicKey) console .log (verifyResult)const cipherMode = 1 msgString = 'HELLO WORLD' let encryptData = sm2.doEncrypt (msgString, publicKey, cipherMode) let decryptData = sm2.doDecrypt (encryptData, privateKey, cipherMode) console .log (encryptData)console .log (decryptData)
输出结果:
true f378b1ea6ee7d8d1729ee38fc5bbe8ccc2c2813f7fc07bcad877169d57fa5e2212d994cbf4c4dff78bd58acc82304373013e5d2e793c0ad2aafc90ab2c65083af6762f53b00324f091f67df9cac9d851a31baee07dfc19114bc0188dcb7aa37c9c9f9bfb43dec6a0712225 HELLO WORLD
SM4
是我国自主设计的分组对称密码算法,用于替代DES/AES等国际算法 。SM4算法与AES算法具有相同的密钥长度、分组长度,都是128bit。于2012年3月21日发布,适用于密码应用中使用分组密码的需求。 ——国密算法介绍
SM4 NPM库中也给出了示例,同样照搬: 加密
const sm4 = require ('sm-crypto' ).sm4 const msg = 'hello world! 我是 juneandgreen.' const key = '0123456789abcdeffedcba9876543210' let encryptData = sm4.encrypt (msg, key) let encryptData = sm4.encrypt (msg, key, {padding : 'none' }) let encryptData = sm4.encrypt (msg, key, {padding : 'none' , output : 'array' }) let encryptData = sm4.encrypt (msg, key, {mode : 'cbc' , iv : 'fedcba98765432100123456789abcdef' })
解密
const encryptData = '0e395deb10f6e8a17e17823e1fd9bd98a1bff1df508b5b8a1efb79ec633d1bb129432ac1b74972dbe97bab04f024e89c' const key = '0123456789abcdeffedcba9876543210' let decryptData = sm4.decrypt (encryptData, key) let decryptData = sm4.decrypt (encryptData, key, {padding : 'none' }) let decryptData = sm4.decrypt (encryptData, key, {padding : 'none' , output : 'array' }) let decryptData = sm4.decrypt (encryptData, key, {mode : 'cbc' , iv : 'fedcba98765432100123456789abcdef' })
实战 逆向 目标:aHR0cHM6Ly9mdXd1Lm5oc2EuZ292LmNuL25hdGlvbmFsSGFsbFN0LyMvc2VhcmNoL21lZGljYWw=
注意:只做技术研究、交流!
可以看到需要逆向的参数有很多: 请求头: 表单: 响应结果:
一顿找(推荐搜索关键字):
本来想半扣的,毕竟这里面比如那个r
就是个标准的SHA256加密,可以直接用三方库,SM不就说了,当时在半扣的过程中,越扣越多,索性全扣得了
同时需要一个参数t
,直接在控制台输出t
看看:
vat t ={ "transformRequest" : {}, "transformResponse" : {}, "timeout" : 30000 , "xsrfCookieName" : "XSRF-TOKEN" , "xsrfHeaderName" : "X-XSRF-TOKEN" , "maxContentLength" : -1 , "headers" : { "common" : { "Accept" : "application/json, text/plain, */*" }, "delete" : {}, "get" : {}, "head" : {}, "post" : { "Content-Type" : "application/x-www-form-urlencoded" }, "put" : { "Content-Type" : "application/x-www-form-urlencoded" }, "patch" : { "Content-Type" : "application/x-www-form-urlencoded" }, "Accept" : "application/json" , "Content-Type" : "application/json" , "channel" : "web" }, "withCredentials" : false , "baseURL" : "/ebus/fuwu/api" , "method" : "post" , "url" : "/nthl/api/CommQuery/queryFixedHospital" , "data" : { "addr" : "" , "regnCode" : "340600" , "medinsName" : "" , "medinsLvCode" : "" , "medinsTypeCode" : "" , "openElec" : "" , "pageNum" : 5 , "pageSize" : 10 } }
t
明显可直接写死,其中一些参数猜测可能为:
regnCode:地区代码(打开此网站会检测你IP位置并在控制台输出)
pageNum:翻页
pageSize:一页内容量
如下所示,打开网站在开发人员工具开启下重新刷新页面,会看到地区检测结果:
如果想要获取到其它位置,自行研究API了
将其封装进代码,最终Webpack代码6W行,如果想将生成相关的代码自己扣下来的话(因为这段代码被包含在了Webpack中)同时还要注意去掉(仅自己封装用于生成表单和请求头的代码)try-catch
语句,不然少了参数都不知道。
因为生成加密参数的代码被包含在Webpack中"7d92"
,所以可以试试不自己扣(因为上面所有的Webpack代码已经全扣了),那么要如何调用呢?
可以知道代码在"7d92"
,那么先输出一下webpack("7d92")
(这里是自行导出了加载器,详见Webpack文章)
console .log (webpack ("7d92" ))
结果:
{ a: [Getter], b: [Getter] }
选择a
再输出:
console .log (webpack ("7d92" ).a )
结果:
可以看到a
为一个函数,并且其名字似乎预示选对了 再调用一下函数:
console .log (webpack ("7d92" ).a ())
结果(只截取片段):
return t.headers["x-tif-paasid"] = l.paasId,
发现报错,很明显,这里便是我们要找的加密方法,把上面的找到的t
塞进去看看结果:
console .log (webpack ("7d92" ).a (t))
结果:
{ transformRequest: {}, transformResponse: {}, timeout: 30000, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, headers: { common: { Accept: 'application/json, text/plain, */*' }, delete: {}, get: {}, head: {}, post: { 'Content-Type': 'application/x-www-form-urlencoded' }, put: { 'Content-Type': 'application/x-www-form-urlencoded' }, patch: { 'Content-Type': 'application/x-www-form-urlencoded' }, Accept: 'application/json', 'Content-Type': 'application/json', channel: 'web', 'x-tif-paasid': undefined, 'x-tif-signature': '092eeb43442f3633e5d22790695b39264f0f0834e14f60dfe99d514e95a2cf93', 'x-tif-timestamp': 1672929556, 'x-tif-nonce': 'Y4aUfjoK', contentType: 'application/x-www-form-urlencoded' }, withCredentials: false, baseURL: '/ebus/fuwu/api', method: 'post', url: '/nthl/api/CommQuery/queryFixedHospital', data: '{"data":{"data":{"encData":"3DFBCA4667B978F639BB23B95DCE4CC73F6F89D76EF932292FE8BA14BC87DB17CCD20943B4DAE96380B41164D761DE9742C84A985FE3BABC31CB352556BB87C9C1495DB24A29AB6BC3A85AB7FCA00F338EE714ACFC4C924F01CF575098AEF167B7A135B72DAC6C2F3CD510E411A3C63A7D334AF16D66C5F4C5E72412B915CB27"},"appCode":"T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ","version":"1.0.0","encType":"SM4","signType":"SM2","timestamp":1672929556,"signData":"2+IZoksuP1rgU4LhVryfmBs1icvANuGmjR3FNk95bJyTjyqm3TK66ts3oV7L1d3Aot5RXvB7GRCK9QjnypILxw=="}}' }
成功拿到需要的数据!
暂时放下,来看看结果如何处理。
最终找到此处: 此时e.data
还是原始响应结果,并未解密:
{ "code" : 0 , "data" : { "signData" : "+EJhLYxiMpbwjGCsEvK+wyXNoAyMKpkGnjb7tGZ3OoFNWqKzDjIeDfqE9ZSO0anEt3z4rNoFJ5lgHxeDuddVdg==" , "encType" : "SM4" , "data" : { "encData" : "2DC8C08C5C251B04B9CCBA9436917607527A4CC8F48CC3C8D7C226EB9483D8FA03CECA79ABA4AC1CE5 ....篇幅有限,这里省略.....FCF4D05388AA6A2E4E171B6486547C2E9DB06B68A81A61409372E7E00A0D1691A2F222D7698DCAC" }, "signType" : "SM2" , "appCode" : "T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ" , "version" : "1.0.0" , "timestamp" : "1672919504404" }, "message" : "成功" , "timestamp" : "1672919504" , "type" : "success" }
复制到本地备后测试使用。
同时这段代码也被包含在Webpack中,可以选择不扣,不过因为实际涉及解密的代码都集中在了一行上,这里选择半扣:
function get_data (data ){ const e = {data :data}, c = webpack ("7d92" ); return e.data .data .appCode && (e.data .data = Object (c.b )("SM4" , e.data )),e.data }
看到这里可以猜测,webpack("7d92")
里a
负责加密,b
负责解密 将备好的数据放进去看运行结果: 成功解密
简单封装下代码,准备模拟请求
模拟请求 这里继续使用Python来模拟请求,只爬取一页数据 同时要注意,从js代码获取的值需要进行一些处理才能让requests
得以正确传递, 同时如果遇到UnicodeEncodeError: 'gbk' codec...
问题请参照此文章:使用PyExecJS为Python爬虫提供加密参数
完整代码请见仓库 SM/gjyb
代码仅作学习交流之用,请勿用于非法用途!