ts) 並放入 request 域。code.jsp)。onsubmit 觸發 JS ras():讀取公鑰,將 密碼\nTS\n 進行 RSA 加密,結果 Base64 編碼填入隱藏域 p;原密碼框置為同長度掩碼(如「0000」);提交表單至 action.jsp。action.jsp 接收參數,調用 Java 端登錄邏輯(見上篇),獲取聯系人並展示或存儲。<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="qq.QQContactsService"%>
<%@page import="qq.RSAInfo"%>
<%
QQContactsService service = QQContactsService.getInstance();
RSAInfo rsa = service.getRSAInfo(); // 獲取公鑰、域名、ts
request.setAttribute("domain", rsa.getDomain());
request.setAttribute("publicKey", rsa.getPublicKey());
request.setAttribute("ts", rsa.getTs());
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>QQ Import Contact</title>
<script language="JavaScript" src="js/auth.js"></script> <!-- 包含 RSA 加密實現 -->
</head>
<body>
<form name="form1" action="action.jsp" onsubmit="ras()" method="post">
QQ: <input type="text" name="uin" id="uin"><br>
PWD: <input type="password" name="pp" id="pp"><br>
verify: <input type="text" name="verifycode" id="verifycode">
<img src="code.jsp" id="captchaImg"><br>
<input type="hidden" name="domain" id="domain" value="${domain}">
<input type="hidden" name="ts" id="ts">
<input type="hidden" name="starttime" id="starttime">
<input type="hidden" name="p" id="p"> <!-- 加密後密碼 -->
<input type="submit" name="login" value="登錄導入">
</form>
<script type="text/javascript">
var $ = function(id) { return document.getElementById(id); };
function ras() {
var publicKey = "${publicKey}"; // JSP EL 輸出
var rsa = new RSAKey(); // auth.js 中定義
rsa.setPublic(publicKey, "10001"); // 指數固定為 0x10001
var ts = "${ts}";
// 加密:密碼 + '\n' + ts + '\n'
var encrypted = rsa.encrypt($("pp").value + '\n' + ts + '\n');
if (encrypted) {
$("p").value = hex2b64(encrypted); // 轉 Base64
}
// 掩碼原密碼,防止明文傳輸
var mask = "";
for (var i=0; i<$("pp").value.length; i++) mask += "0";
$("pp").value = mask;
$("starttime").value = new Date().getTime();
return true; // 允許表單提交
}
</script>
</body>
</html>
js/auth.js 關鍵部分)採用 jsbn 庫(MIT 許可),核心類:RSAKey、BigInteger、Base64 工具。頁面只需引入該 JS 文件即可使用 new RSAKey().setPublic(modulus, exponent).encrypt(data)。
package qq;
public class RSAInfo {
private String domain; // 登錄提交域名,如 m126.mail.qq.com
private String publicKey; // RSA 公鑰模數 (16進制字符串)
private String ts; // 時間戳
// getter/setter 省略
}
public class QQContactsService {
private static QQContactsService instance = new QQContactsService();
public static QQContactsService getInstance() { return instance; }
public RSAInfo getRSAInfo() {
// 1. 訪問登錄頁面 https://mail.qq.com/cgi-bin/loginpage
// 2. 解析 HTML 獲取公鑰、域名、ts(或從固定 JS 文件提取)
// 3. 返回封裝對象
// 注意:騰訊可能定期更換公鑰,需緩存並定時刷新
}
public List loginAndFetchContacts(String uin, String encryptedPwd, String verifyCode, String ts, String domain) {
// 使用 HttpClient 模擬登錄 POST,攜帶 Cookie
// 登錄成功後訪問聯系人接口(可能為 JSON 或 HTML)
// 解析並返回 Contact 列表
}
}
code.jsp<%@ page contentType="image/jpeg" import="java.awt.*,java.awt.image.*,javax.imageio.*,java.util.*" %>
<%
response.setHeader("Cache-Control", "no-store");
BufferedImage img = new BufferedImage(60, 20, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(new Color(0xEE,0xEE,0xEE)); g.fillRect(0,0,60,20);
g.setColor(Color.BLACK); g.drawRect(0,0,59,19);
String code = "";
for(int i=0;i<4;i++) code += (char)('0'+Math.random()*10);
session.setAttribute("qq_verify_code", code);
g.setColor(Color.RED); g.setFont(new Font("Arial", Font.BOLD, 16));
g.drawString(code, 8, 15);
g.dispose();
ImageIO.write(img, "JPEG", response.getOutputStream());
%>
action.jsp 必須再次校驗驗證碼。本方案通過「前端 RSA 加密 + 後端 HttpClient 模擬登錄」實現 QQ 聯系人導入,解決了密碼傳輸安全與動態參數獲取難題。但屬逆向工程方案,穩定性依賴騰訊前端邏輯不變。強烈建議正式項目申請 QQ 互聯 (OpenAPI) 官方授權,使用 OAuth 2.0 標準流程獲取聯系人,安全合規且長期穩定。