RSA+AES realiza cifrado híbrido

Por qué usar el cifrado híbrido RSA + AES

1. Introducción al cifrado

  • Cifrado RSA: Pertenece al cifrado asimétrico, la clave pública se usa para cifrar datos y la clave privada se usa para descifrar datos, las dos son irreversibles. La clave pública y la clave privada se generan al mismo tiempo y se corresponden una a la otra. Por ejemplo: el cliente tiene una clave pública y el servidor tiene una clave pública y una clave privada. Después de que el cliente cifra los datos con la clave pública, envía el texto cifrado al servidor y el servidor puede descifrarlo con la clave privada y la clave pública.
  • Cifrado AES: Pertenece al cifrado simétrico, en pocas palabras, después de que el cliente cifra los datos con una contraseña, el servidor usa la misma contraseña para descifrar el texto cifrado con AES.

2. Idea de cifrado

  • Utilice RSA para cifrar y transmitir la clave AES y utilice la clave AES para cifrar datos.
  • No solo aprovecha la flexibilidad de RSA, sino que puede cambiar la clave de AES en cualquier momento; también aprovecha la alta eficiencia de AES y puede transmitir datos de manera eficiente.

3. Motivos de cifrado híbrido

  • Usar simplemente el método RSA (cifrado asimétrico) será muy ineficiente, porque aunque el método de cifrado y descifrado asimétrico es seguro, el proceso es complicado, requiere mucho tiempo y el rendimiento no es alto;
  • La ventaja de RSA es que la transmisión de datos es segura y, para unos pocos bytes de datos, el tiempo de cifrado y descifrado es básicamente insignificante, por lo que es muy adecuado para cifrar claves AES (generalmente 16 bytes);
  • El simple uso de AES (cifrado simétrico) es muy inseguro. La clave utilizada en este método es una clave fija, el cliente y el servidor son lo mismo, una vez que se obtiene la clave, la otra parte descifrará todos los datos que enviemos;
  • AES tiene una gran ventaja, es decir, la eficiencia de cifrado y descifrado es muy alta, y cuando transmitimos datos de texto, solo necesitamos este tipo de eficiencia de cifrado y descifrado, por lo que este método es adecuado para transmitir grandes cantidades de contenido de datos;

En base a las características anteriores, tenemos nuestra idea de cifrado híbrido

Diagrama de tiempo

Código de front-end:

Crear aesUtils.js

import CryptoJS from 'crypto-js'
import {JSEncrypt} from 'jsencrypt'
import cryptoJS from './CryptoJSUtils'

/**
 * 创建密钥
 * @returns AES密钥
 */
export function createAesKey () {
  const expect = 16
  let str = Math.random().toString(36).substr(2)
  while (str.length < expect) {
    str += Math.random().toString(36).substr(2)
  }
  str = str.substr(0, 16)
  return str
}
/**
 * AES加密
 * @param {*} word 加密字段
 * @param {*} keyStr AES密钥
 * @returns
 */
export function AESencrypt (word, keyStr) {
  keyStr = keyStr || 'abcdefgabcdefg12'
  var key = CryptoJS.enc.Utf8.parse(keyStr) // Latin1 w8m31+Yy/Nw6thPsMpO5fg==
  var srcs = CryptoJS.enc.Utf8.parse(word)
  var encrypted = CryptoJS.DES.encrypt(srcs, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  })
  return encrypted.ciphertext.toString()
}
/**
 * RSA加密算法
 * @param {*} pas
 * @returns
 */
export function RSAencrypt (pas, publickey) {
  let jse = new JSEncrypt()
  jse.setPublicKey(publickey)
  return jse.encrypt(pas)
}

/**
 * 获取16位随机数,当做aes秘钥key,进行加密操作
 * @constructor
 */
export function RsaEncryptData (data) {
  // 此处生成十六位随机数进行aes对称加密密钥准备
  var randomStr = Math.random().toString().substr(0, 16)
  // aes加密
  var datas = cryptoJS.encrypt(JSON.stringify(data), randomStr)
  datas = datas.toString()
  // 声明rsa加密规则
  var encrypt = new JSEncrypt()
  // 将rsa公钥进行保存
  encrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCI2zy2kkLxdhx31pu2gRB95QCx5aOvw5yTt44glEPIWhaoqXVeTch9dwAjaoInm6a1BiQHEtE/ccWTPmM7Iktrjcw3siC3dV2/QJkpk8/b52TMCw9R55qXL1+Y1f0z7BCu3ikCfyTw5cxAh5pa3r0YhYmeC+E6J3crmBPzImfYCwIDAQAB')
  // 使用公钥对aes的密钥进行加密
  var encrypted = encrypt.encrypt(randomStr)
  // 创建json对象
  let json = {
    'requestData': datas,
    'encrypted': encrypted
  }
  return json
}

/**
 * 将返回的数据进行解密操作
 * @constructor
 */
export function RsaDecryptsData (result) {
  // rsa解密,获取到aes秘钥
  var decrypt = new JSEncrypt()
  decrypt.setPrivateKey('MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALs0Ufy3++1luZf7XtsQiPXORRuv5KC6ec7pkApNc3ckFQfpWjhAiM0yn7tqGl8y1zRRR8/g8dsUCofTLOL1EJB+7xEvUCSmjB6RDowtVOxA9vRCrrxwVoNY881x94GE/Ln2A64xVtbFspq0s9hpP4GU0QXWIHKMV/SzB7DsN37PAgMBAAECgYEArS6VukkqUlANBcCR2+7MBTmxTQ/HXbmk/fmsOxuzecBzhEIoKGnrJIl0o5hglTkfRVL8MB9VHurHYyfFGqDDlJB70FVrCPBrtxPoUtB5aI0SLSkDHX3EWjlOBlCQkMiFhx9cS9PCloDSA2Ahzga3y8Bg3LaXhoZediPgz4PmBaECQQD5+mPahaPnpJcR5CKCjryXlpqic+s0cE33ZtYPwKe163KHsSdCErOsFQ9k01JpHbCZmipzRRC+xT0CZ7DfKLRjAkEAv7bOVC+HO9YM5Qf2eYW2kUv4ssG9c8NhsXBSnKQfaFEKM4xLPtulj16YQevHpjgzr2BIg5arVWW81Nu1YLlJpQJAW7cHXcx8d2fG2ZSXKMmP3houf/4BxMqTgHrlfQAVSESrT6eqnK5Z54AOltKFwPVYrvKGMqabXzLkkHZUyXuYuwJAEkhywOCPewtcy3LI9Knl0VF3dES5tpKJfIyDtGCKhj5ERMo6WtJDpbqVtqOvtJBjjXQXNkVmLYy4R2x0jbbd6QJARGMQhsPUTkac/xf956UBZNkP8Xn/rokR3M2fm+HNPZ9t0EOzdfdIYk7aUUoLqR73v9o9YiSGy5NSwOT+33MCpw==')
  var aesKey = decrypt.decrypt(result.encrypted)
  // 用aes秘钥进行解密
  return eval('(' + cryptoJS.decrypt(result.requestData, aesKey) + ')')
}

 Crear CryptoJSUtils.js

import CryptoJS from 'crypto-js'
export default {

  /**
   @param {*需要加密的字符串 注:对象转化为json字符串再加密} word
   @param {*aes加密需要的key值,这个key值后端同学会告诉你} keyStr
   */

  encrypt (word, keyStr) { // 加密
    let key = CryptoJS.enc.Utf8.parse(keyStr)
    let srcs = CryptoJS.enc.Utf8.parse(word)
    let encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}) // 加密模式为ECB,补码方式为PKCS5Padding(也就是PKCS7)
    return encrypted.toString()
  },

  decrypt (word, keyStr) { // 解密
    let key = CryptoJS.enc.Utf8.parse(keyStr)
    let decrypt = CryptoJS.AES.decrypt(word, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7})
    return CryptoJS.enc.Utf8.stringify(decrypt).toString()
  }
}

fondo java

Crear DecodeRequestBodyAdvice.java

package com.wisdom.iotSystem.exception;


import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.wisdom.iotSystem.annotations.SecurityParameter;
import com.wisdom.iotSystem.utils.AesEncryptUtils;
import com.wisdom.iotSystem.utils.RSAUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * @author wzw
 * @desc 请求数据解密
 * @date 2018/10/29 20:17
 */
@ControllerAdvice(basePackages = "com.wisdom.iotSystem.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {

    private static final Logger logger = LoggerFactory.getLogger(DecodeRequestBodyAdvice.class);

//    @Value("${server.private.key}")
    private String SERVER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIjbPLaSQvF2HHfWm7aBEH3lALHlo6/DnJO3jiCUQ8haFqipdV5NyH13ACNqgiebprUGJAcS0T9xxZM+YzsiS2uNzDeyILd1Xb9AmSmTz9vnZMwLD1HnmpcvX5jV/TPsEK7eKQJ/JPDlzECHmlrevRiFiZ4L4TondyuYE/MiZ9gLAgMBAAECgYBT5QnH5ctx1/TFpeKYs2/XrT2K0HpScfiXOSvAXwNaW5eOVyti3w3rk7qa+1zESQ+d4yDM0UVCvkze4ZzVEEXoyGV4q7HkaGhBYJeE9guWi81G4arsso8er5SvIcirAYGQykn9WvVssbsUjGe0P2Fan05RbGy9JnRpWXagyKMuqQJBAL4OgvQwoJ/djBgx5zRrJZzHySAP+Vr06ZF+6q2N+hBKG4g35Qqi7QWJxvJznlhNqqH58eWxl5Ypr0AmvjUKbl8CQQC4V0r4VBEvtorsCnilkoyiaNhITj/i3plKouKiwUf6Xvgkw2J1iqOKkZ1qlXxyUIzIeuasIIXnkJxjwN4xzt3VAkBW4X1dsYkL65QqT024+a4lAHNhs8uyl7jaKSGQmxGQNsBlQd/zP82INZZ7qPzeswpop0C8VrXMEFwrwEo9JvqTAkEAmVAwj/wLFy2wuMO0t6/8uw6L4wcBZ0RPJZ328/ngTUEzDBBcEPovLg4RaBXPnJuVmx9sPfgGpiLFjslXgwFTyQJAYzUrnA9eqmnLjTHLziCpuDuvaKa0Y/WMbieIGxnsIjXbMX1vtP3zIwr/ykrao5Ln+wv2yHwUSHeHlaxfO35xlg==";

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        try {
            boolean encode = false;
            if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                encode = serializedField.inDecode();
            }
            if (encode) {
                return new MyHttpInputMessage(inputMessage);
            }else{
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:"+e.getMessage());
            return inputMessage;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class MyHttpInputMessage implements HttpInputMessage {
        private HttpHeaders headers;

        private InputStream body;

        public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
            this.headers = inputMessage.getHeaders();
            this.body = IOUtils.toInputStream(easpString(IOUtils.toString(inputMessage.getBody(),"utf-8")));
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        /**
         *
         * @param requestData
         * @return
         */
        public String easpString(String requestData) {
            if(requestData != null && !requestData.equals("")){
                Map<String,String> map = new Gson().fromJson(requestData,new TypeToken<Map<String,String>>() {
                }.getType());
                // 密文
                String data = map.get("requestData");
                // 加密的aes秘钥
                String encrypted = map.get("encrypted");
                if(StringUtils.isEmpty(data) || StringUtils.isEmpty(encrypted)){
                    throw new RuntimeException("参数【requestData】缺失异常!");
                }else{
                    String content = null ;
                    String aseKey = null;
                    try {
                        aseKey = RSAUtils.decryptDataOnJava(encrypted,SERVER_PRIVATE_KEY);
                    }catch (Exception e){
                        throw  new RuntimeException("参数【aseKey】解析异常!");
                    }
                    try {
                        content  = AesEncryptUtils.decrypt(data, aseKey);
                    }catch (Exception e){
                        throw  new RuntimeException("参数【content】解析异常!"+e);
                    }
                    if (StringUtils.isEmpty(content) || StringUtils.isEmpty(aseKey)){
                        throw  new RuntimeException("参数【requestData】解析参数空指针异常!");
                    }
                    return content;
                }
            }
            throw new RuntimeException("参数【requestData】不合法异常!");
        }
    }
}

Crear EncodeResponseBodyAdvice.java

package com.wisdom.iotSystem.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wisdom.iotSystem.annotations.SecurityParameter;
import com.wisdom.iotSystem.utils.AesEncryptUtils;
import com.wisdom.iotSystem.utils.RSAUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @author monkey
 * @desc 返回数据加密
 * @date 2018/10/25 20:17
 */
@ControllerAdvice(basePackages = "com.wisdom.iotSystem.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {

    private final static Logger logger = LoggerFactory.getLogger(EncodeResponseBodyAdvice.class);

//    @Value("${client.public.key}")
    private String CLIENT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7NFH8t/vtZbmX+17bEIj1zkUbr+SgunnO6ZAKTXN3JBUH6Vo4QIjNMp+7ahpfMtc0UUfP4PHbFAqH0yzi9RCQfu8RL1AkpowekQ6MLVTsQPb0Qq68cFaDWPPNcfeBhPy59gOuMVbWxbKatLPYaT+BlNEF1iByjFf0swew7Dd+zwIDAQAB";

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        boolean encode = false;
        if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
            //获取注解配置的包含和去除字段
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出参是否需要加密
            encode = serializedField.outEncode();
        }
        if (encode) {
//            logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                // 生成aes秘钥
                String aseKey = getRandomString(16);
                // rsa加密
                String encrypted = RSAUtils.encryptedDataOnJava(aseKey, CLIENT_PUBLIC_KEY);
                // aes加密
                String requestData = AesEncryptUtils.encrypt(result, aseKey);
                Map<String, String> map = new HashMap<>();
                map.put("encrypted", encrypted);
                map.put("requestData", requestData);
                return map;
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }

    /**
     * 创建指定位数的随机字符串
     * @param length 表示生成字符串的长度
     * @return 字符串
     */
    public static String getRandomString(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

}

Crear interfaz: SecurityParameter

package com.wisdom.iotSystem.annotations;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;


/**
 * @author wzw
 * @desc 请求数据解密
 * @date 2018/10/25 20:17
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {

    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;
}

prueba

/**
 * RSA+AES双重加密测试
 *
 * @return object
 */
@RequestMapping("/testEncrypt")
@ResponseBody
// 数据加密注解
@SecurityParameter
public R testEncrypt(@RequestBody Map<String,Object> info) {
    String content = "内容";
    info.put("name",nameTest);
    return R.ok("请求成功").put("info",info);
}

Supongo que te gusta

Origin blog.csdn.net/wzw_wwl/article/details/129743592
Recomendado
Clasificación