SpringCloud使用RAS+AES实现加密传输

数据传输加密

在开发应用过程中,客户端与服务端经常需要进行数据传输,涉及到重要隐私信息时,开发者自然会想到对其进行加密,即使传输过程中被“有心人”截取,也不会将信息泄露。对于加密算法,相信不少开发者也有所耳闻,比如MD5加密,Base64加密,DES加密,AES加密,RSA加密等等。在这里我主要向大家介绍一下我在开发过程中使用到的加密算法,RSA加密算法+AES加密算法。简单地介绍一下这两种算法吧

RSA

之所以叫RSA算法,是因为算法的三位发明者RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准,主要的算法原理就不多加介绍,如果对此感兴趣的话,建议去百度一下RSA算法。需要了解的是RSA算法属于非对称加密算法,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。简单的说是“公钥加密,私钥解密;私钥加密,公钥解密”。

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

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

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

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

客户端传输重要信息给服务端,服务端返回的信息需加密,例如客户端登录的时候,传递用户名和密码等资料,需要进行加密,服务端验证登录信息后,返回令牌token需要进行加密,客户端解密后保存。此时就需要结合这两种算法了。至于整个流程是怎样的,在下面会慢慢通过例子向你介绍,因为如果一开始就这么多文字类的操作,可能会让读者感到一头雾水。

在Spring Cloud 项目中使用 RSA+AES 进行加密

粗略总结一下过程就是:客户端存放 RSA 的公钥,将 AES 的秘钥(客户端随机生成)通过RSA的公钥加密,业务数据使用AES 的秘钥进行加密,然后数据传送到服务端。服务端使用RSA私钥对RSA公钥加密过得AES秘钥进行解密得到AES秘钥。然后使用AES秘钥对业务数据解密,返回数据的时候使用AES 秘钥进行加密。
在Spring Cloud中加解密放在Gateway中,我的项目中gateway 使用的是zuul。

1.首先在gateway中加一个前置过滤器

@Slf4j
@Component
public class SignDecryptFilter extends BaseFilter {

    @Autowired
    private CheckRequireDecryptUtils checkRequireDecryptUtils;
//前置过滤器
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 3;
    }

    @Override
    public boolean shouldFilter() {
        /*return checkRequireDecryptUtils.checkRequireDecrypt();*/
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        getLogger().info("SignDecryptFilter>>>Options>>>{}", request.getMethod());
        if (!RequestContext.getCurrentContext().getBoolean(NEXT_SHOULD_FILTER)) {
            return false;
        }
        if (request.getMethod().equals("OPTIONS")) {

            return false;
        }
        return RequestContext.getCurrentContext().getBoolean(NEXT_SHOULD_FILTER);
    }

    @Override
    public Object run() {
        //验签并解密数据
        checkRequireDecryptUtils.checkAnddecryptWith();
        return null;
    }
}
public void checkAnddecryptWith() {
        RequestContext ctx = RequestContext.getCurrentContext();
        try {
            this.checkAnddecrypt();
//      throw new DecryptException(DecryptException.IllegalInputStream);
        } catch (Exception e) {
            ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
            ctx.setResponseStatusCode(401);
            ctx.set("isOK", false);//可以把一些值放到ctx中,便于后面的filter获取使用

            ResultModel model = new ResultModel();
            model.setMsg("异常信息:" + e.getMessage());
            model.setCode(String.valueOf(DecryptException.IllegalInputStream));
            model.setData("异常类:" + e.getClass());
            ctx.setResponseBody(JSON.toJSONString(model));

      /*ctx.setResponseBody(ResultFactory
          .resultMsg(Commond.R_FAIL, "异常类:" + e.getClass(),
              "异常信息:" + e.getMessage()));//返回内容给客户端 // 返回错误内容*/
            //ctx.setResponseBody(e.getMessage());//返回内容给客户端 // 返回错误内容
            ctx.getResponse().setContentType("application/json;charset=UTF-8");
        }
    }

前端参数转换的实体类

@Data
public class RsaRequestDto {

    /**
     * 时间戳
     */
    private String timestamp;

    /**
     * 随机字符串
     */
    private String nonce;

    /**
     * 业务数据加密后的密文
     */
    private String encrypt;

    /**
     * 被RSA公钥加密过得AES秘钥字符串
     */
    private String echokey;

    /**
     * 签名
     */
    private String signature; 
}
public void checkAnddecrypt() throws Exception {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        InputStream in = null;
        String body = null;
        try {
            in = ctx.getRequest().getInputStream();
            body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
        } catch (IOException e) {
            logger.error("解密接口数据获取入参异常,异常类{},异常信息{}", e.getClass(), e.getMessage());
            throw new DecryptException(DecryptException.IllegalInputStream);
        } 
        RsaRequestDto rsaRequestDto = null;
        if (!StringUtils.isEmpty(request.getContentType()) && request.getContentType()
                .contains("application/json")) {
            try {
                rsaRequestDto = JSONObject.parseObject(body, RsaRequestDto.class);
            } catch (Exception e) {
                logger.error("接口解密,解析json对象失败,将直接调用业务接口");
            }
        }

        if (!this.checkRequireDecrypt()) {
            //校验是否输入了加密参数
            if (rsaRequestDto != null && !StringUtils.isEmpty(rsaRequestDto.getEncrypt())) {
                throw new DecryptException(DecryptException.IllegalInputStream);
            }
            return;
        }

        //验证签名并解密业务数据
        String newBody = commonEncryption.checkAnddecrypt(rsaRequestDto);

        final byte[] reqBodyBytes = newBody.getBytes();
        ctx.setRequest(new HttpServletRequestWrapper(request) {
            @Override
            public ServletInputStream getInputStream() throws IOException {
                return new ServletInputStreamWrapper(reqBodyBytes);
            }

            @Override
            public int getContentLength() {
                return reqBodyBytes.length;
            }

            @Override
            public long getContentLengthLong() {
                return reqBodyBytes.length;
            }
        });
    }
  public String checkAnddecrypt(RsaRequestDto rsaRequestDto) throws Exception {
//判断是否为空,为空抛异常
        if (rsaRequestDto == null || StringUtils.isBlank(rsaRequestDto.getEncrypt()) || StringUtils
                .isBlank(rsaRequestDto.getEchokey()) || StringUtils.isBlank(rsaRequestDto.getNonce())
                || StringUtils.isBlank(rsaRequestDto.getSignature()) || StringUtils
                .isBlank(rsaRequestDto.getTimestamp())) {
            throw new DecryptException(DecryptException.IllegalInputStream);
        }

        //验证签名
        String signOriginStr = getStrForSign(rsaRequestDto.getTimestamp(), rsaRequestDto.getNonce(),
                rsaRequestDto.getEncrypt());
        boolean status = RSACoder
                .verify(signOriginStr.getBytes(), appConfigProperties.getPublicKeyForSign(),
                        rsaRequestDto.getSignature());
        if (!status) {
            throw new DecryptException(DecryptException.ValidateSignatureError);
        }

        //通过RSA的私钥解密获得前端 RSA公钥加密过得AES秘钥
        byte[] key = RSACoder.decryptByPrivateKey(rsaRequestDto.getEchokey(),
                appConfigProperties.getPrivateKeyForDataKey());
        String echokeyStr = new String(key, CHARSET);
//    System.out.println("aes 加密随机key    " + echokeyStr);

        //使用AES 的秘钥解密业务数据
        String data = AesUtils.decrypt(rsaRequestDto.getEncrypt(), echokeyStr, echokeyStr);
//    System.out.println(data);

        return data;
    }

返回加密数据的时候再使用个filter 做一下返回后置处理

public RsaRequestDto encrypt(String data) throws Exception {
        RsaRequestDto dto = new RsaRequestDto();

        String timestamp = String.valueOf(System.currentTimeMillis());
        String nonce = RSACoder.getRandomStr();

        //生成加密业务数据随机密匙(使用RSA 的算法生成一个AES的秘钥)
        String echokeyStr = RSACoder.getRandomStr();
        //加密业务数据随机密匙(使用RSA私钥加密AES秘钥)
        String echokey = RSACoder
                .encryptBASE64(
                        RSACoder.encryptByPublicKey(echokeyStr, appConfigProperties.getPublicKeyForDataKey()));

        //使用AES秘钥加密业务数据
        String encrypt = AesUtils.encrypt(data, echokeyStr, echokeyStr);

        //生成签名
        String signature = RSACoder
                .sign(getStrForSign(timestamp, nonce, encrypt).getBytes(CHARSET),
                        appConfigProperties.getPrivateKeyForSign());

        dto.setTimestamp(timestamp);
        dto.setNonce(nonce);
        //AES秘钥加密过得业务数据
        dto.setEncrypt(encrypt);
        //被加密过得AES秘钥
        dto.setEchokey(echokey);
        dto.setSignature(signature);

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

推荐阅读更多精彩内容