九江学院统一身份认证系统登录加密逆向分析
📋 目录
一、背景介绍
1.1 目标系统
系统名称 :九江学院统一身份认证平台
登录地址 :https://authserver.jju.edu.cn/authserver/login
目标 :分析并理解前端密码加密逻辑
1.2 技术栈
前端加密 :CryptoJS (AES-CBC加密)
后端框架 :基于CAS (Central Authentication Service)
传输协议 :HTTPS
1.3 分析工具
Chrome DevTools (网络抓包、调试)
HTML源码查看
JavaScript代码分析
二、初步侦察
2.1 网络请求抓包
打开登录页面,按 F12 进入开发者工具,切换到 Network 标签,执行一次登录操作:
1 2 3 4 5 6 7 8 9 POST /authserver/login? service=https://ehall.jju.edu.cn/login HTTP/1.1 Host: authserver.jju.edu.cn Content-Type: application/x-www-form-urlencoded username=202012345& password=U2FsdGVkX1%2BabcdefghijklmnopqrstuvwxyzABCDEF... & execution=e7637a06-8937-4197-b207-5986a5a159e6_... & _eventId=submit& cllt=userNameLogin
关键发现 :
✅ 用户名明文传输
✅ 密码已加密(Base64格式)
✅ 存在 execution 参数(类似CSRF Token)
2.2 页面源码分析
右键查看页面源代码,搜索关键字 password、encrypt、salt:
1 2 3 4 5 6 7 8 <!-- 发现密钥存储位置 --> <input type="hidden" id="pwdEncryptSalt" value="V4Kh77JL6yk52pNG"/> <!-- 加密后的密码存储字段 --> <input type="hidden" id="saltPassword" name="password"/> <!-- 原始密码输入框 --> <input id="password" name="passwordText" type="password"/>
发现 :
AES加密密钥直接嵌入在HTML页面的隐藏字段中
三、关键文件分析
3.1 加密核心文件:encrypt.js
定位到 /authserver/jjuThemea/static/common/encrypt.js
3.1.1 随机字符串生成器
1 2 3 4 5 6 7 8 9 10 11 12 // 定义随机字符集(排除易混淆字符 I, L, O, 0, 1, 9) var $aes_chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; var aes_chars_len = $aes_chars.length; // 生成指定长度的随机字符串 function randomString(len) { var retStr = ''; for (i = 0; i < len; i++) { retStr += $aes_chars.charAt(Math.floor(Math.random() * aes_chars_len)); } return retStr; }
原理解析 :
字符集包含 48 个字符(26个大写字母 + 22个小写字母和数字)
排除了 I, L, O, 0, 1, 9 等易混淆字符
使用 Math.random() 生成伪随机数
每次调用返回指定长度的随机字符串
3.1.2 AES加密核心函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // AES加密核心函数 function getAesString(data, key0, iv0) { // 去除密钥首尾空字符 key0 = key0.replace(/(^\s+)|(\s+$)/g, ""); // 将密钥和IV转换为CryptoJS可识别的格式 var key = CryptoJS.enc. Utf8.parse(key0); var iv = CryptoJS.enc.Utf8.parse(iv0); // 执行AES加密 var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv, // 初始化向量 mode: CryptoJS.mode.CBC, // CBC模式 padding: CryptoJS.pad.Pkcs7 // PKCS7填充 }); // 返回Base64编码的密文 return encrypted.toString(); }
原理解析 :
参数
作用
说明
data
明文数据
要加密的内容
key0
加密密钥
16字节AES密钥
iv0
初始化向量
16字节随机IV
mode: CBC
加密模式
密码块链接模式
padding: Pkcs7
填充方式
PKCS#7填充标准
3.1.3 完整加密流程
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 // 密码加密入口函数 function encryptPassword(pwd0, key) { try { return encryptAES(pwd0, key); } catch (e) { return pwd0; } } // 完整加密实现 function encryptAES(data, aesKey) { if (!aesKey) { return data; } // 关键步骤: // 1. 生成64位随机前缀 // 2. 拼接:随机前缀 + 原始密码 // 3. 生成16位随机IV // 4. 调用AES加密 var encrypted = getAesString( randomString(64) + data, // 64位随机前缀 + 密码 aesKey, // 加密密钥 randomString(16) // 16位随机IV ); return encrypted; }
加密流程可视化 :
1 2 3 4 5 6 7 8 9 10 11 12 13 原始密码: "MyPassword123" ↓ 生成64位随机前缀: "TxmRpDkNwYs... 64个字符..." ↓ 拼接明文: "TxmRpDkNwYs...64个字符... MyPassword123" ↓ 生成16位随机IV: "AbCdEfGhJkMnPqRs" ↓ AES-CBC加密 (使用密钥和IV) ↓ Base64编码 ↓ 最终密文: "U2FsdGVkX1+abc..."
3.2 登录逻辑文件:login.js
定位到 /authserver/jjuThemea/static/web/js/login.js
3.2.1 密钥获取与加密调用
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 // 表单验证与加密函数 function checkForm() { var cllt = $(". login-main #cllt").val(); if (cllt == 'userNameLogin') { // 验证用户名和密码... if (utils.requireInput($(LOGIN_USERNAME_ID), 0, 100, ... )) { return; } if (utils.requireInput($(LOGIN_PASSWORD_ID), 0, 32, ...)) { return; } // 从HTML页面获取AES密钥 var aesKey = $("#pwdEncryptSalt").val(); // 获取用户输入的密码 var password = $(LOGIN_PASSWORD_ID).val(); // 使用密钥加密密码 var encryptedPassword = encryptPassword(password, aesKey); // 将加密后的密码存入隐藏字段 $("#saltPassword").val(encryptedPassword); // 禁用原始密码输入框(防止明文提交) $(LOGIN_PASSWORD_ID).attr("disabled", "disabled"); } return true; }
流程说明 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 用户点击登录按钮 ↓ 触发 checkForm() 函数 ↓ 验证用户名和密码格式 ↓ 从页面获取密钥: $("#pwdEncryptSalt").val() ↓ 获取用户输入: $(LOGIN_PASSWORD_ID).val() ↓ 调用加密函数: encryptPassword(password, aesKey) ↓ 将密文存入隐藏字段: $("#saltPassword").val(encrypted) ↓ 禁用原始密码输入框 ↓ 提交表单(只提交密文)
3.3 HTML页面分析:login_page.html
3.3.1 关键表单字段
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 <!-- 完整的登录表单结构 --> <form method="post" id="pwdFromId" action="/authserver/login"> <!-- 用户名输入框 --> <input id="username" name="username" type="text" placeholder="请输入学号/工号"/> <!-- 原始密码输入框(提交时会被禁用) --> <input id="password" name="passwordText" type="password" placeholder="请输入密码" maxlength="32"/> <!-- AES加密密钥(从服务器动态生成) --> <input type="hidden" id="pwdEncryptSalt" value="V4Kh77JL6yk52pNG"/> <!-- 加密后的密码(真正提交给服务器的字段) --> <input type="hidden" id="saltPassword" name="password"/> <!-- 验证码输入框(如果需要) --> <input id="captcha" name="captcha" type="text"/> <!-- 登录类型标识 --> <input type="hidden" id="cllt" name="cllt" value="userNameLogin"/> <!-- 登录方式标识 --> <input type="hidden" id="dllt" name="dllt" value="generalLogin"/> <!-- 执行流程标识(类似CSRF Token) --> <input type="hidden" id="execution" name="execution" value="e7637a06-8937-4197-b207-5986a5a159e6_..."/> <!-- 事件标识 --> <input type="hidden" name="_eventId" value="submit"/> <!-- 登录按钮 --> <button type="button" onclick="startLogin(this)">登录</button> </form>
字段说明表 :
字段名
ID/Name
类型
作用
是否提交
用户名
username
text
用户输入
✅ 明文提交
原始密码
passwordText
password
用户输入
❌ 被禁用
加密密码
password (saltPassword)
hidden
存储密文
✅ 密文提交
加密密钥
pwdEncryptSalt
hidden
存储密钥
❌ 不提交
验证码
captcha
text
用户输入
✅ 明文提交
执行标识
execution
hidden
CSRF保护
✅ 提交
3.3.2 密钥生成机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 服务器端(伪代码): @GetMapping("/login") public String loginPage(HttpSession session, Model model) { // 1. 生成16位随机密钥 String aesKey = generateRandomKey(16); // 例如: "V4Kh77JL6yk52pNG" // 2. 存入服务器Session(用于后续解密) session.setAttribute("aesKey", aesKey); // 3. 将密钥传递给前端页面 model.addAttribute("pwdEncryptSalt", aesKey); return "login"; }
密钥生命周期 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 用户访问登录页面 ↓ 服务器生成随机密钥(16位) ↓ 存入Session: session.put("aesKey", key) ↓ 嵌入HTML: <input id="pwdEncryptSalt" value="xxx"/> ↓ 返回页面给浏览器 ↓ JavaScript读取密钥进行加密 ↓ 用户提交登录 ↓ 服务器从Session取出密钥 ↓ 使用相同密钥解密密码 ↓ 验证密码是否正确
四、加密流程深度解析
4.1 完整加密流程图
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 ┌─────────────────────────────────────────────────────┐ │ 步骤1: 用户输入密码 │ │ 输入: "MyPassword123" │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤2: JavaScript从HTML获取密钥 │ │ $("#pwdEncryptSalt").val() → "V4Kh77JL6yk52pNG" │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤3: 生成64位随机前缀 │ │ randomString(64) │ │ → "TxmRpDkNwYsFqJzH... 64个字符..." │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────���───────────────────────────────────┐ │ 步骤4: 拼接明文 │ │ "TxmRpDkNwYsF...64位... MyPassword123" │ │ 总长度: 64 + 13 = 77 字符 │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤5: 生成16位随机IV │ │ randomString(16) │ │ → "AbCdEfGhJkMnPqRs" │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤6: UTF-8编码 │ │ 明文 → 77字节 │ │ 密钥 → 16字节 │ │ IV → 16字节 │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤7: PKCS7填充 │ │ 77字节 → 填充到16的倍数 → 80字节 │ │ 填充内容: 添加3个字节 0x03 │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤8: AES-CBC加密 │ │ 明文块1(16B) ⊕ IV → AES → 密文块1 │ │ 明文块2(16B) ⊕ 密文块1 → AES → 密文块2 │ │ 明文块3(16B) ⊕ 密文块2 → AES → 密文块3 │ │ 明文块4(16B) ⊕ 密文块3 → AES → 密文块4 │ │ 明文块5(16B) ⊕ 密文块4 → AES → 密文块5 │ │ 结果: 80字节密文 │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤9: 打包IV和密文 │ │ [IV(16字节)][密文(80字节)] = 96字节 │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤10: Base64编码 │ │ 96字节 → Base64 → 128字符 │ │ "U2FsdGVkX1+abcdefghijk..." │ └────────────────┬────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 步骤11: 提交到服务器 │ │ POST /authserver/login │ │ password=U2FsdGVkX1+abc... │ └─────────────────────────────────────────────────────┘
4.2 三大核心机制
4.2.1 机制一:64位随机前缀
作用 :防止密码字典攻击
1 2 // 实现代码 randomString(64) + password
原理 :每次加密都在密码前添加64位随机字符,使得相同密码产生完全不同的密文
4.2.2 机制二:16位随机IV
作用 :确保相同明文产生不同密文
1 2 3 // 实现代码 var iv = randomString(16); CryptoJS.AES.encrypt(data, key, { iv: iv, mode: CryptoJS.mode.CBC });
原理 :CBC模式下,IV参与第一个数据块的加密,每次使用不同的IV会产生不同的密文
4.2.3 机制三:动态密钥
作用 :防止密钥重用和会话劫持
1 2 <!-- 密钥存储在HTML页面中 --> <input type="hidden" id="pwdEncryptSalt" value="V4Kh77JL6yk52pNG"/>
原理 :服务器为每个会话生成独立的加密密钥,存储在Session中,客户端从HTML页面获取并使用
4.3 数据结构
4.3.1 加密前的明文结构
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌──────────────────────────────────────┬─────────────────┐ │ 64位随机前缀 │ 原始密码 │ ├──────────────────────────────────────┼─────────────────┤ │ TxmRpDkNwYsFqJzHcBnMaXvKgWeRtPsJf │ MyPassword123 │ │ DhLmQwZxCvBnMkYuHgTrEwQpLkJh │ │ └──────────────────────────────────────┴─────────────────┘ 64 字符 13 字符 ↓ 总长度: 77 字符 ↓ UTF-8编码: 77 字节 ↓ PKCS7填充: 80 字节
4.3.2 加密后的密文结构
1 2 3 4 5 6 7 8 9 10 ┌─────────────────┬──────────────────────────────────┐ │ IV (16字节) │ 实际密文 (80字节) │ ├─────────────────┼──────────────────────────────────┤ │ AbCdEfGhJkMn... │ [加密后的二进制数据] │ └─────────────────┴──────────────────────────────────┘ 16字节 80字节 ↓ 总计: 96字节 ↓ Base64编码: 128字符
4.4 服务器端解密流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 接收密文: "U2FsdGVkX1+abc..." ↓ Base64解码 → 96字节 ↓ 分离: IV(16字节) + 密文(80字节) ↓ 从Session获取密钥: "V4Kh77JL6yk52pNG" ↓ AES-CBC解密 ↓ 去除PKCS7填充 → 77字节 ↓ 去掉前64位随机前缀 ↓ 得到原始密码: "MyPassword123" ↓ 验证密码是否正确
五、总结
5.1 核心技术要点
加密算法 :
1 2 3 4 算法: AES-128 模式: CBC (Cipher Block Chaining) 填充: PKCS7 编码: Base64
三大核心机制 :
序号
机制
参数
作用
1
随机前缀
64位字符
防止字典攻击
2
随机IV
16字节
增加密文随机性
3
动态密钥
16字节
防止密钥重用
完整流程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 前端流程: 1. 访问登录页面 → 获取动态密钥 2. 生成64位随机前缀 → 拼接密码 3. 生成16位随机IV → 执行AES加密 4. 打包IV和密文 → Base64编码 5. 提交密文到服务器 后端流程: 1. 接收Base64密文 → 解码 2. 分离IV和密文 → 从Session获取密钥 3. AES解密 → 去除填充 4. 去掉64位前缀 → 得到原始密码 5. 验证密码 → 返回结果
5.2 关键知识点
密钥管理 :
来源: HTML页面隐藏字段 <input id="pwdEncryptSalt">
长度: 16字节 (AES-128)
生命周期: Session级别(约30分钟)
获取方式: $("#pwdEncryptSalt").val()
核心函数 :
1 2 3 randomString(length) // 生成随机字符串 getAesString(data, key, iv) // AES加密核心 encryptPassword(password, key) // 密码加密入口
5.3 常见问题
Q: 密钥从哪里来?
1 A: 从HTML页面的隐藏字段 <input id="pwdEncryptSalt"> 中获取
Q: 每次登录密钥都不同吗?
1 A: 是的,每次访问登录页面服务器会生成新的密钥
Q: 为什么要加64位随机前缀?
1 A: 防止密码字典攻击,增加破解难度到 10^108
Q: IV的作用是什么?
1 A: 确保相同密码产生不同密文,可以公开传输(编码在密文前16字节)
5.4 免责声明
1 2 3 本文档仅供学习和研究使用,请勿用于非法用途。 使用本文档内容造成的任何后果由使用者自行承担。 建议在合法合规的前提下学习相关技术。
文档信息
版本: v1.0
类型: 逆向分析文档
目的: 学习研究
许可: 仅供学习使用