cqu-login-password-reverse

Overview

This article is an article focusing on the Chongqing University Unified Identity Authentication System (authserver.cqu.edu.cn) front-end password encryption reverse engineering practical notes. We will use the browser developer tools to find the encryption entry step by step, disassemble its internal logic, and finally use Python to completely reproduce the encryption process and simulate the login request.

The target system uses a common AES-128-CBC mode for password encryption, and combines the salt value prefix and random IV generated by a custom random character set, so that the ciphertext generated by the same plaintext password is different every time it is encrypted, effectively preventing attacks such as rainbow tables.

Disclaimer: This article is only used to learn and research front-end reverse technology. Please do not use the methods in this article for any illegal means. When using it, you should comply with the school's network and information security regulations.


Browser reverse analysis: locating encrypted entrance

First, we need to find the core code of password encryption through Chrome DevTools (other modern browsers operate similarly).

Step 1: Open the login page and developer tools

Visit the login page:http://authserver.cqu.edu.cn/authserver/login
pressF12Open the developer tools, switch to the Network panel, and check Preserve log to prevent the request record from being lost after the page jumps.

Step 2: Trigger login and capture packets

Enter an arbitrary username and password (Do not use a real password, it is only used to trigger the request) and click the "Login" button. At this point you will see aPOSTRequest, filter keywordsloginYou can locate it quickly.

The core parameters of the request include:

  • username: clear text username
  • password: A string of Base64-encoded ciphertext
  • ltdlltexecutionWait for one-time parameters

focus onpasswordway of generating.

Step 3: Locate the encryption function

trackpasswordThe following common methods are used to generate parameters:

  1. Network panel search: Enter in the search boxencryptorpwdDefaultEncryptSalt(common encryption salt name), you can quickly find relevant code snippets.
  2. XHR breakpoint + call stack traceback: Add one on the right side of the Sources panelXHR/fetch Breakpoint, the breakpoint URL is set to*login*, click Login again. The request will be paused before being sent. At this time, trace upward through the Call Stack on the right, and you can find the JavaScript code that initiated the encrypted call.

Eventually, we will embed the<script>The complete encryption logic is located within the tag.


Core dismantling of encryption process

The located encryption code mainly consists of three parts, which are analyzed one by one below.

1. AES-128-CBC underlying implementation

The system directly uses the classic CryptoJS library to encapsulate AES encryption. The core functions are as follows:

const CryptoJS = require("crypto-js");

function getAesString(data, key0, iv0) {
  // 去除密钥两端可能存在的空格
  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
  });
  // 返回 Base64 格式的密文
  return encrypted.toString();
}

Key Points:

  • Key length is fixed at 16 bytes (corresponds to AES-128)
  • Encryption mode is CBC, padding method is Pkcs7
  • The encryption result is directly converted into a Base64 string, which is the same as what is seen in the packet capture.passwordConsistent format

2. Custom random string generation

In order to make the encryption result of the same password different every time, the system uses a character set that removes confusing characters to generate random salt values ​​and IVs.

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;
}

As you can see, the character set has been removedoOLl9gqVvUuI1These easily confused characters can avoid errors during display or debugging.

3. Password encryption main function

main functionencryptAESIt is the entrance to the front-end call, which combines the random salt prefix, original password, random IV and fixed key:

function encryptAES(data, aesKey) {
  if (!aesKey) {
    return data;
  }
  // 核心组合:64位随机前缀 + 原始密码,再用随机16位IV加密
  var encrypted = getAesString(randomString(64) + data, aesKey, randomString(16));
  return encrypted;
}

Processing Flow:

  1. Generate a 64-bit random string as the salt prefix
  2. will随机前缀 + 明文密码Splicing to obtain the data to be encrypted
  3. Generate a 16-digit random string as IV
  4. Use the fixed key issued by the background (aesKey) for AES-128-CBC encryption

Complete reproduction of the login process

To simulate a complete login, in addition to the encrypted password, you also need to obtain the encryption key dynamically issued by the server and the one-time parameters required for login from the login page (such asltexecution)。

Preparation

  1. Install the required Python libraries:
    pip install requests beautifulsoup4 PyExecJS

Tips:PyExecJSRely on local Node.js environment, please make sure Node.js is installed.

  1. Organize the front-end encryption code into an independent JS file and save it ascqu_encrypt.js. Note: If executed in Node.js environment, you need to keeprequire("crypto-js")statement; if you copy the browser code directly, you may need to make corresponding adjustments. It is recommended to install crypto-js in the same directory first:
    npm install crypto-js

Python complete code

The following code will be completed: get the login page → extract the key and parameters → call JS encryption → send a simulated login request.

import requests
from bs4 import BeautifulSoup
import re
import execjs

# ====================== 配置部分 ======================
ENCRYPT_JS_PATH = "./cqu_encrypt.js"          # 加密 JS 文件路径
LOGIN_URL = "http://authserver.cqu.edu.cn/authserver/login"
TEST_USERNAME = "test_user"                  # 测试账号
TEST_PASSWORD = "test_pwd_123"               # 测试密码(切勿使用真实密码)
# ====================== 配置部分 ======================

COMMON_HEADERS = {
    "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.7",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": "http://authserver.cqu.edu.cn",
    "Pragma": "no-cache",
    "Referer": LOGIN_URL,
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
}

def get_login_params_and_cookies(session: requests.Session) -> tuple:
    """
    从登录页 HTML 中提取:
    1. 加密密钥 pwdDefaultEncryptSalt
    2. 一次性参数 lt、dllt、execution 等
    3. 登录页 cookies
    """
    response = session.get(LOGIN_URL, headers=COMMON_HEADERS, verify=False)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, "html.parser")

    # 提取加密密钥
    pwd_salt = ""
    for script in soup.find_all("script"):
        if script.string:
            match = re.search(r'pwdDefaultEncryptSalt\s*=\s*"([^"]+)"', script.string)
            if match:
                pwd_salt = match.group(1)
                break
    if not pwd_salt:
        raise ValueError("未找到加密密钥 pwdDefaultEncryptSalt!")

    # 提取隐藏 input 参数
    login_params = {}
    for inp in soup.find_all("input", type="hidden"):
        name = inp.get("name")
        value = inp.get("value", "")
        if name:
            login_params[name] = value

    # 补充固定参数和测试账号
    login_params["username"] = TEST_USERNAME
    login_params["dllt"] = login_params.get("dllt", "userNamePasswordLogin")
    login_params["_eventId"] = login_params.get("_eventId", "submit")
    login_params["rmShown"] = login_params.get("rmShown", "1")

    return pwd_salt, login_params, session.cookies

def encrypt_password(password: str, salt: str) -> str:
    """调用 JS 加密函数,返回加密后的密码"""
    with open(ENCRYPT_JS_PATH, "r", encoding="utf-8") as f:
        js_code = f.read()
    ctx = execjs.compile(js_code)
    return ctx.call("encryptAES", password, salt)

if __name__ == "__main__":
    requests.packages.urllib3.disable_warnings()
    session = requests.Session()

    try:
        print("正在获取登录参数和加密密钥...")
        pwd_salt, login_params, cookies = get_login_params_and_cookies(session)
        print(f"获取到的加密密钥:{pwd_salt}")

        print("正在加密密码...")
        encrypted_pwd = encrypt_password(TEST_PASSWORD, pwd_salt)
        login_params["password"] = encrypted_pwd
        print(f"加密后的密码:{encrypted_pwd}")

        print("正在发送模拟登录请求...")
        login_response = session.post(
            LOGIN_URL,
            headers=COMMON_HEADERS,
            data=login_params,
            verify=False,
            allow_redirects=True
        )
        login_response.raise_for_status()

        if "统一身份认证" not in login_response.text:
            print("✅ 模拟登录请求成功!(可能已跳转)")
        else:
            print("❌ 模拟登录请求失败!(仍在登录页)")
    except Exception as e:
        print(f"❌ 出错了:{str(e)}")

Supporting front-end JS code (cqu_encrypt.js)

// 注意:若使用 Node.js 执行,需预先执行 npm install crypto-js
const CryptoJS = require("crypto-js");

function getAesString(data, key0, iv0) {
  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();
}

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;
}

function encryptAES(data, aesKey) {
  if (!aesKey) {
    return data;
  }
  var encrypted = getAesString(randomString(64) + data, aesKey, randomString(16));
  return encrypted;
}

Security analysis and precautions

  1. Key Exposure Risk: Encryption KeypwdDefaultEncryptSaltWritten directly in the login page's JavaScript in clear text, any attacker with access to the login page can obtain it. This makes front-end encryption mainly play a role in preventing the leakage of plain text logs, but cannot resist the threat of man-in-the-middle attacks or active key extraction.
  2. Randomness Design: A 64-bit random salt value is spliced ​​in front of the password during each encryption, and then a random IV is used for CBC encryption. This ensures that the same password generates different ciphertext each time, effectively resisting rainbow table attacks and increasing the difficulty of ciphertext comparison and analysis.
  3. Transport Layer Security: Current access uses the HTTP protocol. Even if the password is encrypted, the entire session flow is still exposed to the unencrypted channel, and there is a risk of being tampered with or stolen by a middleman. It is recommended that the system be upgraded to HTTPS to provide transport layer protection.
  4. Learning Purpose: This article is for technical communication only. Please do not use the method for illegal purposes. Unauthorized testing of the system may violate laws, regulations and school rules.

Summarize

This article locates the front-end encryption code of Chongqing University’s unified identity authentication system through browser developer tools, disassembles the AES-128-CBC encryption mode, custom random salt value and IV generation logic in detail, and uses Python + PyExecJS to completely reproduce the password encryption process, and finally realizes a simulated login request.

This is a very typical front-end encryption case for unified identity authentication in universities/enterprises. After mastering this analysis idea and reproduction method, you can quickly respond to the reverse needs of similar systems. I hope this tutorial can provide some help for your front-end reverse learning journey.

Reminder again: Please study and test relevant technologies under the premise of legality and compliance.