Javaweb project login asymmetric encryption method

javaWeb project login asymmetric encryption method

  Some time ago, the project needed to transmit the ciphertext of the login password when logging in, so I thought of using RSAasymmetric encryption.

1. Flowchart

  The encryption flow chart is as follows:
insert image description here

  The logic in the project is also shown in the figure above, and the main calling process is as follows:
insert image description here
The explanation is as follows:
1) First, enter the account password on the login page.
2) After clicking Login, first call the backend to obtain the public key interface, that is, /getPublicKeythe interface. The backend will use rsathe algorithm to generate a pair of keys 公钥, 私钥, and save them in sessionthe interface, 公钥and then return them to the front end.
3) 公钥After the front-end gets it, use jsencrypt.jsthe current method to set 公钥the object, and then encrypt the password entered by the user.
4) Finally, the login interface is called, and the password passed in at this time is the encrypted password.

2. Involved in the interface

  The whole process is relatively simple. Not many steps. There are only two interfaces involved:

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

The following figure is F12the interface diagram called by the browser debugger seen at the front end:
insert image description here

3. Interface code

3.1 Get the public key interface

  The following is the first interface that needs to be called by the front end. In this interface, the background will RSAUtilsgenerate a pair of keys through the tool class below, 公钥+ 私钥, and then store these two keys in the current file session. Of course, it depends on how you design and where to store them. Either way, but I suggest trying to ensure that the pair of keys can only be used once and thrown away after use, which may be better to prevent it from being used.
  Because each client will have a unique cookievalue when it first accesses the project, and this value corresponds to the unique value in the background , so it is actually not bad sessionto save it in the current request . sessiondetails as follows:

/**
	 * 获取公钥
	 * @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));
	}

  The following is RSAUtilsthe tool class, which can be used directly.

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 Front-end encryption password method

  After the front-end gets the return from the back-end 公钥, use jsencrypt.jsthe method in it to encrypt.

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

  The value obtained after being encrypted by the above method encryptedis the encrypted password.

3.3 Call the login interface

  The login interface is nothing more than verifying the account password, but because the password is encrypted, it becomes the matching 私钥decryption password first, and then verifying the account password.

//此处获取到对应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, "密码不正确");
}

  The above is the main logic, just refer to 私钥the decryption method, and the others do not need to be referred to.

3.4 Summary

  Although asymmetric encryption is used, it doesn't feel very secure. Just because the client asked for an encrypted transmission password.
  It can also be seen from the above logic that the key is to obtain the public key. With the public key, it can correspond to the login interface behind. From this point of view, it is nothing more than an additional interface. Those who know a little about interface calls postmanand jmetertools are easy to call and obtain.
  Finally, I wish all programmers a happy 1024 holiday!

Guess you like

Origin blog.csdn.net/wohaqiyi/article/details/109264190
Recommended