Java安全系列-RSA登录表单加密

在Java Web开发项目中,经常会接触到有关于登录问题。在一般的开发过程中,由于没有申请CA证书,我们只能基于HTTP进行开发,然而对于Http数据连接而言,请求数据在进行传输时,采用的为明文不加密的方式进行传输,这便对数据安全造成了很大的威胁。

同样的,由于是基于Http进行构建,并且未对表单数据进行处理,数据出了被监听而被窃取外,还可以通过使用抓包分析的方式,将表单所提交的内容进行获取,从而窃取用户的信息,包括登录表单中所含的密码等信息。

而与Http相对而言,Https连接则更为安全,Https在Http连接的基础上,增加了SSL的特性,从而使得数据在传输过程中,经过加密传输,保证了数据安全。

为此我们可以基于Https的思想,在像服务器传输数据时,采用相类似的方式进行数据加密,则可以很大程度上的保证数据安全,并且防止抓包工具对数据进行获取。

项目构建

在该项目中,我们采用Spring Boot微框架进行搭建,从而减少配置文件的编写,若采用JSP+Servlet或SSM或SSH等框架搭建,也同样适用。

该项目依赖于其父项目:RSAEncrypt进行依赖,请安装好该项目后再进行该项目的构建!

该项目的基本原理为:

客户端发送GET请求,以访问登录页面;

服务器接收到该请求后,利用RSA技术,协商产生密钥对,用于加密及解密;

服务器将产生的公钥经由Base64编码后,传入页面进行渲染,并且将公钥的公共指数值16进制字符串传入页面待渲染,并且传入该公钥的modulus的Base64编码,该密钥对存储到服务器的session中;

浏览器根据服务器所设定的属性进行渲染,用户填写好信息,并请求提交

浏览器在前端,获取到公钥的modulus的Base64编码,并解码为16进制串,获取到公钥对应的公共指数,利用rsa进行密码加密;

将加密的数据重新设置为表单值,进行提交;

服务器接收到该表单,解析,并获取session中所存储的密钥对,对密文进行解密,并验证数据,回传结果即可。

以上为整个项目的基本流程。

java 前后端账号密码加密传输 java登陆加密_java 前后端账号密码加密传输

如何产生密钥对,在文章:Java安全系列-RSA加密 已有说明,不做重复介绍。

在Spring中,我们定义一个PageController,用来响应用户页面的请求:

package site.franksite.encrypt.controller;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import site.franksite.encrpt.rsaencrypt.KeyManager;
import site.franksite.encrpt.rsaencrypt.RSAKeyGenerator;
import site.franksite.encrpt.rsaencrypt.RSAValidator;
@Controller
public class PageController {
@GetMapping("/")
public String indexPage(Model mdl, HttpSession session) {
RSAKeyGenerator generator = new RSAKeyGenerator();
byte[] privateKeyEncoded = generator.getPrivateKeyEncoded();
byte[] publicKeyEncoded = generator.getPublicKeyEncoded();
KeyManager keyMan = new RSAValidator();
RSAPublicKey rsaPubKey = (RSAPublicKey) keyMan.restorePublicKey(Base64.decodeBase64(publicKeyEncoded));
BigInteger publicExponent = rsaPubKey.getPublicExponent();
BigInteger modulus = rsaPubKey.getModulus();
MapkeyPair = new HashMap();
Object obj = session.getAttribute("keys"); // 原始数据
if (null != obj) {
// 移除原始session
session.removeAttribute("keys");
}
session.setAttribute("keys", keyPair);
try {
String pubKeyStr = new String(publicKeyEncoded, "utf-8");
String priKeyStr = new String(privateKeyEncoded, "utf-8");
System.out.println(pubKeyStr);
System.out.println(priKeyStr);
keyPair.put(pubKeyStr, priKeyStr);
mdl.addAttribute("pubKey", pubKeyStr);
mdl.addAttribute("modulus", modulus.toString(16));
mdl.addAttribute("pubExep", publicExponent.toString(16));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "login";
}
}

如此,在系统启动后,将会响应根目录的请求,直接跳转为登录页面。登录页面的HTML模板为:

登录title>


head>

用户名:label>


密码:label>


fieldset>
form>
$('#login-form').on('submit', function() {
var pubKey = $('#pubKey').val()
if (null != pubKey) {
pubKey = pubKey.trim();
}
var username = $('#username').val()
var modulus = $('#modulus').val()
var e = $('#pubExep').val()
if (null != username) {
username = username.trim()
}
if (null != password) {
password = password.trim()
}
if (null != modulus) {
modulus = modulus.trim(); // 模,BigInteger
}
var password = $('#password').val()
// 启用RSA加密
var rsa = new RSAKey()
rsa.setPublic(modulus, e)
var pwdEncrypt = rsa.encrypt(password) // 密文
pwdEncrypt=hex2b64(pwdEncrypt)
var $form = $(")
var $inputUser = $("")
$inputUser.val(username)
var $inputPwd = $("")
$inputPwd.val(pwdEncrypt)
var $key = $("");
$key.val(pubKey)
$form.append($inputUser)
$form.append($inputPwd)
$form.append($key)
$form.attr('action', "/login")
$form.attr('method', 'post')
$('body').append($form)
$form.submit()
return false;
})
})script>
body>
html>

从代码中,我们有使用thymeleaf进行模板的渲染,在js中,我们引用了如下的js进行前端的加密和编解码:

jquery-2.1.4.min.js
jsbn.js
prng4.js
rng.js
jsbn2.js
base64.js
rsa.js
rsa2.js

上述的js文件,为必须的文件,他们相互依赖,并且由于依赖关系,必须保证如上的顺序关系,其中rsa依赖于jsbn(BigInteger),base64依赖于rng,rng依赖于prng4。

为此,前端界面我们将获得到公钥的有关信息。

如图中,我们将由Base64编码的公钥字符串渲染到前端,并渲染了一个指数为10001,并且modulus也渲染到了表单中。

在用户选择提交后,前端将会利用上述信息,对其进行RSA加密,并发送到服务器后端,后端将对其进行验证:

package site.franksite.encrypt.controller;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import site.franksite.encrpt.rsaencrypt.RSAEncrypt;
import site.franksite.encrpt.rsaencrypt.RSAValidator;
import site.franksite.encrypt.entity.ResultEntity;
@Controller
public class LoginController {
@PostMapping("/login")
public @ResponseBody ResultEntity login(@RequestParam("username") String username,
@RequestParam("password") String passwordBased64, HttpSession session, @RequestParam("pubKey") String pubKey) {
ResultEntity result = new ResultEntity();
Object keys = session.getAttribute("keys");
if (null == keys) {
result.setStatus(false);
result.setReason("该公钥已经失效!");
result.setData(pubKey);
} else {
@SuppressWarnings("unchecked")
MapkeyMap = (Map) keys;
String priKey = keyMap.get(pubKey);
RSAEncrypt validator = new RSAValidator();
byte[] dencrypt = validator.dencrypt(Base64.decodeBase64(priKey), Base64.decodeBase64(passwordBased64));
try {
System.out.println(new String(dencrypt, "utf-8"));
if (new String(dencrypt, "utf-8").equals("1234")) {
result.setStatus(true);
result.setData(username);
} else {
result.setStatus(false);
result.setReason("密码错误!");
result.setData(username);
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
session.removeAttribute("keys"); // 移除session
return result;
}
}

在这里,我们返回了一个Json实体,该实体包含属性为3个,分别为:状态,原因,数据。

实体如下:

package site.franksite.encrypt.entity;
public class ResultEntity {
private boolean status;
private String reason;
private Object data;
/**
*@return the status
*/
public boolean isStatus() {
return status;
}
/**
*@param status the status to set
*/
public void setStatus(boolean status) {
this.status = status;
}
/**
*@return the reason
*/
public String getReason() {
return reason;
}
/**
*@param reason the reason to set
*/
public void setReason(String reason) {
this.reason = reason;
}
/**
*@return the data
*/
public Object getData() {
return data;
}
/**
*@param data the data to set
*/
public void setData(Object data) {
this.data = data;
}
public ResultEntity() {
super();
// TODO Auto-generated constructor stub
}
public ResultEntity(boolean status, String reason, Object data) {
super();
this.status = status;
this.reason = reason;
this.data = data;
}
}

在进行提交后,可以看到,表单提交了一系列加密的数据信息

验证成功后,将在浏览器中直接解析出json字符串:

{
"status": true,
"reason": null,
"data": "hello"}

项目获取

github项目地址:RSALoginEncrypt