分享

前后端API交互数据加密——AES与RSA混合加密完整实例

 hncdman 2022-05-13

  前言

   前段时间看到一篇文章讲如何保证API调用时数据的安全性(传送门:https://blog.csdn.net/ityouknow/article/details/80603617),文中讲到利用RSA来加密传输AES的秘钥,用AES来加密数据,并提供如下思路:

  说人话就是前、后端各自生成自己的RSA秘钥对(公钥、私钥),然后交换公钥(后端给前端的是正常的明文公钥,前端给后端的是用后端公钥加密后的密文公钥;PS:其实我觉得直接交换两个明文公钥就行了),后端生成AES的明文key,用明文key进行AES加密得到密文数据,用前端的公钥进行RSA加密得到密文key,API交互时并将密文数据与密文key进行传输,前端用自己的私钥进行RAS解密的到明文key,用明文key进行AES解密得到明文数据;前端给后端发送数据时同理,这样一来,传输的数据都是密文,且只有秘钥才能解密

  可惜这篇博客只提供了思路,但并没有具体的代码,我们在网上查找一下资料,开始生撸代码,实现一个前后端API交互数据加密——AES与RSA混合加密,并应用到项目中

  后端加、解密

  从网上查找工具类,再进行改造

  先引入Base64工具类

        <!-- Base64编码需要  -->
        <dependency>
            <groupId>org.apache.directory.studio</groupId>
            <artifactId>org.apache.commons.codec</artifactId>
            <version>1.8</version>
        </dependency>

  AES

 AesUtil

  RSA

 RsaUtil

  简单测试

  AES对称加密、解密简单测试

  1、字符串

复制代码

   public static void main(String[] args) {        //16位
        String key = "MIGfMA0GCSqGSIb3";        //字符串
        String str = "huanzi.qch@qq.com:欢子";        try {            //加密
            String encrypt = AesUtil.encrypt(str, key);            //解密
            String decrypt = AesUtil.decrypt(encrypt, key);

            System.out.println("加密前:" + str);
            System.out.println("加密后:" + encrypt);
            System.out.println("解密后:" + decrypt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

复制代码

加密前:huanzi.qch@qq.com:欢子
加密后:dXPRtcdHPQSTwxLnmixkaSvNfGHhg5Gz8sGTtiqCpPo=解密后:huanzi.qch@qq.com:欢子

  2、复杂对象

复制代码

      
        String key = "MIGfMA0GCSqGSIb3"
        ImsUserVo userVo = "123456""111111"
            String encrypt =
            String decrypt ="加密前:" +"加密后:" +"解密后:" +

复制代码

加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
加密后:AXv8ewfY+gbuZ/dCmGAxngLry+Idlp1NKZ8yyf9+bmrBggUBo3b+e4XRwMAE/DP+vFS2HpgeYQTrZM1ECjo01uvZ/T6lY7b2C6L8PTotYHQyJM3kOs+YNXL/uyvFZ2EICSQWhmM1XX+g0juHLCbgQDMNXc56S/7eH2p+su1+CTMygUBCF0U/gZaSzqylqujTb3sg7q4xMuxCQ6ne6xmL3ebjanOLeMJHypTDy1rlJTw=解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)

  RAS非对称加密、解密简单测试

  1、字符串的RSA公钥加密、私钥解密

复制代码

    public static void main(String[] args) {        //字符串
        String str = "huanzi.qch@qq.com:欢子";        try {
            System.out.println("私钥:" + RsaUtil.getPrivateKey());
            System.out.println("公钥:" + RsaUtil.getPublicKey());            //公钥加密
            byte[] ciphertext = RsaUtil.encryptByPublicKey(str.getBytes(), RsaUtil.getPublicKey());            //私钥解密
            byte[] plaintext = RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());

            System.out.println("公钥加密前:" + str);
            System.out.println("公钥加密后:" + Base64.encodeBase64String(ciphertext));
            System.out.println("私钥解密后:" + new String(plaintext));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

复制代码

私钥:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANG08b2L0Hk1QCJXyTUI6A4CqW+KENCedZyJCYMteZ/vx93KeYZbPShhI3IWJJtj9U+ibiAVRjzmikI9lkKdgnCaOgTmEZis2RWgLzhcOpSqdp/J6d+YtmCD6UDeO3E6QPyfVv9d3qPrqaYUCxi7CmouzVaa/cJqrfYB7qGYt3u5AgMBAAECgYBbovQX3ebFcG2MFExKLpAovyUHJo/eeb/vHTrY5aBGMWNnGbks6uW4pWn1ypNIi8+AcvwobON6bUtxUrQ8e9OpUlDYTAAqDE8JvJoRC3theHpJbkHCdDLeNnz1EizUwxfe3X3IVwEYd29C00WXt0rUW2D/Fsa7ECp08taeV+ukAQJBAOyfO8opGp8t40bbyMRVsIR2zK19rN6Kd/NGvjjW/7BPgzDJZsybcN6e0AuhFaWTyHNSonpDztEQ0VWhF0mHokECQQDi4W9xCmzQf0l8mgXUP2IDY5YtQN9g9vL51qEwpcxhHxCCcid62R0y6T2GnRTmkEpSwPYZ2EZQrKtpGiEk4wt5AkAhwwqd6sWApuSB7MQ1t2BLVkQYERGEY0+AJ7zmkU7EUmQOpv4C/b7aFODsd9yF1pNIWScTuO8eh37G8AhJlo/BAkEAuIHfME3rGlA5whQ8I1T8b4cgjWLRhrit9tI+OiLLqDwsH/mX88b3gPy/pWa/pZW4a74zJeeFn3wc1heC1s2x+QJAXHVf9fZaFwDlD6nD3x0Sgu8Mdp8tsfdz2wIkvjtANc+eojkfxwdZd6PKWgmiPTLKNNqbPaLgtU74WVAnlpSgsw==公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRtPG9i9B5NUAiV8k1COgOAqlvihDQnnWciQmDLXmf78fdynmGWz0oYSNyFiSbY/VPom4gFUY85opCPZZCnYJwmjoE5hGYrNkVoC84XDqUqnafyenfmLZgg+lA3jtxOkD8n1b/Xd6j66mmFAsYuwpqLs1Wmv3Caq32Ae6hmLd7uQIDAQAB
公钥加密前:huanzi.qch@qq.com:欢子
公钥加密后:MQa65DyVZg/L8SBilLX1yUiajtiTBqUFpQ/qlrSRyMGCubylbp9KisowRghPxk9BuI3+ea/4QpidIZKJaZAbQQ+ZKyslSTk3nm6H+0BF9pMA7BUeC33xHSy+3lJrNOr5S+Vup1Oir3Nu8i2vJYQV1pPkB5+zyUVEcNLD3xr/eNQ=私钥解密后:huanzi.qch@qq.com:欢子

  2、复杂对象的RSA公钥加密、私钥解密

复制代码

    public static void main(String[] args) {        //复杂对象
        ImsUserVo userVo = new ImsUserVo();
        userVo.setUserName("123456");
        userVo.setPassword("111111");        try {
            System.out.println("私钥:" + RsaUtil.getPrivateKey());
            System.out.println("公钥:" + RsaUtil.getPublicKey());            //公钥加密
            byte[] ciphertext = RsaUtil.encryptByPublicKey(userVo.toString().getBytes(), RsaUtil.getPublicKey());            //私钥解密
            byte[] plaintext = RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());

            System.out.println("公钥加密前:" + userVo.toString());
            System.out.println("公钥加密后:" + Base64.encodeBase64String(ciphertext));
            System.out.println("私钥解密后:" + new String(plaintext));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

复制代码

私钥:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL6gSKs2G4iFrhPo0aLELfGzsCAaB5hztvclD9J2hZT2KXfs6S5JwZ0RWRR28rqHm0e2RNW3fzYyOLvSoq93n/TRAkmXBbVia3BCTrSzLPrKFY8JvLyXqbrV0NrxywY+4ZlgR5R+scWaj3LtUR63sSXb5ddmOg9XctrWBGvsKrNJAgMBAAECgYEAoql9OPPDzNxdbcnGUQDcP5pYGRx9DL75Cq2KccoHNNRVEGuNkp0HZLLv84GIoFikzS2gUUnyeFmkhck4X0hRqYpCo9DwRsBgBpqn+4ebjSu4bd3lG5KCAtMaPC5sAbznY1uuuJnUdul3p9PuF7AmFTsoFFB4YvstvkRna5ZPFA0CQQDfpxPYVpZjOsgng7187vEpFa9vlQxmyamvJ2iAeFLRHCqJwlq4VYqJkgr08SE1XCBqSVhXkLyIPAtdeqxU0iFLAkEA2jJfKVSy4I/BHmk/rdpw7InQ3ERBc/a09t2ZiI3bqtnobTIf/sMZEWPeMkY83RrWL9ZQvMNDa843cans3bm1OwJAIdipGi5QaAf3TnOTc5q9iFgtypcl31BZi5ZNLFQJRHgcv+hXzlmzs4oUemkbe3XLugoLgoT24y8jESyFc/iw7QJBAJdR26EENlF6IIoAn8Ln/Oxt30UCqQnNDE8v+2wyRSdFm+Uun/XEQ7xFsDDZeRg1pljinndqS3WWO+k92SEjy0UCQQCr5UsIMBAjpGCYXeXrRWYoYdfI6+R20I+uWoOGzoly+KK4ixqMLFuimEwrmXhYnJMzvVHfbLsoogBv9NOP9ffH
公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+oEirNhuIha4T6NGixC3xs7AgGgeYc7b3JQ/SdoWU9il37OkuScGdEVkUdvK6h5tHtkTVt382Mji70qKvd5/00QJJlwW1YmtwQk60syz6yhWPCby8l6m61dDa8csGPuGZYEeUfrHFmo9y7VEet7El2+XXZjoPV3La1gRr7CqzSQIDAQAB
公钥加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
公钥加密后:Un+1m/CbpzVkkxYrwNOWyEXqpsawxcdv4p3G+9b+SQRiC/THL8YG+IvqFCHnxizzYGB9LEvLbQxw72JB0Wlo1+/SvX7AJb2h0ddpvVUkPjmtXNo073SV1zMK+9NTCJUMMoHu/TIptxRbVxlBoGMHa+jq8h2y3RUOPtx/9zhBWlQmzZEifv0MjgAhKX5ucExYfXctcAVGHL959+TwKqKQmTENw5o0ElksaA0KIF+4L7RvpWVSqZT1Y4O2gMP9ALjamCx6ziRcmk4b4Q5Goph0nmw6nA387qVi3Vz6rQHrIpL0HT5OSiz1O7+2L3N0Him2IZeAgg3EZCi5xTGl54jGEw==私钥解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)

  如需使用更多加密、填充方式,引入

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk16</artifactId>
    <version>1.46</version></dependency>

  加解密的时候改成

Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());

  重要更新

  2020-05-21更新

  重要Bug修复:后端加解密中,不能在代码里new BouncyCastleProvider(),JceSecurity. getVerificationResult内部会进行判断,如果是新值,则每次都会put到map中,导致内存缓便被耗尽,程序假死崩溃(参考博客:https://www./A/lk5aQo7451/ )

 

   应该改成,我已经在开源项目改了,博客上面之前贴出来的代码我就不改了,具体代码大家去开源项目查看看吧

 

   前端加、解密

   AES我们采用CryptoJS,是一个标准和安全加密算法的JavaScript库,它的AES加密支持AES-128、AES-192和AES-256。下载或查看详情介绍请戳官网地址

  GitHub地址:https://github.com/brix/crypto-js

  官网地址:https://code.google.com/archive/p/crypto-js/

  RSA我们采用JSEncrypt,它是一个很好用的RSA加密算法的JavaScript库,使用PKCS#1进行填充,加解密使用方式很简单,具体的介绍或者下载请移步官网

  GitHub地址:https://github.com/travist/jsencrypt

  官网地址:http:///jsencrypt/

  下载下来后我们在项目头部head.html引入,并新建两个小工具类

  AES

 aesUtil

  RSA

 rsaUtil

  简单测试

  AES对称加密、解密简单测试

  1、字符串

复制代码

//字符串let text = "huanzi.qch@qq.com:欢子";//keylet genKey = aesUtil.genKey();//key加密let ciphertext = aesUtil.encrypt(text,genKey);//key解密let plaintext = aesUtil.decrypt(ciphertext,genKey);

console.log("key:");console.log(genKey);
console.log("加密前:");console.log(text);
console.log("key加密后:" + ciphertext);
console.log("key解密后:");console.log(plaintext);

复制代码

key:q99IsnEuryk1ZvgX
加密前:huanzi.qch@qq.com:欢子
key加密后:aZn58GtEj9Is0hNWbJoqpRD6RkiBVPCHOvva3Xq2PYo=key解密后:huanzi.qch@qq.com:欢子

  2、复杂对象

复制代码

//复杂对象let user = {username: "欢子", password: 123456, remark: "abcd!@#$:"};//keylet genKey = aesUtil.genKey();//key加密let ciphertext = aesUtil.encrypt(user,genKey);//key解密let plaintext = aesUtil.decrypt(ciphertext,genKey);

console.log("key:");console.log(genKey);
console.log("加密前:");console.log(user);
console.log("key加密后:" + ciphertext);
console.log("key解密后:");console.log(plaintext);

复制代码

key:e6gzizHIpDfc6hbg
加密前:{username: "欢子", password: 123456, remark: "abcd!@#$:"}
key加密后:YdNw5AwteEp8WZs5xMv0YiGcXvX81P9MCLOvroHjfLUyQV/GwJ6obRqi4DT2ucJy8DWrKueOzLGLSQXUVhAgIA==
key解密后:{username: "欢子", password: 123456, remark: "abcd!@#$:"}

  RAS非对称加密、解密简单测试

  1、字符串的RSA公钥加密、私钥解密

复制代码

//普通字符串let text = "huanzi.qch@qq.com:欢子";//秘钥对let keyPair = rsaUtil.genKeyPair();//公钥加密let ciphertext = rsaUtil.encrypt(text,keyPair.publicKey);//私钥解密let plaintext = rsaUtil.decrypt(ciphertext,keyPair.privateKey);

console.log("秘钥:");console.log(keyPair.privateKey);
console.log("公钥:" + keyPair.publicKey);
console.log("加密前:" + text);
console.log("公钥加密后:" + ciphertext);
console.log("解密后:" + plaintext);

复制代码

秘钥:MIICXQIBAAKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQABAoGBANH92gJ85jld3YyoqHa6M4bSC5s2cGEqklWbkLEqQSacp7BrAP2yJ85UPkB9oRtYbr0tkciLYnptshq03TR2r7QT5+ovb5KJ2MQExTXk8GZTO/5sSqD0zwA9SESlAmWj8yc49p5Tk0h5UYFgsRATTer1n1llziryXa4QMiIfKrmBAkEA+za3DryZnMyNqre6Kx+FYsFVvIbHRU7tJ5LOiZ43Vl0DXq44zmeNQeh6MzfH6sc0Avu1c61/+KNDVf23yfVt7QJBAPGIr0GokOZ+L0sttiEoQSq/dBdYaSCBfTht+rA/9ie8RgcFJkYj4h/6RPzdYIRWDco5RzI+oPnZmFC4rfPjFVsCQHE2XFMw3c2TRfj86dKLVxKFbL0UxHNQuYIPIDNW8TtjmaQuwf0LH9bnDUNNzTPaaG87vq+OLlEAStVTDWPfzpUCQDVJvbjTstxXfKGufR9FnVMMGFXKOK9mQjU/9m4KPom3vQ9xcGdLJWl+stfDE7c+sR4rkuyf6q4U9sjgZeiH8j8CQQCfaxXfxiDJPztUDm1AKI6uDwz4P4eiYRbbbQ5x+iQSunHbq0Y7U9UUkWcLw0xDhReHEYkFuOeiBj2ViAPJz1r0
公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQAB
加密前:huanzi.qch@qq.com:欢子
公钥加密后:aUkUMYC7lF8M1xzcx5ZEdc0DQt4FrqvWqEnD30raV++j7rwsfEcyXpPmeF1g2LR86FVG3oxgdTptorkwUDSXB3Tv4av7toGg7Zcf9l1vs5WQX7kCDTitwBVwyBNTZq22xed1J/LAkDujDav6tUJHdMmRKYVe2NeTswvWLOqWWW4=
解密后:huanzi.qch@qq.com:欢子

  2、复杂对象的RSA公钥加密、私钥解密

复制代码

//复杂对象let user = {username:"欢子",password:123456,remark:"abcd!@#$:"};//秘钥对let keyPair = rsaUtil.genKeyPair();//公钥加密let ciphertext = rsaUtil.encrypt(user,keyPair.publicKey);//私钥解密let plaintext = rsaUtil.decrypt(ciphertext,keyPair.privateKey);

console.log("秘钥:");console.log(keyPair.privateKey);
console.log("公钥:" + keyPair.publicKey);
console.log("加密前:" + user);
console.log("公钥加密后:" + ciphertext);
console.log("解密后:" + plaintext);

复制代码

秘钥:MIICXAIBAAKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQABAoGAbwrLhkIvjk938nNnaRufoqqrW+5OMrzgis6bWlghckawr6NPj5ZPs7nKF/Sv79jdA/N55I6V7bHrxI2N+S9Ckm2ygv8nNYimSjzspR48SqVRuH/xYmQQ9hi8Iy4dTlCMud34oXsV2sYI5tEn7f3bypOVfJa6kHSqxe1PIQTxirkCQQDjmHOp2JMcltpL+639nnNgZ2U06cRhPHX+tcGTIgoqu1Sqp0bKH7QuUF9WPNWxHrbYY5+s5jnnhTTwQZg74atTAkEAwXelo0JLYHpML7+sLs8aUzitRJXjkW3dY4JPf1wLTNLbawvi4KA/6NA3jx7kCD6KzM7vsWWsRgArrUWa1Dbn6wJARY5pAuZyh1E/I+umEBWl0zemQZaT8tekhBSONWY4zzhzNrhqtQkdau4bROLQuBHX9af0u8WcuroGJMsXOG3OiwJBALc91OPJ8cziaPC80Z/QRvXV877HXTCsZ4lNrnBJxOYxvOMp8eyhu4aOWGE1d/QbEKolwj86tq3ikXvfNmOT0ZsCQBkfviB7CKdHCrUCnpAK0upa9x8uFraomDNxFP/HwTFPSOPGhA5pgAzJgygSu2hpEwFFIwfC3E+pQ2EAhoeIcdw=公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQAB
加密前:[object Object]
公钥加密后:LwggD8SIeWoqzh4gHh/vJ9nEZsqeNZfoxgrRRPD7k0wpp9/uZmR5kfRJ8O59yW5IaOt1z50mJ26ylRBOKNoTTl7Rt4zVmBYX4EXr4Ajq3CINFcPI/j5l8yQRSIgLPUvOxhIAKmfrgNKCaLjSdjK/CnTbPrZoDArI8iAHq/ih4r8=
解密后:{\"username\":\"欢子\",\"password\":123456,\"remark\":\"abcd!@#$:\"}

  联调测试

  自己解密自己加密的的数据基本上没有什么问题,重要的是解密对方加密的数据会不会成功,前后端相互加解密的工程中,最重要的就是保持两边的加密、填充方式一致、加密位数一致,还有就是后端Base64字符串转成byte[]数组的时候要注意,Base64工具类转跟直接字符串getByte()跟用输入输出流来转,得到的数组结果有差异,在本次测试中我也是搞了好久才使得前后端一致,紧跟上面的简单测试,接下来我们进行前后端联调测试

  1、AES:前后端相互用对方的key解密对方加密的数据

  2、RSA:前后端相互用对方的公钥进行加密数据,然后将数据叫给对方解密

  这里要讲一下步骤,不然大家看不懂下面这几张图,为了确保后端加密解密用的是同一个密钥对,我们采用控制台输入前端秘钥跟前端使用后端公钥加密后的密文,然后再使用私钥去解密从而得到前端的明文,而js前端部分,只有不刷新页面,对象数据会存在浏览器内存中,确保了加密解密是用同一个对密钥对

  总而言之,测试结果是正确的,接下来就可以再项目中进行加解密了

   项目应用

  理论思路

  前、后端的代码都封装好了,并且都通过了简单测试,接下来就是应用到项目中,首先我们要解决的是生成公钥秘钥并交换的问题,思路如下:

  生成:

    1、后端:在项目启动的时候生成RSA公钥秘钥并在整个项目运行中不发生改变(或者每隔一段时间更新一次也行),AES的key是每次响应之前随机获取

    2、前端:我们在访问页面开始生成RSA公钥秘钥并且希望页面在刷新之前都不发生改变,因此将它们存在window对象中(如果需要更加健全,使用H5的本地存储localStorage、sessionStorage)在head.html中生成,AES的key在每次发起请求之前随机获取

  交换:

    1、前端获取后端RSA公钥:前端访问登录页面(网站入口),后台返回modelAndView时注入RSA的公钥,前端获取用存到sessionStorage中,直到回话关闭

    2、后端获取前端RSA公钥:前端公钥跟随http请求发送到后端

  生成与交换公钥的问题解决了,接下来就是如何传输AES加密后的数据RSA公钥加密后的AES的key,思路如下:

  1、前端:重写$.ajax方法(或者封装一个ajax),发送数据前用AES加密数据(key随机生成),用后端的RSA公钥加密AES的key,将加密后的data数据、加密后的AES的key、前端RSA公钥发送到后端;触发回调后,先用前端RSA私钥解密AES的key,在用明文key去解密

  2、后端:写两个自定义注解Encrypt、Decrypt,AOP拦截所有带自定义注解的post请求进行加密解密,有@Encrypt需要对返回值进行加密,有@Decrypt需要对参数进行解密,加密解密过程与前端的操作同理

  前端代码

  引入js

        <!--CryptoJS jsencrypt -->
        <script th:src="@{/js/cryptojs.js}"></script>
        <script th:src="@{/js/jsencrypt.js}"></script>
        <script th:src="@{/js/aesUtil.js}"></script>
        <script th:src="@{/js/rsaUtil.js}"></script>

  下载CryptoJs跟jsencrypt下来发现CryptoJs需要引入很多js,因此在网上找了这个整合的js,引它就够了

 cryptojs.js

  获取后端公钥

<script th:inline="javascript">
    //获取后端RSA公钥并存到sessionStorage    sessionStorage.setItem('javaPublicKey', [[${publicKey}]]);</script>

  重写ajax以及生成前端密钥对,因为我们项目中大部分都是使用$.ajax方法,所有重写它比较合适,并且我们只拦截post请求

 head.html

  后端代码

  两个自定义注解

 Decrypt
 Encrypt

  aop扫描所有的controller,拦截带自定义标签的post请求,进行解密、加密再将明文设置回去(如何使用aop?请戳我之前的博客:SpringBoot系列——aop 面向切面

  要注意的是:

  1、我们在aop只设置了第一个参数,因此controller方法需要是实体接参且第一个参数就是,所有要求,要么有一个实体Vo参数,要么没有参数;

  2、对于返回值,需要是统一的返回值,因为我们目前是按统一的返回值设置值的,例如本例中的Result,是我们约定好的统一返回值(后续升级可以用反射来设置值);

  3、还有一个需要注意的地方,method方法必须是要public修饰的才能设置方法的形参值,private的设置不了;

  PS:2019-06-12补充,我们之前在进行jackson序列化和反序列化忘记对date进行处理,导致时间格式错乱,现在补充一下

 SafetyAspect.java

  在需要进行加密解密的controller方法上加自定义注解,需要加密@Encrypt,需要解密@Decrypt,两个都有就两个都加

复制代码

    /**
     * 登录     */
    @PostMapping("login")
    @Decrypt
    @Encrypt    public Result<ImsUserVo> login(ImsUserVo userVo, HttpServletResponse response)

复制代码

  到这里就准备好了,可以发现除了要在需要进行加密解密的controller方法上加自定义注解,根本不需要对之前的代码进行修改,也不影响之前的业务,当然,因为我们直接重写了$.ajax方法,所以我们需要对所有的post请求的controller都加注解,好在我们用的是单表继承通用common、自动生成单表基础增、删、改、查接口(详情请戳:SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口),基础的API我们只需要在common的controller加注解就好了,自定义controller跟重写的controller也要记得加,接下来就可以把项目跑起来进行测试

  效果演示

  未加密之前的效果

  数据直接暴露在http数据包中

  AES与RSA混合加密之后的效果

  http数据包中传输的是密文

 

  解密之后才能看到明文数据

  到这里我们实现了加解密与项目的结合,如果项目已经是按照我前面说的约定的话,即插即用,不影响项目原有业务,直接可以使用这一套,把我的代码拿过去就可以跑起来

  后记

   前后端API交互数据加密——AES与RSA混合加密完整实例先记录到这里,后续有空再更新升级,虽然说没有绝对的安全,但加密总比不加密的要好,整篇文章从头看起来也没什么,比较简单,实际上...中间踩了很多坑,心酸血泪史就不在这里阐述了,希望这篇博客能帮到你

  2019-09-24补充:完成这个混合加密后不久,我试着将它应用在websocket中,而后就有了另一篇文章,感兴趣的可以移步前往阅读《WebSocket数据加密——AES与RSA混合加密

  代码开源

  2019-09-24补充:混合加密的代码一直嵌在ims项目里没有整理出来,而且ims还没完工尚未开源出来,好在前段时间我开源的一个简单通用的后台管理系统,Base Admin(开源一套简单通用的后台管理系统),里面整合了这套API混合加密,大家前往这个项目查看这套API混合加密代码

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/base-admin

  码云:https:///huanzi-qch/base-admin

  中间人攻击

  什么是中间人攻击?维基百科:https://zh./wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB

  以下介绍摘自维基百科:

  中间人攻击英语:Man-in-the-middle attack,缩写:MITM)在密码学和计算机安全领域中是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。在许多情况下这是很简单的(例如,在一个未加密的Wi-Fi 无线接入点的接受范围内的中间人攻击者,可以将自己作为一个中间人插入这个网络)。

  一个中间人攻击能成功的前提条件是攻击者能将自己伪装成每一个参与会话的终端,并且不被其他终端识破。中间人攻击是一个(缺乏)相互认证的攻击。大多数的加密协议都专门加入了一些特殊的认证方法以阻止中间人攻击。例如,SSL协议可以验证参与通讯的一方或双方使用的证书是否是由权威的受信任的数字证书认证机构颁发,并且能执行双向身份认证。

  攻击示例

                                                                                                                                                     

中间人攻击示意图

  假设爱丽丝(Alice)希望与鲍伯(Bob)通信。同时,马洛里(Mallory)希望拦截窃会话以进行窃听并可能在某些时候传送给鲍伯一个虚假的消息。

首先,爱丽丝会向鲍勃索取他的公钥。如果Bob将他的公钥发送给Alice,并且此时马洛里能够拦截到这个公钥,就可以实施中间人攻击。马洛里发送给爱丽丝一个伪造的消息,声称自己是鲍伯,并且附上了马洛里自己的公钥(而不是鲍伯的)。

  爱丽丝收到公钥后相信这个公钥是鲍伯的,于是爱丽丝将她的消息用马洛里的公钥(爱丽丝以为是鲍伯的)加密,并将加密后的消息回给鲍伯。马洛里再次截获爱丽丝回给鲍伯的消息,并使用马洛里自己的私钥对消息进行解密,如果马洛里愿意,她也可以对消息进行修改,然后马洛里使用鲍伯原先发给爱丽丝的公钥对消息再次加密。当鲍伯收到新加密后的消息时,他会相信这是从爱丽丝那里发来的消息。

  1.爱丽丝发送给鲍伯一条消息,却被马洛里截获:

爱丽丝“嗨,鲍勃,我是爱丽丝。给我你的公钥” --> 马洛里 鲍勃

  2.马洛里将这条截获的消息转送给鲍伯;此时鲍伯并无法分辨这条消息是否从真的爱丽丝那里发来的:

爱丽丝 马洛里“嗨,鲍勃,我是爱丽丝。给我你的公钥” --> 鲍伯

  3.鲍伯回应爱丽丝的消息,并附上了他的公钥:

爱丽丝 马洛里<-- [鲍伯的公钥]-- 鲍伯

  4.马洛里用自己的密钥替换了消息中鲍伯的密钥,并将消息转发给爱丽丝,声称这是鲍伯的公钥:

爱丽丝<-- [马洛里的公钥]-- 马洛里 鲍勃

  5.爱丽丝用她以为是鲍伯的公钥加密了她的消息,以为只有鲍伯才能读到它:

爱丽丝“我们在公共汽车站见面!”--[使用马洛里的公钥加密] --> 马洛里 鲍勃

  6.然而,由于这个消息实际上是用马洛里的密钥加密的,所以马洛里可以解密它,阅读它,并在愿意的时候修改它。他使用鲍伯的密钥重新加密,并将重新加密后的消息转发给鲍伯:

爱丽丝 马洛里“在家等我!”--[使用鲍伯的公钥加密] --> 鲍伯

  7.鲍勃认为,这条消息是经由安全的传输通道从爱丽丝那里传来的。

  这个例子显示了爱丽丝和鲍伯需要某种方法来确定他们是真正拿到了属于对方的公钥,而不是拿到来自攻击者的公钥。否则,这类攻击一般都是可行的,在原理上,可以针对任何使用公钥——密钥技术的通讯消息发起攻击。幸运的是,有各种不同的技术可以帮助抵御MITM攻击。

  ----------- end -----------

  单纯的加密只能防监听偷窥,不能防中间人伪装,那么我们应该如何阻止中间人攻击呢?SSL协议

  HTTPS SSL协议:数字证书、CA机构、数字签名,请看大佬的这篇,通俗易懂:看完这篇文章,我奶奶都懂了https的原理 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多