vue/java使用国密SM2加密

————踩坑不易,转载需注明出处—————

本文前后端均使用国密SM2加密,后端在gateway中统一拦截,解密和加密。
流程步骤为:
1、前端对请求数据进行加密;
2、网关请求拦截器拦截前端请求,并对请求数据解密;
3、网关响应拦截器拦截后端响应数据,并对响应数据加密;
4、前端对返回的数据进行解密。

一:导入jar包

<!--SM2加密-->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.69</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.56</version>
</dependency>
<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.4</version>
</dependency>

二:密钥生成

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.junit.Test;

public class SM2Test {
	@Test
    public void createSM2Key(){

        SM2 sm2 = SmUtil.sm2();
        // sm2的加解密时有两种方式即 C1C2C3、 C1C3C2,
        sm2.setMode(SM2Engine.Mode.C1C3C2);
        // 生成私钥
        String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
        // 生成公钥
        String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
    }
}

生成2份公钥和私钥

三:网关拦截器

1、请求拦截器——私钥2解密
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {

    @Value("${ht.publicKey2}")
    private String publicKey2;

    @Value("${ht.privateKey2}")
    private String privateKey2;

    static Logger logger = LoggerFactory.getLogger(RequestGlobalFilter.class);

    @Autowired
    private ServerCodecConfigurer configurer;

    protected String decrypt(String body, SM2 sm2) {
        String data;
        if (StrUtil.isNotEmpty(body)) {
            if (StrUtil.isNotEmpty(body)) {
                String d = body.startsWith("04") ? body : "04" + body;
                data = sm2.decryptStr(d, KeyType.PrivateKey, Charset.forName("utf-8"));
            } else {
                data = body;
            }
        } else {
            data = body;
        }
        return data;
    }

    /**
     * 全局过滤器 核心方法
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(exchange.getRequest().getHeaders().getContentType())) {
            return chain.filter(exchange);
        }
        final HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        final ServerRequest serverRequest = ServerRequest.create(exchange, configurer.getReaders());
        Mono modifiedBody;
        Class dataClass;
        dataClass = String.class;
        modifiedBody = serverRequest
                .bodyToMono(String.class)
                .flatMap(body -> {
                    String newBody = null;
                    try {
                        if (body != null) {
                            String d = body.startsWith("04") ? body : "04" + body;
                            // 解密
                            SM2 sm2 = SmUtil.sm2(privateKey2, publicKey2);
//                            sm2.setMode(SM2Engine.Mode.C1C3C2);
//                            String newBody1 = StrUtil.utf8Str(sm2.decryptFromBcd(d, KeyType.PrivateKey));
                            newBody = sm2.decryptStr(d, KeyType.PrivateKey, Charset.forName("utf-8"));
                            // 去掉解密后字符串开始结尾多出的引号
                            if (newBody.startsWith("\"")) {
                                newBody = newBody.substring(1);
                            }
                            if (newBody.endsWith("\"")) {
                                newBody = newBody.substring(0, newBody.length() - 1);
                            }
                        }
                    } catch (Exception e) {
                        return Mono.error(e);
                    }
                    return Mono.just(newBody);
                });
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, dataClass);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter
                .insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Override
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
//                            headers.set("Content-Type", "application/json");
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

                        @Override
                        public Flux getBody() {
                            return outputMessage.getBody();
                        }

                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }

    @Override
    public int getOrder() {
        return -200;
    }

}
2、响应拦截器——公钥1加密
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

@Component
@Slf4j
public class ResponseGlobalFilter implements GlobalFilter, Ordered {

    @Value("${ht.publicKey1}")
    private String publicKey1;

    @Value("${ht.privateKey1}")
    private String privateKey1;

    @Override
    public int getOrder() {
        // -1 is response write filter, must be called before that
        return -2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                ServerHttpResponse originalResponse = exchange.getResponse();
                MediaType mediaType = originalResponse.getHeaders().getContentType();
                if (body instanceof Flux && !MediaType.APPLICATION_OCTET_STREAM.isCompatibleWith(mediaType)) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        // 释放掉内存
                        DataBufferUtils.release(join);
                        // 加密
                        SM2 sm2 = SmUtil.sm2(privateKey1, publicKey1);
//                        sm2.setMode(SM2Engine.Mode.C1C3C2);
                        // 返回值的字符串,需要注意此处加密后密文为16进制,否则前端解密前需要先转换格式
                        String encryptStr = sm2.encryptHex(new String(content), Charset.forName("utf-8"), KeyType.PublicKey);
                        // 加密后密文前有04,需要去掉(改为前端去除)
//                        String encrypt = encryptStr.startsWith("04") ? encryptStr.substring(2) : encryptStr;
                        return bufferFactory.wrap(encryptStr.getBytes());
                    }));
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }
}

四:前端代码

npm install --save sm-crypto

1、公钥2加密
main.js添加:
// 加密:
export function doEncrypt(msgString) {
  let msg = msgString;
  if (typeof (msgString) !== 'string') {
    msg = JSON.stringify(msgString);
  }
  let sm2 = require('sm-crypto').sm2;
  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
  let cipherMode = 1
  let publicKey2 = '你的公钥2'
  // 加密结果
  let encryptData = sm2.doEncrypt(msg, publicKey2, cipherMode); 
  // 加密后的密文前需要添加04,后端才能正常解密
  let encrypt = '04' + encryptData;
  return encrypt;
};
2、私钥1解密
// 解密
doDecryptStr(enStr) {
  let msg = enStr;
  if (typeof (enStr) !== 'string') {
    msg = JSON.stringify(enStr);
  }

  let sm2 = require('sm-crypto').sm2;
  // 1 - C1C3C2;	0 - C1C2C3;	默认为1
  let cipherMode = 1
  let privateKey1 = '你的私钥1'
  // 加密后的密文,需要前去掉04。因为doDecrypt中自行添加了04,后端加密代码也自行添加了04
  let en = enStr.data.substr(2)
  // 解密结果
  let doDecrypt = sm2.doDecrypt(en, privateKey1, cipherMode); 
  // 解密后类型转换
  let objData = JSON.parse(doDecrypt)
  return objData;
},

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值