非对称加密算法 RSA+对称AES

非对称加密算法系列文章,推荐阅读顺序:

  1. 非对称加密算法 (RSA、DSA)概述
  2. 非对称加密算法 RSA+对称AES
  3. 非对称加密算法 (DSA)

一、RSA 部分

1.1 简介

RSA是3个发明者的名字缩写, 是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。

1.2 工作流程

A 要把信息发给 B 为例,确定角色:A 为加密者,B 为解密者。首先由 B 随机确定一个 KEY,称之为私钥,将这个 KEY 始终保存在机器 B 中而不发出来;然后,由这个 KEY 计算出另一个 KEY,称之为公钥。这个公钥的特性是几乎不可能通过它自身计算出生成它的私钥。接下来通过网络把这个公钥传给 A,A 收到公钥后,利用公钥对信息加密,并把密文通过网络发送到 B,最后 B 利用已知的私钥,就能对密文进行解码了。以上就是 RSA 算法的工作流程。

1.3 攻击

2009年12月12日,编号为 RSA-768 (768 bits, 232 digits) 数也被成功分解。这一事件威胁了现通行的1024-bit 密钥的安全性,普遍认为用户应尽快升级到2048-bit或以上。

1.4 例子1

包括生成公私钥对,转换成字符串可以保存在数据库、配置文件或者配置中心。
加解密字符串是发送端使用公钥加密数据,在接收端使用私钥解密数据。数字签名的公私钥使用顺序正好相反。

package com.erbadagang.springboot.rsa;


import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @description 大一统的例子,包括生成公私钥对,加解密字符串。
 * @ClassName: RsaDemo
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/25 10:33
 * @Copyright:
 */
public class RsaDemo {
    private static final String ALGO = "RSA";
    private static final String CHARSET = "UTF-8";

    /*
     * 用于存储随机产生的公钥与私钥
     */
    private static Map<Integer, String> KEY_CACHE = new HashMap<>();

    /**
     * 随机生成密钥对
     *
     * @throws NoSuchAlgorithmException
     */
    private static void generateKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator 类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGO);
        // 初始化密钥对生成器,密钥大小为 96-1024 位
        keyPairGen.initialize(2048, new SecureRandom());
        // 生成一个密钥对,保存在 keyPair 中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
        // 得到私钥字符串
        String privateKeyString = new String(Base64.getEncoder().encode((privateKey.getEncoded())));
        // 将公钥和私钥保存到 Map
        KEY_CACHE.put(0, publicKeyString);
        KEY_CACHE.put(1, privateKeyString);
    }

    /**
     * RSA公钥加密
     *
     * @param data      加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    private static String encrypt(String data, String publicKey) throws Exception {
        // base64 编码的公钥
        byte[] decoded = Base64.getDecoder().decode(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(ALGO).generatePublic(new X509EncodedKeySpec(decoded));
        // RSA加密
        Cipher cipher = Cipher.getInstance(ALGO);
        // 公钥加密
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(CHARSET)));
    }

    /**
     * RSA私钥解密
     *
     * @param data       加密字符串
     * @param privateKey 私钥
     * @return 铭文
     * @throws Exception 解密过程中的异常信息
     */
    private static String decrypt(String data, String privateKey) throws Exception {
        byte[] inputByte = Base64.getDecoder().decode(data.getBytes(CHARSET));
        // base64 编码的私钥
        byte[] decoded = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(ALGO).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        // RSA 解密
        Cipher cipher = Cipher.getInstance(ALGO);
        // 私钥解密
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return new String(cipher.doFinal(inputByte));
    }

    public static void main(String[] args) {
        String originData = "郭秀志 Test Asymmetric encrypt!";
        System.out.println("originData = " + originData);
        try {
            generateKeyPair();

            String publicKey = KEY_CACHE.get(0);
            System.out.println("publicKey = " + publicKey);

            String encryData = encrypt(originData, publicKey);
            System.out.println("encryData = " + encryData);

            String privateKey = KEY_CACHE.get(1);
            System.out.println("privateKey = " + privateKey);
            String decryData = decrypt(encryData, privateKey);
            System.out.println("decryData = " + decryData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

originData = 郭秀志 Test Asymmetric encrypt!
publicKey = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi8DngVAbAS5vBUgJjAaTflHVKSVfRGqDV81pmxT+XAJzZ2M26J+F5U1Vx+bb0MReKTEOs2q6VLwQGEsJN2pSG7zrRwwKiJtkxQoEaeJ0f4mmFlhRnD0q2xbSjkoZaSAbWy7KWuyEaDzjspl8xBodB1kvLORBqVJ07YRy5a3/2KeEoHE8B6brNMBrLUZ5UNs4hn97iMLbuUsYfp07kRaKCCjWemqPOCijKjWfIqpbpzoEw92hr/RDrkOS4N6DMXQ+DOpW7b2JoZC/pqZ/GY0tKgLpFdEN+lUXG9SoB1sRO2CVKUdDZ/iT6FlnQG2Fej8YaYNvKBGGlBPSqW0pdJ9j0wIDAQAB
encryData = h+q0jd4Sf64eT0mdfW8k2RPYNkt5A7qVjqvmv1QN33GEmEeE51TJhnaSDdZyletaS8HVcu+PJw/V/69bJ2SymkXG7cIFcw6OA1iLn6KGvE6lOaKfST8ARZzVteaQr9CRdGijNqum1r/rLrvtUHb+mpyirC9YqIZgH1AcFIw+zZtYphw/sNDRCbIG9F4QsotLJpZmE1ukJw+lOFFEbXVmpaiYmUM3Uw3RCHMVlws/oqZgRElqhB7PUDffuk03dczfX1LL1GarSC6HGHIpsL+WzhM0TeOhxrUyl5rqblAPQ4TtbGXN5tTlvXMFIl/KoL9rv/izY3UsjMJ2RM3ZHSBB7w==
privateKey = MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCLwOeBUBsBLm8FSAmMBpN+UdUpJV9EaoNXzWmbFP5cAnNnYzbon4XlTVXH5tvQxF4pMQ6zarpUvBAYSwk3alIbvOtHDAqIm2TFCgRp4nR/iaYWWFGcPSrbFtKOShlpIBtbLspa7IRoPOOymXzEGh0HWS8s5EGpUnTthHLlrf/Yp4SgcTwHpus0wGstRnlQ2ziGf3uIwtu5Sxh+nTuRFooIKNZ6ao84KKMqNZ8iqlunOgTD3aGv9EOuQ5Lg3oMxdD4M6lbtvYmhkL+mpn8ZjS0qAukV0Q36VRcb1KgHWxE7YJUpR0Nn+JPoWWdAbYV6Pxhpg28oEYaUE9KpbSl0n2PTAgMBAAECggEAVxHNalx9JqRGWRUDlbD3LalQU/1LuHVf9VEuLYfL3YlNWymOKSpSIoWnHw9u/keJnsJItXGWO3qb0HbXfCYKl+uA7lfLLEccZkKSc2G6UUdyKdPGrL/TNoKmli4GXN+7C3lAa5uV9teQyVIlUIwwb8aZxK9FVXfhD6YIa56XmuP/c4z+uqyTdjC1YFS8TPL+mLh4qOiiYejkhhX6KLh9bHvCWTkDGTZIZjLdxoDqV5bjxWpJ0F+6Kn66PlfegrPPcjW5OhP/Z+c3rKtuEPnf51ug69AyZjYzYIqRZOezGYgjaqg8is3MDdWZOx0FsZpUC9o6dwIzsDgYFZDxydFD0QKBgQDJJkrYFluePlajtCyBjyyLkvIkZZKOE3r5jjbzUNZYG9sZWVrvAYsReQZiEw30h5yEOJOFOkmbpBW7TNABWh3N1rPLzqXX+EW9fhA2Y1+uiC+3/7pqK80L/JleJXFVyuVbfiThf0EnwcowHnKOWc8ZfcqhUSpO/lnkEasr1qkY6wKBgQCx3LhZLFc39D/DFxrHZmY02ZN9Ffn6++UVjA/IhPNYiEZl4SUd04hF8b4QQUta00rLr2JRNYlJEg32SxuC9bmf8XbX3UitLdZ5re5s/5WSvNzwf7U06USj1fhPLwfCCjubQiSwP9BNPJ/2ITuOtnQcZmZ5Wmgs15diQMCYucymuQKBgQDHUf+GIpmEtAcMTqRveZ1NbT4uXMwdpyYLliXTc34Cbw/sDYQzI9dXaBKwKmuArMSmrJ1ZvklkRfMW12WigVbZOnCNe2cRHD6XKA0Op+gPPXnznR9ux5p2z0Z2aSnmNpiR0ezf2kaJC9m7VuBzOIEkpGae9Zu0DQysF+oDFcIYIwKBgBOVafbnmvLeQecJNDmgXMCU9FhhgxTPh3nH4jUB7olg999f2uZd1DNfWr4Pcmydty6WMQ0gB+2zvzXPL0hMJhQmUh+Sjd4DngnnzMjTm3R8txcD+L/Kr3QaqyyM0R3cYpPFxKRjYlwewL4pCpW8ISy/Waki+zV0x4ZZ+trWGmKBAoGAR9t9gmX99Yck32RZd2t9wkAQ8TUfaRNtworLXti5cV6bsNPf6XDowEKC2NX9AoXjPbBaTsry37SpOe7NWskVhE8sFAwa2m4uk/PVGLxn5rvEyn6tAPnguSn+Ov6pDYv5vSUFk7R+M/bP1cCoLaqHENt3SaJjnJ9+B2/ZfT7MQLg=
decryData = 郭秀志 Test Asymmetric encrypt!

使用了java.util的Base64进行encode和decode。但是要注意:java中有自带的Base64算法类,但是安卓中却没有,之前出现的情况是,使用的Base64类不统一,比如在安卓客户端开发使用的Base64算法是使用第三方提供的jar包,而java服务端中使用的是JDK自带的Base64,导致从客户端传过来的密文,服务端解析出错。
org.apache.commons.codec.binary.Base64的高版本已经没有对应的方法。

1.5 例子2(文件存储公私钥)

package com.erbadagang.springboot.rsa;

import org.apache.commons.codec.binary.Base64;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * 生成公私钥对并存储到本地文件。
 *
 * @ClassName: RsaDemoWithFile
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/25 10:58
 * @Copyright:
 */
public class RsaDemoWithFile {
    /**
     * 生成公钥和私钥, 一般一次性生成, 存储在文件中进行分发和使用
     */
    public static void generateKey() {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(1024);  //一般来说, 长度为2048是最好的, 也是推荐的
            KeyPair kp = kpg.genKeyPair();
            PublicKey pbkey = kp.getPublic();
            PrivateKey prkey = kp.getPrivate();
            // 保存公钥
            FileOutputStream f1 = new FileOutputStream("d:/pubkey.dat");
            ObjectOutputStream b1 = new ObjectOutputStream(f1);
            b1.writeObject(pbkey);
            // 保存私钥
            FileOutputStream f2 = new FileOutputStream("d:/privatekey.dat");
            ObjectOutputStream b2 = new ObjectOutputStream(f2);
            b2.writeObject(prkey);
        } catch (Exception e) {
        }
    }

    /**
     * 公钥加密, 一般调用者传递明文, 从本地存储读取公钥进行加密
     *
     * @param plainTxt
     * @return
     * @throws Exception
     */
    public static String pubEncrypt(String plainTxt) throws Exception {
        String s = Base64.encodeBase64String(plainTxt.getBytes("UTF-8"));
        // 获取公钥及参数e,n
        FileInputStream f = new FileInputStream("d:/pubkey.dat");
        ObjectInputStream b = new ObjectInputStream(f);
        RSAPublicKey pbk = (RSAPublicKey) b.readObject();
        BigInteger e = pbk.getPublicExponent();
        BigInteger n = pbk.getModulus();
        // 获取明文m
        byte ptext[] = s.getBytes("UTF-8");
        BigInteger m = new BigInteger(ptext);
        // 计算密文c
        BigInteger c = m.modPow(e, n);
        // 保存密文
        String ciperTxt = c.toString();
        return ciperTxt;
    }

    /**
     * 私钥解密, 一般调用者传递密文, 从本地存储读取私钥进行解密
     *
     * @param ciperTxt
     * @return
     * @throws Exception
     */
    public static String privDecrypt(String ciperTxt) throws Exception {
        BigInteger c = new BigInteger(ciperTxt);
        // 读取私钥
        FileInputStream f = new FileInputStream("d:/privatekey.dat");
        ObjectInputStream b = new ObjectInputStream(f);
        RSAPrivateKey prk = (RSAPrivateKey) b.readObject();
        BigInteger d = prk.getPrivateExponent();
        // 获取私钥参数及解密
        BigInteger n = prk.getModulus();
        BigInteger m = c.modPow(d, n);
        // 显示解密结果
        byte[] mt = m.toByteArray();
        String plainTxt = new String(Base64.decodeBase64(mt), "UTF-8");
        return plainTxt;
    }

    public static void main(String args[]) {
        try {
            generateKey();
            String ciperTxt = pubEncrypt("郭秀志 Test Asymmetric encrypt!");
            System.out.println("公钥加密密文:" + ciperTxt);
            System.out.println("私钥解密:" + privDecrypt(ciperTxt));
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }
}

运行结果:

公钥加密密文:6099844164314059443431591651751615722055254322730619374416036868657674637290798963105567557375836230466843160570341925833251877311218292252966007503499880207100523576112983947344123430250478502342666667129135836235200008011709395758839438577626394237727966426413570214930347318988576727557310041590164979452
私钥解密:郭秀志 Test Asymmetric encrypt!

1.6 例子3(使用controller加解密)

1.6.1 生成公私钥对的工具类RsaGenerator

package com.erbadagang.springboot.rsa.util;

import lombok.Getter;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * @description RSA公私钥对生成工具, 加解密处理注意:密钥长度2048
 * @ClassName: RsaGenerator
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/25 11:16
 * @Copyright:
 */
@Getter
public class RsaGenerator {

    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 获取公钥的key
     */
    private RSAPublicKey publicKey;

    /**
     * 获取私钥的key
     */
    private RSAPrivateKey privateKey;

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     *
     * @return
     * @throws Exception
     */
    public void generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(2048);
        KeyPair keyPair = keyPairGen.generateKeyPair();

        this.publicKey = (RSAPublicKey) keyPair.getPublic();
        this.privateKey = (RSAPrivateKey) keyPair.getPrivate();
    }

}

1.6.2 获取公私钥对的Controller方法getRsaKey()

package com.erbadagang.springboot.rsa.controller;

import com.erbadagang.springboot.rsa.util.RsaGenerator;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

/**
 * RsaController作用是:通过controller来生成公私钥对,加密及解密。
 *
 * @ClassName: RsaController
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/7/25 11:09
 * @Copyright:
 */
@RestController
@RequestMapping("rsa")
public class RsaController {

    /**
     * 获取RSA加密形式的publicKey和privateKey
     */
    @GetMapping("/genKey")
    public Map<String, String> getRsaKey() throws NoSuchAlgorithmException {
        RsaGenerator rsaGenerator = new RsaGenerator();
        rsaGenerator.generateKeyPair();
        Map<String, String> map = new HashMap<>(4);
        map.put("privateKey", Base64.encodeBase64String(rsaGenerator.getPrivateKey().getEncoded()));
        map.put("publicKey", Base64.encodeBase64String(rsaGenerator.getPublicKey().getEncoded()));
        return map;
    }
}

访问上面URLhttp://localhost:8080/rsa/genKey运行返回结果:

{
  "privateKey": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDKr3Zhb/aM7DHnhT0PN2Fb7hnQxCfjwkurtROSVNZhDvaxAiNhuC/wUIUDd9tAGQSgIgEag6ISaFMcRIMTADpN6QJyiUcVy2M4LmRO55VotmMB9g6OUBmsBju3TL58Iloy05j1x/T0PG9N0KegKDbIwG8/FQsKXm5kfDOV5piPPReveiykfv6kh8ZqCbSBtIVpgdPDD5r13Cj4+Q0xGabznGXv5gC0xhKyZr3ayGPGcUvB2eW3FAFn6qOLMKvbawnhDZPkiNdGU+yD6Uz27R5Fyy8s1wGK3Uk+yPA23mxVXsYu19a+8JINRV1acmYGGXsYJmAi445kNsgFVsO28TLdAgMBAAECggEAC6plqd4D1sCRbr3gccvCMsRVgAqKMTWxnURix/1SCWwPDskMuEcdmztHLJftapcGCSFr5tbEsUKH5gybbrCIqotKtMTp7nsyTr180H3Lv6cfs7ExzUcW8yu4rCginoprnplHKH5FvvjrfxMPUsx9urg4ruzLIeGlgOsVHP+UsEm9oqGSA1O7fzWQfJszuLRSVAu8GFYYzpGMm5FMVfgPHpZZUrZRcnJnMwQVNzwNh121zDmbx7Win/LKAD4C6s6g/KDKs7L1QYhiEBvmI0AkCcaLtdl3YhiZfNSbxRnJ364nnLFZSPvyvLl0zxFyPWIX61jYKSqu6uM3nogT5JnvgQKBgQDrxhDKwLCYmOd2QZ5EPK9/d+y1SOgwKxW243SAZobRIVTUkJW3NzJe5lxisbMEDsxRyUKHm0aL49bNkQeklBmnMAmtSG5MMgTz/fqRjN7o9+bWukdOwFVRNfDJ/dbHKCDr+dwCiHDyu/qkcCfjIBUR+mS1z2x3LWUIxwb0MQkuVQKBgQDcErrH5zeJ86obWDhhEstD0JgYf/pGXpGQadEcuOall0Uhc/hRlTSTH7QC+k3J5zQ8WdG78vxEAAuPMC7JU7Nm0GKw0xmp/nq0nwvmF+psAw+uNNtbIn1o3uUjqdt27Sv7rS512mKX8S1Mj7y2jtn45lLv9VeYDsSL0O2k2wZqaQKBgQChYhq+XbTDTu4oQPQPKybJbpIE6Jmd1u/vFrP467TeUx1YvnrsRQjicnXMTGwHnAV4+fTjE4LvYA34+YusuH7ytGv7Q3fUCezgAfnQRQeTmZRVaH5Exlvf0bc229x2x935CDbzOOdvDwKaKfbzfVNO0gC7ffZ1gQoGPw1gemwZXQKBgQDEED+tpxX45kevsuoPueGzmhxW/3VmygvfYBa4AxchgeJKCnq5nDdJt931JTC2ZzBHcDIFw1Xx8yRZPjEAlnxnZdH2/SuJIroJPwUnyjjEX/nRVy/yQoj+LE5ydnqaunQL9d9Fifl6qpiT9B7Jef1B3VkYhTiztLxwYAPIcoWFuQKBgFulQsCQYESMeT59Vf9cSUTlVs/Hen2TsKeHhaqhx3SyRsHLvDnTz5JxL/5GZS8dCb+VZdCxcMYtfaFfY2wzVBDRj7wOFLf6RuQ5mSwHpapRv2XV4pImVRAjktFQsHAyN7fflhpVy/cGWw7tovJPjmIDNGIq8hdiIdo6CfepGoTB",
  "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyq92YW/2jOwx54U9DzdhW+4Z0MQn48JLq7UTklTWYQ72sQIjYbgv8FCFA3fbQBkEoCIBGoOiEmhTHESDEwA6TekCcolHFctjOC5kTueVaLZjAfYOjlAZrAY7t0y+fCJaMtOY9cf09DxvTdCnoCg2yMBvPxULCl5uZHwzleaYjz0Xr3ospH7+pIfGagm0gbSFaYHTww+a9dwo+PkNMRmm85xl7+YAtMYSsma92shjxnFLwdnltxQBZ+qjizCr22sJ4Q2T5IjXRlPsg+lM9u0eRcsvLNcBit1JPsjwNt5sVV7GLtfWvvCSDUVdWnJmBhl7GCZgIuOOZDbIBVbDtvEy3QIDAQAB"
}

二、RSA加密算法+AES加密算法

2.1 AES概念

高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES。

2.2 数据传送的两个模式

2.2.1 客户端传输重要信息给服务端,服务端返回的信息不需加密的情况

客户端传输重要信息给服务端,服务端返回的信息不需加密,例如绑定银行卡的时候,需要传递用户的银行卡号,手机号等重要信息,客户端这边就需要对这些重要信息进行加密,使用RSA公钥加密,服务端使用RSA解密,然后返回一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败。这种场景下,仅仅使用RSA加密是可以的。

2.2.2 客户端传输重要信息给服务端,服务端返回的信息需加密的情况

客户端传输重要信息给服务端,服务端返回的信息需加密,例如客户端登录的时候,传递用户名和密码等资料,需要进行加密,服务端验证登录信息后,返回令牌token需要进行加密,客户端解密后保存。此时就需要结合这两种算法了。

客户端使用公钥加密,服务端使用私钥解密的过程。也许你会这么想,既然可以如此,那服务端那边信息也可以通过RSA加密后,传递加密信息过来,客户端进行解密。但是,这样做,显示是不安全的。原因是,由于客户端并没有保存私钥,只有公钥,只可以服务端进行私钥加密,客户端进行公钥解密,但由于公钥是公开,别人也可以获取到公钥,如果信息被他们截取,他们同样可以通过公钥进行解密,那么这样子加密,就毫无意义了,所以这个时候,就要结合对称算法,实现客户端与服务端之前的安全通信了。另外一个重要原因是上篇文章提到的:“作为加密使用的 RSA 有着随密钥长度增加,性能急剧下降的问题”,所以使用AES加密业务数据,仅仅使用RSA来加解密AES的对称密钥。

2.3 AES加解密过程

于AES属于对称算法,加密和解密需要使用同一把密钥,所以,服务端要解密传递过来的内容,就需要密钥 + 密文。
客户端使用AES进行加密,服务端要进行解密的话,需要用到产生的密钥,那密钥必须从客户端传输到服务端,如果不对密钥进行加密,那加密就没有意义了。所以这里终于谈到了重点,RSA算法+AES算法结合使用。

2.3.1 客户端使用RSA + AES对重要信息进行加密步骤

1.客户端随机产生AES的密钥;
2.对重要信息进行AES加密;
3.通过使用服务端RSA公钥对AES密钥进行加密。
这样在传输的过程中,即时加密后的AES密钥被别人截取,对其也无济于事,因为他并不知道服务端RSA的私钥,无法解密得到原本的AES密钥,就无法解密用AES加密后的重要信息。

2.3.2 服务端使用RSA + AES对重要信息进行解密步骤

1.对加密后的AES密钥进行服务端RSA私钥解密,拿到密钥原文;
2.对加密后的重要信息进行AES解密,拿到原始内容。

现实开发中,服务端有时也需要向客户端传递重要信息,比如登录的时候,返回token给客户端,作为令牌,这个令牌就需要进行加密,原理也是差不多的,比上面多一个步骤而已,就是将解密后的AES密钥,对将要传递给客户端的数据token进行AES加密,返回给客户端,由于客户端和服务端都已经拿到同一把AES钥匙,所以客户端可以解密服务端返回的加密后的数据。如果客户端想要将令牌进行保存,则需要使用自己定义的默认的AES密钥进行加密后保存,需要使用的时候传入默认密钥和密文,解密后得到原token。

2.4 代码:


    /**
     * 发送端逻辑:
     * 1.客户端随机产生AES的密钥;
     * 2.对重要信息进行AES加密;
     * 3.通过使用服务端RSA公钥对AES密钥进行加密。
     * 接收端逻辑:
     * 1.对加密后的AES密钥进行服务端RSA私钥解密,拿到密钥原文;
     * 2.对加密后的重要信息进行AES解密,拿到原始内容。
     */
    @GetMapping("/encryptDecryptSign")
    public void encryptDecryptSign() throws Exception {
        //发送方公私钥
        String sendPrivateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDKr3Zhb/aM7DHnhT0PN2Fb7hnQxCfjwkurtROSVNZhDvaxAiNhuC/wUIUDd9tAGQSgIgEag6ISaFMcRIMTADpN6QJyiUcVy2M4LmRO55VotmMB9g6OUBmsBju3TL58Iloy05j1x/T0PG9N0KegKDbIwG8/FQsKXm5kfDOV5piPPReveiykfv6kh8ZqCbSBtIVpgdPDD5r13Cj4+Q0xGabznGXv5gC0xhKyZr3ayGPGcUvB2eW3FAFn6qOLMKvbawnhDZPkiNdGU+yD6Uz27R5Fyy8s1wGK3Uk+yPA23mxVXsYu19a+8JINRV1acmYGGXsYJmAi445kNsgFVsO28TLdAgMBAAECggEAC6plqd4D1sCRbr3gccvCMsRVgAqKMTWxnURix/1SCWwPDskMuEcdmztHLJftapcGCSFr5tbEsUKH5gybbrCIqotKtMTp7nsyTr180H3Lv6cfs7ExzUcW8yu4rCginoprnplHKH5FvvjrfxMPUsx9urg4ruzLIeGlgOsVHP+UsEm9oqGSA1O7fzWQfJszuLRSVAu8GFYYzpGMm5FMVfgPHpZZUrZRcnJnMwQVNzwNh121zDmbx7Win/LKAD4C6s6g/KDKs7L1QYhiEBvmI0AkCcaLtdl3YhiZfNSbxRnJ364nnLFZSPvyvLl0zxFyPWIX61jYKSqu6uM3nogT5JnvgQKBgQDrxhDKwLCYmOd2QZ5EPK9/d+y1SOgwKxW243SAZobRIVTUkJW3NzJe5lxisbMEDsxRyUKHm0aL49bNkQeklBmnMAmtSG5MMgTz/fqRjN7o9+bWukdOwFVRNfDJ/dbHKCDr+dwCiHDyu/qkcCfjIBUR+mS1z2x3LWUIxwb0MQkuVQKBgQDcErrH5zeJ86obWDhhEstD0JgYf/pGXpGQadEcuOall0Uhc/hRlTSTH7QC+k3J5zQ8WdG78vxEAAuPMC7JU7Nm0GKw0xmp/nq0nwvmF+psAw+uNNtbIn1o3uUjqdt27Sv7rS512mKX8S1Mj7y2jtn45lLv9VeYDsSL0O2k2wZqaQKBgQChYhq+XbTDTu4oQPQPKybJbpIE6Jmd1u/vFrP467TeUx1YvnrsRQjicnXMTGwHnAV4+fTjE4LvYA34+YusuH7ytGv7Q3fUCezgAfnQRQeTmZRVaH5Exlvf0bc229x2x935CDbzOOdvDwKaKfbzfVNO0gC7ffZ1gQoGPw1gemwZXQKBgQDEED+tpxX45kevsuoPueGzmhxW/3VmygvfYBa4AxchgeJKCnq5nDdJt931JTC2ZzBHcDIFw1Xx8yRZPjEAlnxnZdH2/SuJIroJPwUnyjjEX/nRVy/yQoj+LE5ydnqaunQL9d9Fifl6qpiT9B7Jef1B3VkYhTiztLxwYAPIcoWFuQKBgFulQsCQYESMeT59Vf9cSUTlVs/Hen2TsKeHhaqhx3SyRsHLvDnTz5JxL/5GZS8dCb+VZdCxcMYtfaFfY2wzVBDRj7wOFLf6RuQ5mSwHpapRv2XV4pImVRAjktFQsHAyN7fflhpVy/cGWw7tovJPjmIDNGIq8hdiIdo6CfepGoTB";
        String sendPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyq92YW/2jOwx54U9DzdhW+4Z0MQn48JLq7UTklTWYQ72sQIjYbgv8FCFA3fbQBkEoCIBGoOiEmhTHESDEwA6TekCcolHFctjOC5kTueVaLZjAfYOjlAZrAY7t0y+fCJaMtOY9cf09DxvTdCnoCg2yMBvPxULCl5uZHwzleaYjz0Xr3ospH7+pIfGagm0gbSFaYHTww+a9dwo+PkNMRmm85xl7+YAtMYSsma92shjxnFLwdnltxQBZ+qjizCr22sJ4Q2T5IjXRlPsg+lM9u0eRcsvLNcBit1JPsjwNt5sVV7GLtfWvvCSDUVdWnJmBhl7GCZgIuOOZDbIBVbDtvEy3QIDAQAB";

        //接收方公私钥
        String recPrivateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCP3no8bYUMsdW1sSsIaUxaWDxxOBlDYNCuoVn7BlyptCnIOkqu86dVCB0dRxG90KlZoCtXhfYQgZkIkzUYdAeQsIHJr2PZn+z8XuqQllqDXDbBi0VXIx0JS8pjNP9pe/3iyLOWK7UPZkew1+OaomKnjpjFAYUxBlakFloP02mmaOfCHxkHzLWQRw/uBD7WCmCtKP5Mism78s5QuZxyfbMopECloVU8YzYc6tm5nygPOvudIxZtCJDNRTmgXtDra/WnvWE/+hVBjvCO1uPxLI5krPwdlYuBzlrylmuCoVZr9K5mJcMLt4SH2ApWLx/hyWJbyB1FZlhM4DzGIw2oRnnVAgMBAAECggEAfmAPP8V0ehI8h71474qPZ0zayxlcF7OTm9JgGAEepHN9wER0Ffoxop/d8znae8IvAGuRpvAllZpBsyacHT7O5moll+RY8XFp2sYFhbyNBZabAqgz4LcXanMI9Nw4/4/LFMr39ZGvGjfeAZmidNLvlf/MckFDnizTLo/zzLMIuwNW7ZNAYCokNa+/MwmTly6d1fuoazYNvv+4u7GVkEU9pNDwqeQP0JRY1O5uN+7RLfH7IZ3ObuKIvoJOIZCzEgSqA0DTvqgRkNHm3L5oRkVXyv85rO55n3EFwZ6boe1KaHRGX0WHTpkGWcto9Sz3tmKIexBCeveV+XjwRya5RJrcAQKBgQDdikiTSYmXMREdQfab4+BXrK4Wv5c0l+DdfGczdn4/wl5TMV7nEXh9zOSgET1Wxt6yULFQalofHQzZym5VENsEe2kjSEWf39m4Y+wRHjhsG89wSDEGL+dYVsS74Np0doREVzOssXSltfV2iOAwBgublMQJ41hLcC7ypMZugtKz+QKBgQCmP1WtgFB5uugEKaqQul9N/17g1PZ1vbIRAoYhinbsBrlhc+H6YonFXPECIcc84Hds7dhxkcLUlUawybTo/JzpvQBTB823TH1U/VuA+uE3ajQv2lZC8hYWu4hpwndsdqePrmmXVJFT2aH12E9y5yaoVesvFPQ350x/NVLxfQUzvQKBgQCsW+XTEaeGhZo3FRb0efoUvDhFYpIVTQSZzSvNkibvHB2exA59383Ksho9nqwGU3r3aGhLlDLBeiyBVUk5zX9YoVtPI+9nTxVoq/UB7G0hTxG43bGmiqaGyBsPwQS1D3Aga2e8t+N0+Xgb3KnvMwTc6oUK3GHZb1JXXXM0j3u2oQKBgCHeDy88T6is2e1XK6c2QIocNxDocZkE3wy2DesxUQ6+Q+/FcsjWYCizyWlcxkDxnYK0ZX6laiJykqcbQF6ib7jyRumjUlZAH9w7jPOWqGDoot8IxL/4n2VcKOsasceH2JTdvCcXFFAXqvXxbiYDTw3GCxZZV3M4DI5xp4cIqBGlAoGAUHdGKaCauzdUrXB8wV9symBQvrEbmAXlHI713CpDPBHjYZ+STvYbMncSxI2IXELZkE0UHYQYqDgc+h+1n+r5lgi+ljBcFAiZBlU4cdrdzkEvJ6/OMqyAlko5+0hOr1tgkuPU1jQe33JuOaKvzqtGjmV8hkxh2wH0FSGBfLLd4IE=";
        String recPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj956PG2FDLHVtbErCGlMWlg8cTgZQ2DQrqFZ+wZcqbQpyDpKrvOnVQgdHUcRvdCpWaArV4X2EIGZCJM1GHQHkLCBya9j2Z/s/F7qkJZag1w2wYtFVyMdCUvKYzT/aXv94sizliu1D2ZHsNfjmqJip46YxQGFMQZWpBZaD9Nppmjnwh8ZB8y1kEcP7gQ+1gpgrSj+TIrJu/LOULmccn2zKKRApaFVPGM2HOrZuZ8oDzr7nSMWbQiQzUU5oF7Q62v1p71hP/oVQY7wjtbj8SyOZKz8HZWLgc5a8pZrgqFWa/SuZiXDC7eEh9gKVi8f4cliW8gdRWZYTOA8xiMNqEZ51QIDAQAB";

//        String AesKey =RandomStringUtils.randomAlphanumeric(16);

        //输入的明文参数
        String inputJson = "{\"name\": \"guo\",\"age\": 40}";

        /**
         * 发送端业务开始
         */
        byte[] aesKey = AesUtils.generateAesKey(128);
        // AES 对称加密业务数据
        String encryptData = AesUtils.encrypt(inputJson, aesKey);//注意key为16位
        System.out.println("encryptData = " + encryptData);
        // RSA 私钥签名数据
        String signature = RsaUtils.sign(inputJson, sendPrivateKey);
        // RSA 公钥加密AES密钥传送给接收方
        String encryptAesKey = RsaUtils.encrypt(aesKey, recPublicKey);

        /**
         * 接收端业务开始
         */
        byte[] decryptedData;
        boolean signChecked;
        // 使用"接收端RSA私钥"进行【非对称解密操作】发送端的“AES对称加密Key”
        byte[] encryptKeyByte = RsaUtils.decrypt(encryptAesKey, recPrivateKey);
        System.out.println("encryptAesKey = " + Base64.encodeBase64String(encryptKeyByte));
        // 使用对称加密AES Key对发送端对称加密后的业务数据内容,进行【对称解密操作】,获取解密后的数据内容。
        decryptedData = AesUtils.decrypt(encryptData, encryptKeyByte);
        System.out.println("decryptedData解密后的业务数据 = " + new String(decryptedData));
        // 使用“发送端RSA公钥”对发送端使用“RSA私钥进”行非对称加密签名后的数据进行【非对称解密验签操作】,判断数据完整性。
        signChecked = RsaUtils.checkSign(decryptedData, signature, sendPublicKey);
        System.out.println("signChecked = " + signChecked);
    }

2.5 测试

访问URLhttp://localhost:8080/rsa/encryptDecryptSign
输出日志:

encryptData = gB6NMdYoL/CiKVuxPx7+b39KE4BEowmALf3Z98P8yas=
encryptAesKey = CdgT8AViaDMl9R000drYmg==
decryptedData解密后的业务数据 = {"name": "guo","age": 40}
signChecked = true

底线


本文源代码使用 Apache License 2.0开源许可协议,这里是本文源码Gitee地址,可通过命令git clone+地址下载代码到本地,也可直接点击链接通过浏览器方式查看源代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350