背景
有一个通讯录的网页,因为网站比较老旧,用户体验很不好。所以就想试着写一个更加易用的通讯录查询器。本文只讲了加密的这个过程,其中涉及到加密模式等的内容,可以参照PyCryptodome的文档,https://www.pycryptodome.org/en/latest/index.html,欢迎交流。
登录操作
分析网站的源码,登录时输入的密码会加密传输,加密方法如下:
document.forms[0].j_password.value = desEncrypt(document.forms[0].j_password.value);
从sources中找到了对应的js文件,又遇到了熟悉的 eval(function(p,a,c,k,e,d) 加密代码。这里附上一个在线解密代码的网站http://www.oicqzone.com/tool/eval/,可以一键生成源代码。下面是解密后的代码。
Com_RegisterFile("security.js");
Com_IncludeFile("aes.js");
Com_IncludeFile("base64.js");
var SECURITYKEY = {
toHexString: function(str) {
var temp = "";
for (i = 0; i < str.length; i++) {
temp += str.charCodeAt(i).toString(16)
}
return temp
},
_2: function() {
$.ajax({
async: false,
dataType: "script",
url: Com_Parameter.ResPath + "js/session.jsp?_=" + new Date().getTime()
});
var str = "";
if (window.getSessionId) {
str = getSessionId()
}
return str
},
supportEncodings: function() {
return ["aes", "des"]
},
get: function(encodeType) {
var str = SECURITYKEY._2();
if (encodeType == null || encodeType == 'aes') {
if (str.length < 32) {
str += "abcdefghijklmnopqrstuvwxyz1234567890"
}
str = str.toUpperCase();
var key = {};
key.key = str.substring(0, 16);
key.iv = str.substring(16, 32);
key.security = "\u4435\u5320\u4d35"
} else {
if (str.length < 16) {
str += "abcdefghijklmnopqrstuvwxyz"
}
str = str.toUpperCase();
var key = {};
key.key = SECURITYKEY.toHexString(str.substring(0, 8));
key.iv = SECURITYKEY.toHexString(str.substring(8, 16));
key.security = "\u4445\u5320\u4d45"
}
return key
},
getCookie: function(c_name) {
if (document.cookie.length > 0) {
var cookies = document.cookie.split(";");
for (i = 0; i < cookies.length; i++) {
var xc = cookies[i];
var cn = xc.substring(0, xc.indexOf("=")).toUpperCase();
cn = cn.replace(/^\s*/, "").replace(/\s*$/, "");
if (cn == c_name) {
return unescape(xc.substring(xc.indexOf("=") + 1, xc.length))
}
}
}
return ""
}
};
function base64Convert() {
return "\u4241\u5345\u3634{" + Base64.encode(arguments[0]) + "}"
}
function _0(xForm) {
if (xForm == null) {
xForm = document.forms[0]
}
if (xForm != null) {
if (xForm.encoding == "multipart/form-data") {
return true
}
}
return false
}
function _1(str, xForm, isX) {
if (_0(xForm)) {
return str
} else {
var val = str;
if (str != null && str.length > 0) str = base64Convert(str);
if (val != str) {
if (isX == true) {
val = "\u4645\u5810\u4d40" + str
} else {
val = "\u4649\u5820\u4d45" + str
}
}
return val
}
}
function desEncrypt(value, xForm, type) {
if (_0(xForm)) {
return value
} else {
var keyObj = {};
if (type == null || "aes" == type.toLowerCase()) {
keyObj = SECURITYKEY.get();
value = CryptoJS.AES.encrypt(value, CryptoJS.enc.Utf8.parse(keyObj.key), {
iv: CryptoJS.enc.Utf8.parse(keyObj.iv)
}).toString()
} else {
keyObj = SECURITYKEY.get('des');
value = CryptoJS.DES.encrypt(value, CryptoJS.enc.Hex.parse(keyObj.key), {
iv: CryptoJS.enc.Hex.parse(keyObj.iv)
})
}
return keyObj.security + value
}
}
function base64Encode(str, xForm) {
return _1(str, xForm)
}
function base64Encodex(str, xForm) {
return _1(str, xForm, true)
}
分析后可以看到加密的过程大概是以下的步骤:
- 通过 _2获取浏览器的sessionID
- 通过get,将sessionID分为了key和iv
- 通过CryptoJS.AES.encrypt方法加密,这里的value是输入的password,第二个参数是key,第三个参数是偏移iv
CryptoJS.AES.encrypt(value, CryptoJS.enc.Utf8.parse(keyObj.key), {iv: CryptoJS.enc.Utf8.parse(keyObj.iv)
- 回传加密后的密钥
这里我们通过python来完成这个加密过程,以下是核心步骤,这里用到了python中CryptoJS的库,PyCryptodome。
from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import pad
# sessionid为浏览器获取到的sessionID
id2 = sessionid
# 转为byte型
pw = b'password'
# 根据js代码中的过程,将sessionid分为了key和iv,且要进行转码
key = id2[0:16].encode("utf-8")
iv = id2[16:32].encode("utf-8")
# 根据长度,keyObj.security
security = "\u4445\u5320\u4d45"
# 定义一个AES加密方法,key和iv为上述值,AES.MODE_CBC一般为默认的mode
aes = AES.new(key,AES.MODE_CBC,iv)
# 通过pad将密码进行转换
text=aes.encrypt(pad(pw, AES.block_size))
# base64转码
text=str(base64.encodebytes(text))
# 获取相应的字符串
j_password = text[2:][:-3]
后面就是利用加密后的j_password ,可以通过requests来请求sessionid来爬了。