Método de cifrado asimétrico de inicio de sesión del proyecto Javaweb

Método de cifrado asimétrico de inicio de sesión del proyecto JavaWeb

  Hace algún tiempo, el proyecto necesitaba transmitir el texto cifrado de la contraseña de inicio de sesión al iniciar sesión, así que pensé en usar el RSAcifrado asimétrico.

1. Diagrama de flujo

  El diagrama de flujo de cifrado es el siguiente:
inserte la descripción de la imagen aquí

  La lógica del proyecto también se muestra en la figura anterior, y el proceso de llamada principal es el siguiente:
inserte la descripción de la imagen aquí
La explicación es la siguiente:
1) Primero, ingrese la contraseña de la cuenta en la página de inicio de sesión.
2) Después de hacer clic en Iniciar sesión, primero llame al backend para obtener la interfaz de clave pública, es decir, /getPublicKeyla interfaz. El backend usará rsael algoritmo para generar un par de claves 公钥, 私钥guardarlas en sessionla interfaz 公钥y luego devolverlas a la Interfaz.
3) 公钥Después de que el front-end lo obtenga, use jsencrypt.jsel método actual para configurar 公钥el objeto y luego cifre la contraseña ingresada por el usuario.
4) Finalmente, se llama a la interfaz de inicio de sesión y la contraseña pasada en este momento es la contraseña cifrada.

2. Involucrado en la interfaz

  Todo el proceso es relativamente simple. No muchos pasos. Solo hay dos interfaces involucradas:

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

La siguiente figura es F12el diagrama de interfaz llamado por el depurador del navegador que se ve en la parte delantera:
inserte la descripción de la imagen aquí

3. Código de interfaz

3.1 Obtener la interfaz de clave pública

  La siguiente es la primera interfaz que debe llamar el front-end. En esta interfaz, el fondo generará RSAUtilsun par de claves a través de la clase de herramienta a continuación, 公钥+ 私钥, y luego almacenará estas dos claves en el archivo actual session. Por supuesto, depende de cómo las diseñes y dónde las guardes De cualquier manera, pero sugiero tratar de asegurarse de que el par de llaves solo se pueda usar una vez y tirar después del uso, lo que puede ser mejor para evitar que se use.
  Debido a que cada cliente tendrá un cookievalor único cuando acceda por primera vez al proyecto, y este valor corresponde al valor único en segundo plano , por lo que en realidad no está mal sessionguardarlo en la solicitud actual . sessiondetalles de la siguiente manera:

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

  La siguiente es RSAUtilsla clase de herramienta, que se puede utilizar directamente.

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 Método de contraseña de cifrado front-end

  Después de que el front-end obtenga el retorno del back-end 公钥, use jsencrypt.jsel método que contiene para cifrar.

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

  El valor obtenido después de ser encriptado por el método anterior encryptedes la contraseña encriptada.

3.3 Llamar a la interfaz de inicio de sesión

  La interfaz de inicio de sesión no es más que verificar la contraseña de la cuenta, pero debido a que la contraseña está cifrada, primero se convierte en la 私钥contraseña de descifrado coincidente y luego verifica la contraseña de la cuenta.

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

  Lo anterior es la lógica principal, solo consulte 私钥el método de descifrado, y no es necesario hacer referencia a los demás.

3.4 Resumen

  Aunque se usa el cifrado asimétrico, no se siente muy seguro. Solo porque el cliente solicitó una contraseña de transmisión encriptada.
  También se puede ver de la lógica anterior que la clave es para obtener la clave pública. Con la clave pública, puede corresponder a la interfaz de inicio de sesión detrás. Desde este punto de vista, no es más que una interfaz adicional. Aquellos que saben un poco sobre llamadas postmany jmeterherramientas de interfaz son fáciles de llamar y obtener.
  ¡Finalmente, les deseo a todos los programadores unas felices vacaciones 1024!

Supongo que te gusta

Origin blog.csdn.net/wohaqiyi/article/details/109264190
Recomendado
Clasificación