记XX大学身份认证管理平台密码加密解密流程

Fiddler拦截

开启Fiddler拦截https(设置拦截https就不详细介绍了)

点击登录

发现一个post参数是username和password,密码部分被加密了。

放行后返回cookies

代码解析

这里使用火狐浏览器查看按钮事件的js代码

点击查看代码

if(!isSliderCaptcha);{
     // 帐号登陆提交banding事件
     $("#casLoginForm");
     casLoginForm.submit(doLogin);
     doLogin() {
         var username = casLoginForm.find("#username");
         var password = casLoginForm.find("#password");
         if (!checkRequired(username, "usernameError")) {
             username.focus();
             return false;
         }
         if (!checkRequired(password, "passwordError")) {
             password.focus();
             return false;
         }
         var captchaResponse = casLoginForm.find("#captchaResponse");
         if (!checkRequired(captchaResponse, "cpatchaError")) {
             captchaResponse.focus();
             return false;
         }
         _etd2(password.val(), casLoginForm.find("#pwdDefaultEncryptSalt").val());
     }
 }

加密部分在_etd2(),参数分别为密码明文和密钥,继续看

function _etd2(_p0, _p1) {
    try {
        var _p2 = encryptAES(_p0, _p1);
        ("#casLoginForm").find("#passwordEncrypt").val(_p2);
    } catch(e) {("#casLoginForm").find("#passwordEncrypt").val(_p0);
    }
}

似乎明文传输也能被服务器接受,

加密算法就在_gas函数里,_rds(64)为取长度为64的随机字符串,与密码明文拼接,16位的随机字符串作为初始向量,AES加密算法,CBC模式,pk#7填充。

function encryptAES(data, _p1) {
    if (!_p1) {
        return data;
    }
    var encrypted = _gas(_rds(64) + data, _p1, _rds(16));// 64位随机数 + 密码 , 密钥, 16位随机数
    return encrypted;
}
function _gas(data, key0, iv0) {// 64位随机数 + 密码 , 密钥, 16位随机数
    key0 = key0.replace(/(^\s+)|(\s+$)/g, "");
    var key = CryptoJS.enc.Utf8.parse(key0);            // 加密密钥
    var iv = CryptoJS.enc.Utf8.parse(iv0);              //  初始向量
    var encrypted = CryptoJS.AES.encrypt(data, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.toString();
}

附上python加密解密代码(部分代码来自网络)

import urllib
from Crypto.Cipher import AES
import base64
import random
class AesCrypt():
    def __init__(self, key, model, iv):
        BS = 16
        self.pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
        self.model = {'ECB': AES.MODE_ECB, 'CBC': AES.MODE_CBC}[model]
        #self.key = self.add_16 (key)
        self.key = bytes(key,encoding="utf-8")
        self.iv = iv.encode ()
        if model == 'ECB':
            self.aes = AES.new (self.key, self.model)  # 创建aes对象
        elif model == 'CBC':
            self.aes = AES.new (self.key, self.model, self.iv)  # 创建aes对象
    def aesdecrypt(self, text):
        # CBC解密需要重新创建一个aes对象
        if self.model == AES.MODE_CBC:
            self.aes = AES.new(self.key, self.model, self.iv)
        text = base64.decodebytes(text.encode('utf-8'))
        self.decrypt_text = self.aes.decrypt(text)
        return self.decrypt_text.decode('utf-8').strip('')
    def aesencrypt(self, word):
        chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
        retStr = ''
        for i in range(64):
            retStr += chars[random.randint(0,len(chars)-1)]
        word = retStr + word
        word = self.pad(word)
        t = self.aes.encrypt(word.encode('utf-8'))
        b = base64.encodebytes(t)
        #b = t
        return str(b,encoding='utf-8')

现在使用的加密算法已经明了,还剩下两个问题:

  1. 加密的密钥从哪里来?
  2. 偏移使用随机数服务器怎么解密?

先解决第一个问题,密钥哪里来的?以上代码在_etd2()中传入的第二个参数就是密钥。

_etd2(password.val(), casLoginForm.find("#pwdDefaultEncryptSalt").val());

密钥取自pwdDefaultEncryptSalt,继续抓包,发现密钥直接保存在页面里。

第二个问题,偏移使用随机数那么服务器要怎么解密?

这里先简单了解一下AES CBC模式的加密过程:

CBC模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密从而得到密文片段,最后在将密文片段拼接起来得到完整的密文。

解密过程就与加密流程相反,可以看到在解密时初始向量只用在了第一个密文段中,因此即使使用了错误的初始向量,影响的也只是第一个密文段,这将导致初始向量在某些长字符串AES加密场景下形同虚设。

回到正题,这里的AES加密是64个随机字符加上密码明文,那么在解密的时候即使使用错误的初始向量,也仅仅是前16个字符错误,将整个字符串前64个截取掉,剩下的就是密码明文。

function encryptAES(data, _p1) {
    if (!_p1) {
        return data;
      }
    var encrypted = _gas(_rds(64) + data, _p1, _rds(16));// 64位随机数 + 密码 , 密钥, 16位随机数
    return encrypted;
}

至此加密解密流程分析完成。

写一下登录函数,简单的通过GET index.htm页面判断是否登录成功

# 判断是否登录成功
def is_login(self):
    self.url_index = 'http://x.xxxxxx.edu.cn/EIP/user/index.htm'
    r = self.connect.get(self.url_index, headers=self.headers).content.decode()
    #print("is_login:\n",r)
    r = r.find('请输入用户名')
    if(r == -1):
        return True
    else:
        return False
# 登录
def login(self):
    if(self.is_login() == False):
        print('-----------------登录中...----------------')
        headers_1 = self.headers.copy()
        headers_1['Sec-Fetch-Site'] = 'same-origin'
        headers_1['Sec-Fetch-Mode'] = 'navigate'
        headers_1['Sec-Fetch-User'] = '?1'
        headers_1['Sec-Fetch-Dest'] = 'document'
        headers_1['Content-Length'] = '292'
        headers_1['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
        headers_1['Referer'] = 'https://ids.xxxxx.edu.cn/authserver/login?service=https%3A%2F%2Fi.xxxxx.edu.cn%2FEIP%2Fcaslogin.jsp'
        # 获取登录参数
        r = self.connect.get(self.url_login,verify=False);
        soup = BeautifulSoup(r.content.decode(), 'html.parser')
        self.lt         = soup.find('input',{'name':'lt','type':'hidden'}).get('value')
        self.dllt       = soup.find('input',{'name':'dllt','type':'hidden'}).get('value')
        self.execution  = soup.find('input',{'name':'execution','type':'hidden'}).get('value')
        self._eventId   = soup.find('input',{'name':'_eventId','type':'hidden'}).get('value')
        self.rmShown    = soup.find('input',{'name':'rmShown','type':'hidden'}).get('value')
        # 加密密钥
        self.pwdDefaultEncryptSalt = soup.find('input',{'id':'pwdDefaultEncryptSalt','type':'hidden'}).get('value')
        # 加密密码
        se{"type":"inserter","blocks":[{"clientId":"211eef6d-820a-4b3c-a660-692668d54f0f","name":"core/code","isValid":true,"attributes":{},"innerBlocks":[]}]}lf.password = Crypto_Demo.AesCrypt(self.pwdDefaultEncryptSalt).aesencrypt(self.passwd)
        data = {
            'username':self.username,
            'password':self.password,
            'lt':self.lt,
            'execution':self.execution,
            '_eventId':self._eventId,
            'rmShown':self.rmShown,
            }
        # 登录 获取cookies
        r = self.connect.post(self.url_login, data=data, headers=headers_1,verify=False)

运行后得到cookies

在线加密网站:https://tool.lmeee.com/jiami/aes

有兴趣的同学可以试试看,AES加密模式:CBC填充:pkcs7padding密钥长度:128位

密钥和偏移随便1{“type”:”block”,”srcClientIds”:[“e5832fb4-51c1-415d-a269-e6812c480f35″],”srcRootClientId”:””}6个字符,给长度大于16的字符串加密,后得到的字符串进行解密,一个个改变偏移的字符看是会有什么变化。

参考:

https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86

https://zh.wikipedia.org/wiki/%E5%88%9D%E5%A7%8B%E5%90%91%E9%87%8F

https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

https://www.jianshu.com/p/45848dd484a9

https://blog.csdn.net/qq_28205153/article/details/55798628

https://zhuanlan.zhihu.com/p/78913397

moller_1
moller_1
文章: 16

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注