前后端数据传输加密详解

共计10292字,阅读大约35分钟。

[toc]

一 : 加密方式利弊

  • 对称加密

    AES之类的加密 , 秘钥会公开在前端
    缺点 : 前端的信息是完全公开的
  • 非对称加密

    RAS之类的加密 , 私钥在服务端相对安全
    缺点 : 增加服务器负担 , 影响交互速度

二 : 两种加密方式并用流程

A:随机生成AES的秘钥--》用AES秘钥加密原文--》用RSA公钥加密AES秘钥--》用RSA私钥签名原文;

B:用RSA私钥解密AES秘钥--》用AES秘钥解密出原文--》用RSA公钥验证签名。

三 : 并用方法

  • 前端引入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>
  • 前端获取后端公钥
<script th:inline="javascript">
    //获取后端RSA公钥并存到sessionStorage
    sessionStorage.setItem('javaPublicKey', [[${publicKey}]]);
</script>

  • 重写ajax以及生成前端密钥对,并且我们只拦截post请求
<script>
            //获取前端RSA公钥密码、AES的key,并放到window
            let genKeyPair = rsaUtil.genKeyPair();
            window.jsPublicKey = genKeyPair.publicKey;
            window.jsPrivateKey = genKeyPair.privateKey;

            /**
             * 重写jquery的ajax方法
             */
            let _ajax = $.ajax;//首先备份下jquery的ajax方法
            $.ajax = function (opt) {
                //备份opt中error和success方法
                let fn = {
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                    },
                    success: function (data, textStatus) {
                    }
                };
                if (opt.error) {
                    fn.error = opt.error;
                }
                if (opt.success) {
                    fn.success = opt.success;
                }

                //加密再传输
                if (opt.type.toLowerCase() === "post") {
                    let data = opt.data;
                    //发送请求之前随机获取AES的key
                    let aesKey = aesUtil.genKey();
                    data = {
                        data: aesUtil.encrypt(data, aesKey),//AES加密后的数据
                        aesKey: rsaUtil.encrypt(aesKey, sessionStorage.getItem('javaPublicKey')),//后端RSA公钥加密后的AES的key
                        publicKey: window.jsPublicKey//前端公钥
                    };
                    opt.data = data;
                }

                //扩展增强处理
                let _opt = $.extend(opt, {
                    //成功回调方法增强处理
                    success: function (data, textStatus) {
                        if (opt.type.toLowerCase() === "post") {
                            data = aesUtil.decrypt(data.data.data, rsaUtil.decrypt(data.data.aesKey, window.jsPrivateKey));
                        }
                        //先获取明文aesKey,再用明文key去解密数据
                        fn.success(data, textStatus);
                    }
                });
                return _ajax(_opt);
            };
        </script>

head.html

  • 后端代码 两个自定义注解
package cn.huanzi.ims.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decrypt {

}

Decrypt
package cn.huanzi.ims.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {

}

Encrypt
package cn.huanzi.ims.aspect;

import cn.huanzi.ims.annotation.Decrypt;
import cn.huanzi.ims.annotation.Encrypt;
import cn.huanzi.ims.common.pojo.Result;
import cn.huanzi.ims.util.AesUtil;
import cn.huanzi.ims.util.RsaUtil;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;

/**
 * AES + RSA 加解密AOP处理
 */
@Aspect
@Component
public class SafetyAspect {

    /**
     * Pointcut 切入点
     * 匹配cn.huanzi.ims.*.controller包下面的所有方法
     */
    @Pointcut(value = "execution(public * cn.huanzi.ims.*.controller.*.*(..))")
    public void safetyAspect() {
    }

    /**
     * 环绕通知
     */
    @Around(value = "safetyAspect()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert attributes != null;
            //request对象
            HttpServletRequest request = attributes.getRequest();

            //http请求方法  post get
            String httpMethod = request.getMethod().toLowerCase();

            //method方法
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();

            //method方法上面的注解
            Annotation[] annotations = method.getAnnotations();

            //方法的形参参数
            Object[] args = pjp.getArgs();

            //是否有@Decrypt
            boolean hasDecrypt = false;
            //是否有@Encrypt
            boolean hasEncrypt = false;
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Decrypt.class) {
                    hasDecrypt = true;
                }
                if (annotation.annotationType() == Encrypt.class) {
                    hasEncrypt = true;
                }
            }

            //前端公钥
            String publicKey = null;

            //jackson
            ObjectMapper mapper = new ObjectMapper();
            //jackson 序列化和反序列化 date处理
            mapper.setDateFormat( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

            //执行方法之前解密,且只拦截post请求
            if ("post".equals(httpMethod) && hasDecrypt) {
                //AES加密后的数据
                String data = request.getParameter("data");
                //后端RSA公钥加密后的AES的key
                String aesKey = request.getParameter("aesKey");
                //前端公钥
                publicKey = request.getParameter("publicKey");

                System.out.println("前端公钥:" + publicKey);

                //后端私钥解密的到AES的key
                byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
                aesKey = new String(plaintext);
                System.out.println("解密出来的AES的key:" + aesKey);

                //AES解密得到明文data数据
                String decrypt = AesUtil.decrypt(data, aesKey);
                System.out.println("解密出来的data数据:" + decrypt);

                //设置到方法的形参中,目前只能设置只有一个参数的情况
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                if(args.length > 0){
                    args[0] = mapper.readValue(decrypt, args[0].getClass());
                }
            }

            //执行并替换最新形参参数   PS:这里有一个需要注意的地方,method方法必须是要public修饰的才能设置值,private的设置不了
            Object o = pjp.proceed(args);

            //返回结果之前加密
            if (hasEncrypt) {
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                //每次响应之前随机获取AES的key,加密data数据
                String key = AesUtil.getKey();
                System.out.println("AES的key:" + key);
                String dataString = mapper.writeValueAsString(o);
                System.out.println("需要加密的data数据:" + dataString);
                String data = AesUtil.encrypt(dataString, key);

                //用前端的公钥来解密AES的key,并转成Base64
                String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));

                //转json字符串并转成Object对象,设置到Result中并赋值给返回值o
                o = Result.of(mapper.readValue("{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}", Object.class));
            }

            //返回
            return o;

        } catch (Throwable e) {
            System.err.println(pjp.getSignature());
            e.printStackTrace();
            return Result.of(null, false, "加解密异常:" + e.getMessage());
        }
    }
}

SafetyAspect.java
  • 在需要进行加密解密的controller方法上加自定义注解,需要加密@Encrypt,需要解密@Decrypt,两个都有就两个都加
/**
     * 登录
     */
    @PostMapping("login")
    @Decrypt
    @Encrypt
    public Result<ImsUserVo> login(ImsUserVo userVo, HttpServletResponse response)
图片[1] | Web Stack | 前后端数据传输加密详解 | 一个栈

四 : 中间人攻击

什么是中间人攻击?维基百科:https://zh.wikipedia.org/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.鲍勃认为,这条消息是经由安全的传输通道从爱丽丝那里传来的。

五 : 总结

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

为了保护数据隐私,让数据不再“裸奔”。

请开启 HTTPS 协议之旅吧

图片[2] | Web Stack | 前后端数据传输加密详解 | 一个栈
图片[3] | Web Stack | 前后端数据传输加密详解 | 一个栈
https略解
温馨提示:本文最后更新于2022-06-12 12:20:24,某些文章具有时效性,若有错误或已失效,请在下方留言或联系雅舍站长
© 版权声明
THE END
有所帮助就支持一下吧
点赞0当赏 分享
箴言区 抢沙发
头像
达瓦里希请发言...
提交
头像

昵称

取消
昵称表情代码图片