700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 使用非对称加密(RSA)实现前端加密后端解密。

使用非对称加密(RSA)实现前端加密后端解密。

时间:2021-04-03 01:57:29

相关推荐

使用非对称加密(RSA)实现前端加密后端解密。

目录

1、前言2、例子 - 实现简单的登录和注册功能2.1、创建spring项目2.2、dao层、service层、entity层、mapper.xml文件(熟悉一般业务的可以跳过)2.3、RSA关键代码2.4、运行结果 3、总结

1、前言

现有比较熟知的加密方式有:MD5、对称加密(单密钥加密)、非对称加密(双密钥)。其中MD5使用最广泛,但是安全性最高的还是RSA。

MD5:可以将任意长度的输入串经过计算得到固定长度的输出,而且只有在明文相同的情况下,才能等到相同的密文,并且这个算法是不可逆的,即便得到了加密以后的密文,也不可能通过解密算法反算出明文。这样就可以把用户的密码以MD5值(或类似的其它算法)的方式保存起来,用户注册的时候,系统是把用户输入的密码计算成 MD5 值,然后再去和系统中保存的 MD5 值进行比较,如果密文相同,就可以认定密码是正确的,否则密码错误。通过这样的步骤,系统在并不知道用户密码明码的情况下就可以确定用户登录系统的合法性。

对称加密:双方拥有共同的密钥,一方使用密钥加密明文,另一方使用相同的密钥解密密文,缺点也很明显,只要一方泄露了密钥,对于数据都是不安全的。常用的算法有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK…。

RSA:生成两把密钥(公钥、私钥),一般情况下私钥留给自己,公钥作为提供 - 公钥加密数据成密文(每次加密的结果都不相同),密文只能用生成的对应的私钥解密。RSA算法的保密强度随其密钥的长度增加而增强。但是,密钥越长,其加解密所耗用的时间也越长。因此,要根据所保护信息的敏感程度与攻击者破解所要花费的代价值不值得以及系统所要求的反应时间来综合考虑。

2、例子 - 实现简单的登录和注册功能

这里我使用一个简单的登录、注册功能来介绍RSA的使用。

技术:spring boot+mybatis+Ajax

2.1、创建spring项目

每次创建完一个项目需要测试一下,我一般习惯写一个“hello word”测试运行环境,这里我不做详细介绍,不知道怎么测试或者有兴趣的小伙伴可以看我以往有关spring boot的博客。

2.2、dao层、service层、entity层、mapper.xml文件(熟悉一般业务的可以跳过)

这是我的数据库表结构和yml配置

# 连接数据库spring:datasource:username: password: url: jdbc:mysql://localhost:3306/cap?serverTimeZone=UTC&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driver# 清理thymeleaf缓存thymeleaf:cache: false# 整合mybatismybatis:type-aliases-package: com.desiy.entitymapper-locations: classpath:mapper/**.xml

注意:不要把usernamepassword写成data-usernamedata-password,不然会报错。

实体类Admin

@Data@NoArgsConstructor@AllArgsConstructorpublic class Admin {private String rel_name;private String username;private String password;}

dao层 - AdminDao

@Mapperpublic interface AdminDao {Admin selectAdmin(String username);String selectPassword(String username);String selectUsername(String username);// 用户注册时需要void add(String rel_name,String username,String password);}

service层 - AdminService and AdminServiceImpl

public interface AdminService {Admin login(String username, String password);String selectPassword(String username);String selectUsername(String username);void add(String rel_name, String username, String password);}

@Servicepublic class AdminServiceImpl implements AdminService {@AutowiredAdminDao adminDao;@Overridepublic Admin login(String username, String password) {return adminDao.selectAdmin(username);}@Overridepublic String selectPassword(String username) {return adminDao.selectPassword(username);}@Overridepublic String selectUsername(String username) {return adminDao.selectUsername(username);}@Overridepublic void add(String rel_name, String username, String password) {adminDao.add(rel_name, username, password);}}

AdminDao.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.desiy.dao.AdminDao"><select id="adminList" resultType="Admin">select * from admin</select><select id="selectAdmin" resultType="Admin">select * from admin where username = #{username}</select><select id="selectPassword" resultType="string">select password from admin where username = #{username}</select><select id="selectUsername" resultType="string">select username from admin where username = #{username}</select><insert id="add" parameterType="Admin">insert into cap.admin(rel_name,username,password)values (#{rel_name},#{username},#{password})</insert></mapper>

2.3、RSA关键代码

导入相关依赖:

<!--下面两个是RSA需要的依赖--><!-- /artifact/commons-io/commons-io --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version></dependency><!-- /artifact/commons-codec/commons-codec --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.10</version></dependency>

前端 - jsencrypt.js

后端 - RSAUtils.java:

这里的jsencrypt.js我是用的是 jsencrypt的 cdn

<script src="/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script>

获取公钥、私钥、以及公钥加密解密,私钥加密解密,分段加密、解密

import mons.codec.binary.Base64;import mons.io.IOUtils;import javax.crypto.Cipher;import java.io.ByteArrayOutputStream;import java.security.*;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.HashMap;import java.util.Map;public class RSAUtils {RSA rsa = new RSA();public static final String CHARSET = "UTF-8";public static final String RSA_ALGORITHM = "RSA"; // ALGORITHM ['ælgərɪð(ə)m] 算法的意思public static Map<String, String> createKeys(int keySize) {// 为RSA算法创建一个KeyPairGenerator对象KeyPairGenerator kpg;try {kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);} catch (NoSuchAlgorithmException e) {throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");}// 初始化KeyPairGenerator对象,密钥长度kpg.initialize(keySize);// 生成密匙对KeyPair keyPair = kpg.generateKeyPair();// 得到公钥Key publicKey = keyPair.getPublic();String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());// 得到私钥Key privateKey = keyPair.getPrivate();String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());// map装载公钥和私钥Map<String, String> keyPairMap = new HashMap<String, String>();keyPairMap.put("publicKey", publicKeyStr);keyPairMap.put("privateKey", privateKeyStr);// 返回mapreturn keyPairMap;}/*** 得到公钥** @param publicKey 密钥字符串(经过base64编码)* @throws Exception*/public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {// 通过X509编码的Key指令获得公钥对象KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);return key;}/*** 得到私钥** @param privateKey 密钥字符串(经过base64编码)* @throws Exception*/public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {// 通过PKCS#8编码的Key指令获得私钥对象KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);return key;}/*** 公钥加密** @param data* @param publicKey* @return*/public static String publicEncrypt(String data, RSAPublicKey publicKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, publicKey);return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));} catch (Exception e) {throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);}}/*** 私钥解密** @param data* @param privateKey* @return*/public static String privateDecrypt(String data, RSAPrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, privateKey);return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);} catch (Exception e) {throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);}}/*** 私钥加密** @param data* @param privateKey* @return*/public static String privateEncrypt(String data, RSAPrivateKey privateKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);//每个Cipher初始化方法使用一个模式参数opmod,并用此模式初始化Cipher对象。此外还有其他参数,包括密钥key、包含密钥的证书certificate、算法参数params和随机源random。cipher.init(Cipher.ENCRYPT_MODE, privateKey);return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));} catch (Exception e) {throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);}}/*** 公钥解密** @param data* @param publicKey* @return*/public static String publicDecrypt(String data, RSAPublicKey publicKey) {try {Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, publicKey);return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);} catch (Exception e) {throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);}}//rsa切割解码 , ENCRYPT_MODE,加密数据 ,DECRYPT_MODE,解密数据private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {int maxBlock = 0; //最大块if (opmode == Cipher.DECRYPT_MODE) {maxBlock = keySize / 8;} else {maxBlock = keySize / 8 - 11;}ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] buff;int i = 0;try {while (datas.length > offSet) {if (datas.length - offSet > maxBlock) {//可以调用以下的doFinal()方法完成加密或解密数据:buff = cipher.doFinal(datas, offSet, maxBlock);} else {buff = cipher.doFinal(datas, offSet, datas.length - offSet);}out.write(buff, 0, buff.length);i++;offSet = i * maxBlock;}} catch (Exception e) {throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);}byte[] resultDatas = out.toByteArray();IOUtils.closeQuietly(out);return resultDatas;}public static void main(String[] args) {// 创建密钥对Map<String, String> keys = RSAUtils.createKeys(1024);// 从Map中获取密钥对String publicKey = keys.get("publicKey");String privateKey = keys.get("privateKey");// 获取公钥System.out.println("publicKey:"+publicKey);// 获取私钥System.out.println("privateKey:"+privateKey);}}

由上面的RSAUtils先获得公钥和私钥:

接着我创建 一个RSA类,放我们的密钥对。

首页代码:index.html

<!DOCTYPE html><html lang="en" xmlns:th=""><head><meta charset="UTF-8"><title>Title</title></head><body><form id="doLogin"><!--页面放置一个隐藏的input标签,用于存放公钥--><input type="hidden" th:value="${session.publicKey}" name="publicKey" id="publicKey"><!--用户名;autofocus=""--页面加载时有光标--><input type="text" name="username" required="" autofocus=""><!--密码--><input type="password" name="password" required=""><button type="button" id="bt">登录</button><a th:href="@{/AddPage}">注册</a></form></body><!--jquery cdn--><script src="/libs/jquery/2.1.4/jquery.min.js"></script><!--引入jsencrypt.js cdn--><script src="/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script><!--SweetAlert--><link href="/ajax/libs/limonte-sweetalert2/0.0.1/sweetalert2.css" rel="stylesheet"><script src="/ajax/libs/limonte-sweetalert2/0.0.1/sweetalert2.min.js"></script><script type="text/javascript">$("#bt").click(function () {let data = $("#doLogin").serializeArray();let publicKey = data[0].value;let username = data[1].value;let oldPwd = data[2].value;let encrypt_Pwd = encrypt(publicKey, oldPwd);let data1 = {"username": username, "encrypt_Pwd": encrypt_Pwd};$.ajax({url: '/user/login',type: 'POST',data: data1,dataType: 'json',success: function (res) {// 一旦设置的 dataType 选项,就不再关心 服务端 响应的 Content-Type 了// 客户端会主观认为服务端返回的就是 JSON 格式的字符串if (res.code == 200) {swal({title: "登录成功!",type: "success",closeOnConfirm: false,}, function () {window.location = "/go";});}if (res.code == 101) {swal({title: "登录失败。",text: "输入信息有误。",type: "error",confirmButtonText: "重新登录",});}},error: function () {swal({title: "登录失败",text: "网络异常",type: "error",confirmButtonText: "重新登录",});}});});// RSA前端加密function encrypt(key, oldPwd) {let encrypt = new JSEncrypt();encrypt.setPublicKey(key);let encrypted = encrypt.encrypt(oldPwd);return encrypted;}</script></html>

首页:

注册页代码:add.html

<!DOCTYPE html><html lang="en" xmlns:th=""><head><meta charset="UTF-8"><title>注册</title></head><body><form id="doRegister"><!--页面放置一个隐藏的input标签,用于存放公钥--><input type="hidden" th:value="${session.publicKey}" name="publicKey" id="publicKey"><div><label>真实姓名</label><input type="text" name="name" ></div><div><label>用户名</label><input type="text" name="username" ></div><div><label>密码</label><input type="password" name="password" ></div><button type="button" id="register">注册</button></form></body><!--jquery--><script src="/libs/jquery/2.1.4/jquery.min.js"></script><!--JSEncrypt--><script src="/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script><!--SweetAlert--><link href="/ajax/libs/limonte-sweetalert2/0.0.1/sweetalert2.css" rel="stylesheet"><script src="/ajax/libs/limonte-sweetalert2/0.0.1/sweetalert2.min.js"></script><script type="text/javascript">$("#register").click(function () {let data = $("#doRegister").serializeArray();console.log(data);let publicKey = data[0].value;let rel_name = data[1].value;let username = data[2].value;let old_Pwd = data[3].value;let encrypt_Pwd = encrypt(publicKey, old_Pwd);let rea_data = {"rel_name": rel_name, "username": username, "encrypt_Pwd": encrypt_Pwd};$.ajax({url: '/doAdd',type: 'post',dataType: 'json',encoding: 'UTF-8',data: rea_data,success: function (res) {// 一旦设置的 dataType 选项,就不再关心 服务端 响应的 Content-Type 了// 客户端会主观认为服务端返回的就是 JSON 格式的字符串console.log(res);if (res.code == 20) {swal({title: "注册成功!",text: "",type: "success",confirmButtonText: "立即登录",closeOnConfirm: false,}, function () {window.location = "/";})}if (res.code == 10) {swal({title: "注册失败。",text: "该用户名已被注册,请重新输入。",type: "error",confirmButtonText: "确认",});}return false;},error: function () {alert("服务器忙碌...");}});});// RSA前端加密function encrypt(key, oldPwd) {let encrypt = new JSEncrypt();encrypt.setPublicKey(key);let encrypted = encrypt.encrypt(oldPwd);return encrypted;}</script></html>

注册页面:

工具类 - MsgData.java

public class MsgData<T> {private Integer code;private String msg;private T data;public MsgData() {}public MsgData(Integer code, String msg) {this.code = code;this.msg = msg;}public MsgData(Integer code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}@Overridepublic String toString() {return "MsgData{" +"code=" + code +", msg='" + msg + '\'' +", data=" + data +'}';}}

controller层 - AdminController.java

import com.desiy.service.AdminService;import com.desiy.utils.MsgData;import com.desiy.utils.RSA;import com.desiy.utils.RSAUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpSession;import java.security.NoSuchAlgorithmException;import java.security.interfaces.RSAPrivateKey;import java.security.spec.InvalidKeySpecException;@Controllerpublic class AdminController {RSA rsa = new RSA();@AutowiredAdminService adminService;@GetMapping("/")public String defaultIndex(HttpSession session) {session.setAttribute("publicKey", rsa.publicKey);return "index";}/*** 跳转到首页(登录页面)** @param* @return*/@RequestMapping("/user/login")@ResponseBodypublic MsgData loginPage(String username,String encrypt_Pwd) throws InvalidKeySpecException, NoSuchAlgorithmException {MsgData msgData = new MsgData<>();// 后端获取私钥String privateKey = rsa.privateKey;// 获得RSA类型的私钥RSAPrivateKey rsaPrivateKey = RSAUtils.getPrivateKey(privateKey);// 从数据库中获取密文String select_Password = adminService.selectPassword(username);// 使用私钥解密select_PasswordString Decrypt_database = RSAUtils.privateDecrypt(select_Password, rsaPrivateKey);// 使用私钥解密经过前端加密用户输入的密文String Decrypt_web = RSAUtils.privateDecrypt(encrypt_Pwd, rsaPrivateKey);if (Decrypt_database.equals(Decrypt_web)) {msgData.setCode(200);msgData.setMsg("success");return msgData;} else {msgData.setCode(101);msgData.setMsg("fails");return msgData;}}@GetMapping("/go")public String loginPage() {return "success";}//进入注册页面@GetMapping("/AddPage")public String addPage() {return "admin/add";}//注册功能@RequestMapping("/doAdd")@ResponseBodypublic MsgData add(String rel_name, String username, String encrypt_Pwd) {MsgData msgData = new MsgData<>();// 注册:真实姓名和密码可以重复,但是用户名不行。String select_username = adminService.selectUsername(username);// null:表示未能从数据库中找到与username值一样的数据if (select_username == null) {adminService.add(rel_name, username, encrypt_Pwd);msgData.setCode(20);} else {msgData.setCode(10);}return msgData;}}

2.4、运行结果

进入首页,首先存入publicKey到session中,用户进入前端后,获取session中的key;

点击注册(我原先在数据库已经存放了一个username为xiaozhu的用户。)

所以,我们另想一个用户名:desiy,密码:123456

使用新注册的username和password进行登录

这是我的项目结构:

3、总结

注册:

用户在前端输入真实姓名、用户名、密码(明文)进行注册,经过jsencrypt.js将密码加密成密文。通过Ajax传送给后端,那么数据库中存储的就是用户输入姓名、用户名、密文。注册我们需要注意的是用户名重复的问题;我的想法是用户名和密码可以相同,但是用户名不能相同。sql语句中的selectPassword是根据username查询的。

登录:由上面的结果看出,登录时,前端只给后端传递了username和密文。后端如何判断?由于每次使用公钥加密同一明文的结果都不一样,也就是说,假设明文如果是1,第一次加密后密文是A,第二次加密1后的密文就不再是A。这里我用私钥解密密文(数据库中的密文以及用户登录时前端输入经加密后的密文),解密后再进行判断。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。