RSA+AES realizes hybrid encryption

Why Use RSA + AES Hybrid Encryption

1. Introduction to Encryption

  • RSA encryption: It belongs to asymmetric encryption. The public key is used to encrypt data, and the private key is used to decrypt data. The two are irreversible. The public key and the private key are generated at the same time and correspond to each other one by one. For example: the client has a public key, and the server has a public key and a private key. After the client encrypts the data with the public key, it sends the ciphertext to the server, and the server can decrypt it with the private key and public key.
  • AES encryption: It belongs to symmetric encryption. To put it simply, after the client encrypts the data with a password, the server uses the same password to decrypt the ciphertext with AES.

2. Encryption ideas

  • Use RSA to encrypt and transmit the AES key, and use the AES key to encrypt data.
  • It not only takes advantage of the flexibility of RSA, but can change the key of AES at any time; it also takes advantage of the high efficiency of AES, which can transmit data efficiently.

3. Hybrid encryption reasons

  • Simply using the RSA (asymmetric encryption) method will be very inefficient, because although the asymmetric encryption and decryption method is safe, the process is complicated, time-consuming, and performance is not high;
  • The advantage of RSA is that data transmission is secure, and for a few bytes of data, the encryption and decryption time is basically negligible, so it is very suitable for encrypting AES keys (generally 16 bytes);
  • Simply using AES (symmetric encryption) is very insecure. The key used in this method is a fixed key, the client and the server are the same, once the key is obtained, then every piece of data we send will be cracked by the other party;
  • AES has a great advantage, that is, the encryption and decryption efficiency is very high, and when we transmit text data, we just need this kind of encryption and decryption efficiency, so this method is suitable for transmitting large amounts of data content;

Based on the above characteristics, we have our idea of hybrid encryption

Timing diagram

Front-end code:

Create aesUtils.js

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

/**
 * create key
 * @returns AES key
 */
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 encryption
 * @param {*} word encrypted field
 * @param {*} keyStr AES key
 * @returns
 */
export function AESncrypt(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 encryption algorithm
 * @param {*} pas
 * @returns
 */
export function RSAencrypt(pas, publickey) {
  let jse = new JSEncrypt()
  jse.setPublicKey(publickey)
  return jse.encrypt(pas)
}

/**
 * Obtain a 16-bit random number, use it as the aes secret key, and perform encryption operations
 * @constructor
 */
export function RsaEncryptData (data) {
  // Generate sixteen-digit random numbers here for aes symmetric encryption key preparation
  var randomStr = Math. random(). toString(). substr(0, 16)
  // aes encryption
  var datas = cryptoJS. encrypt(JSON. stringify(data), randomStr)
  datas = datas.toString()
  // Declare rsa encryption rules
  var encrypt = new JSEncrypt()
  // Save the rsa public key
  encrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCI2zy2kkLxdhx31pu2gRB95QCx5aOvw5yTt44glEPIWhaoqXVeTch9dwAjaoInm6a1BiQHEtE/ccWTPmM7Iktrjcw3siC3dV2/QJkpk8/b52TMCw9R55qXL1 + Y1f0z7BCu3ikCfyTw5cxAh5pa3r0YhYmeC + E6J3crmBPzImfYCwIDAQAB')
  // Use the public key to encrypt the aes key
  var encrypted = encrypt.encrypt(randomStr)
  // create json object
  let json = {
    'requestData': datas,
    'encrypted': encrypted
  }
  return json
}

/**
 * Decrypt the returned data
 * @constructor
 */
export function RsaDecryptsData(result) {
  // rsa decryption, get the aes key
  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)
  // Decrypt with aes key
  return eval('(' + cryptoJS. decrypt(result. requestData, aesKey) + ')')
}

Create CryptoJSUtils.js

import CryptoJS from 'crypto-js'
export default {

  /**
   @param {*string that needs to be encrypted Note: the object is converted into a json string and then encrypted} word
   @param {*The key value required for aes encryption, the back-end classmates will tell you this key value} keyStr
   */

  encrypt (word, keyStr) { // encrypt
    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}) // The encryption mode is ECB, and the complement method is PKCS5Padding (that is, PKCS7)
    return encrypted. toString()
  },

  decrypt (word, keyStr) { // decrypt
    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()
  }
}

java background

Create 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 request data decryption
 * @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)) {
                // Get the inclusion and removal fields of the annotation configuration
                SecurityParameter serializedField = methodParameter. getMethodAnnotation(SecurityParameter. class);
                //Whether the input parameter needs to be decrypted
                encode = serializedField.inDecode();
            }
            if (encode) {
                return new MyHttpInputMessage(inputMessage);
            }else{
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("There is an exception in decrypting the data returned by the method 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 & amp; & amp; !requestData.equals("")){
                Map<String,String> map = new Gson().fromJson(requestData,new TypeToken<Map<String,String>>() {
                }.getType());
                // ciphertext
                String data = map. get("requestData");
                // Encrypted aes key
                String encrypted = map. get("encrypted");
                if(StringUtils.isEmpty(data) || StringUtils.isEmpty(encrypted)){
                    throw new RuntimeException("The parameter [requestData] is missing exception!");
                }else{
                    String content = null;
                    String aseKey = null;
                    try {
                        aseKey = RSAUtils.decryptDataOnJava(encrypted,SERVER_PRIVATE_KEY);
                    }catch (Exception e){
                        throw new RuntimeException("The parsing of the parameter [aseKey] is abnormal!");
                    }
                    try {
                        content = AesEncryptUtils. decrypt(data, aseKey);
                    }catch (Exception e){
                        throw new RuntimeException("parameter [content] parsing exception!" + e);
                    }
                    if (StringUtils.isEmpty(content) || StringUtils.isEmpty(aseKey)){
                        throw new RuntimeException("parameter [requestData] parsing parameter null pointer exception!");
                    }
                    return content;
                }
            }
            throw new RuntimeException("The parameter [requestData] is invalid exception!");
        }
    }
}

Create 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 return data encryption
 * @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)) {
            // Get the inclusion and removal fields of the annotation configuration
            SecurityParameter serializedField = methodParameter. getMethodAnnotation(SecurityParameter. class);
            //Does the output parameter need to be encrypted
            encode = serializedField. outEncode();
        }
        if (encode) {
// logger.info("Encrypt the data returned by the method method: [" + methodParameter.getMethod().getName() + "]");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                // generate aes key
                String aseKey = getRandomString(16);
                // rsa encryption
                String encrypted = RSAUtils.encryptedDataOnJava(aseKey, CLIENT_PUBLIC_KEY);
                // aes encryption
                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("There is an exception in decrypting the data returned by the method method: [" + methodParameter.getMethod().getName() + "]: " + e.getMessage());
            }
        }
        return body;
    }

    /**
     * Create a random string of specified digits
     * @param length indicates the length of the generated string
     * @return string
     */
    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();
    }

}

Create interface: SecurityParameter

package com.wisdom.iotSystem.annotations;

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

import java.lang.annotation.*;


/**
 * @author wzw
 * @desc request data decryption
 * @date 2018/10/25 20:17
 */
@Target({ElementType. METHOD, ElementType. TYPE})
@Retention(RetentionPolicy. RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {

    /**
     * Whether the input parameter is decrypted, the default decryption
     */
    boolean inDecode() default true;

    /**
     * Whether the output parameter is encrypted, the default encryption
     */
    boolean outEncode() default true;
}

test

/**
 * RSA + AES double encryption test
 *
 * @return object
 */
@RequestMapping("/testEncrypt")
@ResponseBody
// data encryption annotation
@SecurityParameter
public R testEncrypt(@RequestBody Map<String,Object> info) {
    String content = "Content";
    info.put("name",nameTest);
    return R.ok("request successful").put("info", info);
}