javaweb项目登录非对称加密方法

14 篇文章 0 订阅

javaWeb项目登录非对称加密方法

  前一段时间项目中需要对于登录的时候将登录密码密文传输,因此就想到了用RSA非对称加密的方法。

1.流程图

  加密的流程图如下:
在这里插入图片描述

  项目中的逻辑也是如上图所示,主要的调用流程简单如下:
在这里插入图片描述
解释如下:
1)首先在登录页上输入账号密码。
2)点击登录后,首先调用后台一获取公钥接口,即/getPublicKey接口,该接口中后台会用rsa算法生成一对钥匙公钥私钥,并存到session中,公钥则返回给前端。
3)前端拿到公钥后,使用jsencrypt.js中的方法,设置公钥对象,然后将用户输入的密码进行加密。
4)最后调用登录接口,此时接口上传入的密码即为加密后的密码。

2.涉及接口

  整个流程比较简单。没有多少步骤。涉及的接口就两个:

/getPublicKey (获取公钥接口)
/security/loginNew(登录接口)

如下图是前端F12看到的浏览器调试器调用的接口图:
在这里插入图片描述

3.接口代码
3.1 获取公钥接口

  以下为前端第一个需要调用的接口,在该接口里,后台会通过下边的RSAUtils工具类生成一对钥匙,公钥+私钥,然后这两个钥匙存放到当前session下,当然这里看你们怎么设计,存到哪里都行,但是我建议尽量保证这一对钥匙只能用一次,用完即丢,这样可能会好点,防止被使用。
  因为每个客户端在首次访问项目的时候都会有唯一的cookie值,该值都对应后台的唯一session,因此存到当前请求里的session里,其实也不错。具体如下:

/**
	 * 获取公钥
	 * @param httpSession
	 */
	@RequestMapping(value="/getPublicKey",method = RequestMethod.GET)
	public Object getPublicKey(HttpSession httpSession){
		Map<String, String> rsaKeys = RSAUtils.createRSAKeys();
		httpSession.setAttribute("publicKey",rsaKeys.get(RSAUtils.PUBLIC_KEY_NAME));
		httpSession.setAttribute("privateKey",rsaKeys.get(RSAUtils.PRIVATE_KEY_NAME));
		return addResultMapMsg(true,"",rsaKeys.get(RSAUtils.PUBLIC_KEY_NAME));
	}

  如下即为RSAUtils工具类,直接可以使用。

package znxd.lxynzl.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Created by lingsf on 2020/3/17.
 */
public class RSAUtils {
    protected static final Log log = LogFactory.getLog(RSAUtils.class);
    private static String KEY_RSA_TYPE = "RSA";
    private static String KEY_RSA_TYPE_ALL = "RSA/ECB/PKCS1Padding";
    private static int KEY_SIZE = 1024;//JDK方式RSA加密最大只有1024位
    private static int ENCODE_PART_SIZE = KEY_SIZE/8;
    public static final String PUBLIC_KEY_NAME = "public";
    public static final String PRIVATE_KEY_NAME = "private";

    /**
     * 创建公钥秘钥
     * @return
     */
    public static Map<String,String> createRSAKeys(){
        Map<String,String> keyPairMap = new HashMap<>();//里面存放公私秘钥的Base64位加密
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE);
            keyPairGenerator.initialize(KEY_SIZE,new SecureRandom());
            KeyPair keyPair = keyPairGenerator.generateKeyPair();

            //获取公钥秘钥
            String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded());
            String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());

            //存入公钥秘钥,以便以后获取
            keyPairMap.put(PUBLIC_KEY_NAME,publicKeyValue);
            keyPairMap.put(PRIVATE_KEY_NAME,privateKeyValue);
        } catch (NoSuchAlgorithmException e) {
            log.error("当前JDK版本没找到RSA加密算法!");
            e.printStackTrace();
        }
        return keyPairMap;
    }

    /**
     * 公钥加密
     * 描述:
     *     1字节 = 8位;
     *     最大加密长度如 1024位私钥时,最大加密长度为 128-11 = 117字节,不管多长数据,加密出来都是 128 字节长度。
     * @param sourceStr
     * @param publicKeyBase64Str
     * @return
     */
    public static String encode(String sourceStr,String publicKeyBase64Str){
        byte [] publicBytes = Base64.decodeBase64(publicKeyBase64Str);
        //公钥加密
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);
        List<byte[]> alreadyEncodeListData = new LinkedList<>();

        int maxEncodeSize = ENCODE_PART_SIZE - 11;
        String encodeBase64Result = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
            cipher.init(Cipher.ENCRYPT_MODE,publicKey);
            byte[] sourceBytes = sourceStr.getBytes("utf-8");
            int sourceLen = sourceBytes.length;
            for(int i=0;i<sourceLen;i+=maxEncodeSize){
                int curPosition = sourceLen - i;
                int tempLen = curPosition;
                if(curPosition > maxEncodeSize){
                    tempLen = maxEncodeSize;
                }
                byte[] tempBytes = new byte[tempLen];//待加密分段数据
                System.arraycopy(sourceBytes,i,tempBytes,0,tempLen);
                byte[] tempAlreadyEncodeData = cipher.doFinal(tempBytes);
                alreadyEncodeListData.add(tempAlreadyEncodeData);
            }
            int partLen = alreadyEncodeListData.size();//加密次数

            int allEncodeLen = partLen * ENCODE_PART_SIZE;
            byte[] encodeData = new byte[allEncodeLen];//存放所有RSA分段加密数据
            for (int i = 0; i < partLen; i++) {
                byte[] tempByteList = alreadyEncodeListData.get(i);
                System.arraycopy(tempByteList,0,encodeData,i*ENCODE_PART_SIZE,ENCODE_PART_SIZE);
            }
            encodeBase64Result = Base64.encodeBase64String(encodeData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encodeBase64Result;
    }

    /**
     * 私钥解密
     * @param sourceBase64RSA
     * @param privateKeyBase64Str
     */
    public static String decode(String sourceBase64RSA,String privateKeyBase64Str){
        byte[] privateBytes = Base64.decodeBase64(privateKeyBase64Str);
        byte[] encodeSource = Base64.decodeBase64(sourceBase64RSA);
        int encodePartLen = encodeSource.length/ENCODE_PART_SIZE;
        List<byte[]> decodeListData = new LinkedList<>();//所有解密数据
        String decodeStrResult = null;
        //私钥解密
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
            cipher.init(Cipher.DECRYPT_MODE,privateKey);
            int allDecodeByteLen = 0;//初始化所有被解密数据长度
            for (int i = 0; i < encodePartLen; i++) {
                byte[] tempEncodedData = new byte[ENCODE_PART_SIZE];
                System.arraycopy(encodeSource,i*ENCODE_PART_SIZE,tempEncodedData,0,ENCODE_PART_SIZE);
                byte[] decodePartData = cipher.doFinal(tempEncodedData);
                decodeListData.add(decodePartData);
                allDecodeByteLen += decodePartData.length;
            }
            byte [] decodeResultBytes = new byte[allDecodeByteLen];
            for (int i = 0,curPosition = 0; i < encodePartLen; i++) {
                byte[] tempSorceBytes = decodeListData.get(i);
                int tempSourceBytesLen = tempSorceBytes.length;
                System.arraycopy(tempSorceBytes,0,decodeResultBytes,curPosition,tempSourceBytesLen);
                curPosition += tempSourceBytesLen;
            }
            decodeStrResult = new String(decodeResultBytes,"UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        return decodeStrResult;
    }
    public static void main(String[] args){
        Map<String, String> rsaKeys = RSAUtils.createRSAKeys();
        String publicKey = rsaKeys.get(RSAUtils.PUBLIC_KEY_NAME);
        System.out.println("publicKey:"+ publicKey);
        String privateKey = rsaKeys.get(RSAUtils.PRIVATE_KEY_NAME);
        System.out.println("privateKey:"+ privateKey);
//        String encode = RSAUtils.encode("znxd2018", publicKey);
        String encode = RSAUtils.encode("znxd2018", publicKey);
        System.out.println("公钥加密后:"+encode);
        String decode = RSAUtils.decode("CyOw0Z5yrDVWRPE3DDksEePBdRwB0SLouNaVA520VxNhjRA8mTEvdTxlhyugZXJYf8lVj57XUO1fsojuEpv6t9Vq3Nnd15AIhSp01GI7oS/BlzJ78cInX7AfCJBZciDbS9o9s9JRNmkI0JwM5WvVt9085Q6tXx2/JhyRlS7MMFk=", privateKey);
        System.out.println(decode);
    }
}

3.2 前端加密密码方法

  当前端拿到后端返回的公钥后,使用jsencrypt.js里的方法进行加密。

var encrypt = new JSEncrypt();
encrypt.setPublicKey($('#pubkey').val()); //从公钥接口上获取到的公钥
var encrypted = encrypt.encrypt($('#password').val()); //加密用户输入的密码

  当通过上边的方法加密后得到的encrypted值,即为加密后的密码。

3.3 调用登录接口

  登录接口无非主要就是验证账号密码,但是因为密码加密,因此就变成了先用匹配的私钥解密密码,再验证账号密码即可。

//此处获取到对应session里的私钥
Object privateKey = request.getSession().getAttribute("privateKey");
if(privateKey == null || StringUtils.isEmpty(privateKey.toString())){
	return addResultMapMsg(false,"解密密码失败,联系管理员");
}
//利用匹配的私钥进行解密密码
String passwd = RSAUtils.decode(userReq.getPassword(),privateKey.toString());
if(StringUtils.isEmpty(passwd)){
	return addResultMapMsg(false,"解密密码失败,联系管理员");
}
//用户信息
UUser user = userService.findByUserName(userReq.getUsername());

//账号不存在
if (user == null) {
	return addResultMapMsg(false, "账号不存在");
	//return Result.error("账号不存在");
}

if(!"1".equals(user.getUsertype())){ //不为审核端用户不能登录
	return addResultMapMsg(false, "该账号没有访问审核端的权限");
}
//密码错误

if (!userService.loginVerify(user.getUsername(), passwd)) {
	return addResultMapMsg(false, "密码不正确");
}

  以上是主要逻辑,看只是参考私钥解密方法即可,其他的不需要参考了。

3.4 总结

  虽然为采用的非对称加密,但是并不感觉多么安全。只是因为客户要求加密传输密码。
  从上边的逻辑也能看出,关键就是获取公钥,有了公钥就能与后边的登录接口对应上,这样看来,无非就是多调了一个接口而已。懂点接口调用的postmanjmeter工具都很轻松调用获取。
  最后,祝广大程序员1024节日快乐!

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茁壮成长的凌大大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值