API接口安全设计

简介

HTTP接口是互联网各系统之间对接的重要方式之一,使用HTTP接口开发和调用都很方便,也是被大量采用的方式,它可以让不同系统之间实现数据的交换和共享。
由于HTTP接口开放在互联网上,所以我们就需要有一定的安全措施来保证接口安全。

个人觉得安全措施大体来看主要在两个方面:

  1. 一方面就是如何保证数据在传输过程中的安全性;
  2. 另一个方面是数据已经到达服务器端,服务器端如何识别数据,如何不被攻击;

HTTP API接口安全性演进如下

  1. HTTP + 完全开放 (毫无安全可言)
  2. HTTP + 参数签名 (基本安全)
  3. HTTP + 私钥签名公钥验签 (安全性高)
  4. HTTPS + 参数签名 (安全性更高)
  5. HTTPS + 私钥签名公钥验签 (最安全)

使用token进行用户身份认证

用户身份认证的流程图如下:

API接口安全设计_第1张图片

具体说明如下:

1、 用户登录时,客户端请求接口,传入用户名和密文的密码
2、 后台服务对用户身份进行验证。若验证失败,则返回错误结果;若验证通过,则生成一个随机不重复的token,并将其存储在redis中,设置一个过期时间。
3、 用户身份校验通过后,后台服务将生成的token返回客户端。
4、客户端请求后续其他接口时,需要带上这个token。后台服务会统一拦截接口请求,进行token有效性校验,并从中获取用户信息,供后续业务逻辑使用

数据加密

现在主流的加密方式有对称加密和非对称加密

  1. 对称加密:对称密钥在加密和解密的过程中使用的密钥是相同的,常见的对称加密算法有DES,AES;优点是计算速度快,缺点是在数据传送前,发送方和接收方必须商定好秘钥,然后使双方都能保存好秘钥,如果一方的秘钥被泄露,那么加密信息也就不安全了;
  2. 非对称加密:服务端会生成一对密钥,私钥存放在服务器端,公钥可以发布给任何人使用;优点就是比起对称加密更加安全,但是加解密的速度比对称加密慢太多了;广泛使用的是RSA算法;

两种方式各有优缺点,而https的实现方式正好是结合了两种加密方式,整合了双方的优点,在安全和性能方面都比较好;

数据加签名sign

常见的签名方式实现一般分为以下几个步骤 :

1 . 将所有(或者特殊)请求参数按特定规则排序;在 Java 中可以使用 TreeMap 进行排序。

2 . 将请求参数按特定规则拼装为加密字符串;

3 . 加密算法对加密字符串进行加密,得到签名。

str:参数1={参数1}&参数2={参数2}&……&参数n={参数n}$key={用户密钥};
MD5.encrypt(str);

时间戳机制

sign机制可以防止参数被篡改,但无法防dos攻击(第三方使用正确的参数,不停请求服务器,使之无法正常提供服务)。因此,还需要引入时间戳机制。
具体的操作为:客户端在形成sign值时,除了使用所有参数和token外,再加一个发起请求时的时间戳。即

sign值来源 = 所有非空参数升序排序+token+timestamp

后端则需要根据当前时间和sign值的时间戳进行比较,差值超过一段时间则不予放行。

API接口安全设计_第2张图片

long interval=5*60*1000//超时时间
long clientTime=request.getparameter("clientTime");
long serverTime=System.currentTimeMillis();
if(serverTime-clientTime>interval){
    return new Response("超过处理时长")
}

数据合法性校验

接口层对参数进行合法校验,只有在数据是合法的情况下才会进行数据处理。

AppId机制

对应的对外提供的接口,并不是谁都可以调用,需要使用接口的用户需要在后台开通appid,提供给用户相关的密钥;在调用的接口中需要提供 appid+密钥,服务器端会进行相关的验证;

限流

常用的限流算法有令牌桶和漏桶算法。

防止重复提交

对于一些重要的操作需要防止客户端重复提交的(如非幂等性重要操作),具体办法是当请求第一次提交时将sign作为key保存到redis,并设置超时时间,超时时间和Timestamp中设置的差值相同。

当同一个请求第二次访问时会先检测redis是否存在该sign,如果存在则证明重复提交了,接口就不再继续调用了。如果sign在缓存服务器中因过期时间到了,而被删除了,此时当这个url再次请求服务器时,因token的过期时间和sign的过期时间一直,sign过期也意味着token过期,那样同样的url再访问服务器会因token错误会被拦截掉,这就是为什么sign和token的过期时间要保持一致的原因。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。

接口参数私钥签名公钥验签

在客户端与服务端请求交互的过程中,请求的数据容易被拦截并篡改,比如在支付场景中,请求支付金额为 1 元,被拦截后篡改为 100 元,由于没有防篡改校验,导致多支付了金钱,造成了用户损失。因此我们在接口设计时必须考虑防篡改校验,加签、验签就是用来解决这个问题的。加签、验签是用来解决防篡改问题的。

相关概念

  • 明文:指没有经过加密的信息/数据。
  • 密文:明文被加密算法加密之后,会变成密文,以确保数据安全。
  • 密钥:是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥。
  • 加密:将明文变成密文的过程。
  • 解密:将密文还原为明文的过程。

对称加密、非对称加密

  • 对称加密:加密和解密使用相同密钥的加密算法。 API接口安全设计_第3张图片
  • 非对称加密:非对称加密算法中加密和解密用的不是同一个秘钥,所以叫作非对称加密算法。在非对称加密算法每个用户都有两把钥匙,一把公钥一把私钥。公钥是对外发布的,所有人都看的到所有人的公钥,私钥是自己保存,每个人都只知道自己的私钥而不知道别人的。

API接口安全设计_第4张图片

非对称加密

什么是公钥私钥

  • 公钥与私钥是成对存在的密钥,如果用公钥对数据进行加密,只有用对应的私钥才能解密。
  • 公钥就是公开的秘钥,私钥就是要你自己保存好的秘钥。
  • 非对称加密算法需要有一对公私钥

如果用到的是非对称加密,那么你和第三方之间就有两对公私钥,各自持有对方的公钥和自己的私钥

加签验签

加签

签名主要包含摘要和非对称加密两部分内容,首先对需要签名的数据进行摘要计算得到摘要值,然后通过签名者的私钥对摘要值进行非对称加密即可得到签名结果。

API接口安全设计_第5张图片

一般情况下,发送请求时,会将数据和数字签名一起打包发送给接收方。

API接口安全设计_第6张图片

验签
  • 接收方拿到原始报文和数字签名后,用**「同一个Hash函数」**从报文中生成摘要A。另外,用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。

API接口安全设计_第7张图片

互联网网上的这个图,更容易理解一点:

API接口安全设计_第8张图片

加密和解密

用该用户的公钥加密后只能该用户的私钥才能解密。这种情况下,公钥是用来加密信息的,确保只有特定的人(用谁的公钥就是谁)才能解密该信息。所以这种我们称之为加密和解密。

一、发送方(RSAwithSHA、RSAwithMD5):
1.对传输的报文进行摘要,主要的算法有MD5和SHA
2.对摘要用自己的私钥进行加密生成签名,一般用到的算法如RS
3.传输报文及签名

二、接收方:
1.对接收到的报文用同样的算法进行摘要
2.用发送方的公钥对发送方的签名进行解密得到发送方的摘要
3.对比两份摘要看是否有不同以验证是否被篡改

常见加密相关算法简介

数据摘要算法是密码学算法中非常重要的一个分支,它通过对所有数据提取指纹信息以实现数据签名、数据完整性校验等功能,由于其不可逆性,有时候会被用做敏感信息的加密。数据摘要算法也被称为哈希(Hash)算法或散列算法。

消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。(摘要可以比方为指纹,消息摘要算法就是要得到文件的唯一职位)

  • 消息摘要算法
  • 对称加密算法
  • 非对称加密算法
  • 国密算法

消息摘要算法

  • 相同的明文数据经过相同的消息摘要算法会得到相同的密文结果值。
  • 数据经过消息摘要算法处理,得到的摘要结果值,是无法还原为处理前的数据的。
  • 数据摘要算法也被称为哈希(Hash)算法或散列算法。
  • 消息摘要算法一般用于签名验签。

消息摘要算法主要分三类:MD(Message Digest,消息摘要算法)、SHA(Secure Hash Algorithm,安全散列算法)和MAC(Message Authentication Code,消息认证码算法)。

API接口安全设计_第9张图片

MD家族算法

MD(Message Digest,消息摘要算法)家族,包括MD2,MD4,MD5。

  • MD2,MD4,MD5 计算的结果都是是一个128位(即16字节)的散列值,用于确保信息传输完整一致。
  • MD2的算法较慢但相对安全,MD4速度很快,但安全性下降,MD5则比MD4更安全、速度更快。
  • MD5被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。
  • MD5,可以被攻破,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞攻击,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

举个例子,看看如何获取字符串的MD5值吧:

public class MD5Test {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "123";
        byte[] result = getMD5Bytes(s.getBytes());
        StringBuilder stringBuilder = new StringBuilder();
        for (byte temp : result) {
            if (temp >= 0 && temp < 16) {
                stringBuilder.append("0");
            }
            stringBuilder.append(Integer.toHexString(temp & 0xff));
        }
        System.out.println(s + ",MD5加密后:" + stringBuilder.toString());
    }

    private static byte[] getMD5Bytes(byte[] content) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            return md5.digest(content);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:

123,MD5加密后:202cb962ac59075b964b07152d234b70
ShA家族算法

SHA(Secure Hash Algorithm,安全散列算法),包括SHA-0、SHA-1、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384等)、SHA-3。它是在MD算法基础上实现的,与MD算法区别在于**「摘要长度」,SHA 算法的摘要「长度更长,安全性更高」**。

  • SHA-0发布之后很快就被NSA撤回,因为含有会降低密码安全性的错误,它是SHA-1的前身。
  • SHA-1在许多安全协议中广为使用,包括TLS、GnuPG、SSH、S/MIME和IPsec,是MD5的后继者。
  • SHA-2包括SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。它的算法跟SHA-1基本上相似,目前还没有出现明显弱点。
  • SHA-3是2015年正式发布,由于对**「MD5出现成功的攻破」**,以及对SHA-0和SHA-1出现理论上攻破的方法,SHA-3应运而生。它与之前算法不同的是,它是可替换的加密散列算法。

SHA-1、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384)等算法是比较常用的,我们来看看跟MD5的对比吧

算法类型 摘要长度(bits) 最大输入消息长度(bits) 碰撞攻击(bits) 性能示例(MiB/s)
MD5 128 无限 ≤18(发现碰撞) 335
SHA-1 160 2^64 − 1 <63(发现碰撞) 192
SHA-224 224 2^64 − 1 112 139
SHA-256 256 2^64 − 1 128 139
SHA-384 384 2^128 − 1 192 154
SHA-512 512 2^128 − 1 256 154
try {
    // 生成一个MD5加密计算摘要
    MessageDigest md = MessageDigest.getInstance("MD5");
    // 计算md5函数
    md.update(str.getBytes());
    // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
    // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
    return new BigInteger(1, md .digest()).toString(16);
    } catch (Exception e) {
		throw new Exception("MD5加密出现错误,"+e.toString());
    }
}

常用的签名算法:

API接口安全设计_第10张图片

MAC算法家族

MAC算法 MAC(Message Authentication Code,消息认证码算法),是带密钥的Hash函数。输入密钥和消息,输出一个消息摘要。 它集合了MD和SHA两大系列消息摘要算法。

  • MD 系列算法: HmacMD2、HmacMD4 和 HmacMD5 ;
  • SHA 系列算法:HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384 和 HmacSHA512 。

对称加密算法

加密和解密使用**「相同密钥」**的加密算法就是对称加密算法。常见的对称加密算法有AES、3DES、DES、RC5、RC6等。

API接口安全设计_第11张图片

DES

数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法。 DES算法的入口参数有三个:Key、Data、Mode。

  • Key: 7个字节共56位,是DES算法的工作密钥;
  • Data: 8个字节64位,是要被加密或被解密的数据;
  • Mode: 加密或解密。
3DES

三重数据加密算法(英语:Triple Data Encryption Algorithm,又称3DES(Triple DES),是一种对称密钥加密块密码,相当于是对每个数据块应用三次数据加密标准(DES)算法。

AES

AES,高级加密标准(英语:Advanced Encryption Standard),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。

  • 采用对称分组密码体制,密钥长度为 128 位、 192 位、256 位,分组长度128位
  • 相对于DES ,AES具有更好的 安全性、效率 和 灵活性。

非对称加密算法

非对称加密算法需要两个密钥:公钥和私钥。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有用对应的私钥才能解密。主要的非对称加密算法有:RSA、Elgamal、DSA、D-H、ECC。

API接口安全设计_第12张图片

RSA算法
  • RSA加密算法是一种非对称加密算法,广泛应用于加密和数字签名
  • RSA算法原理:两个大素数的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
  • RSA是被研究得最广泛的公钥算法,从提出到现在,经历了各种攻击的考验,普遍认为是目前最优秀的公钥方案之一。
DSA
  • DSA(Digital Signature Algorithm,数字签名算法),也是一种非对称加密算法。
  • DSA和RSA区别在,DSA仅用于数字签名,不能用于数据加密解密。其安全性和RSA相当,但其性能要比RSA好。
ECC 算法
  • ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学),基于椭圆曲线加密。
  • Ecc主要优势是,在某些情况下,它比其他的方法使用更小的密钥,比如RSA加密算法,提供相当的或更高等级的安全级别。
  • 它的一个缺点是,加密和解密操作的实现比其他机制时间长 (相比RSA算法,该算法对CPU 消耗严重)。

国密算法

国密即国家密码局认定的国产密码算法。为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,即SM1,SM2,SM3,SM4等国密算法。

API接口安全设计_第13张图片

SM1
  • SM1,为对称加密算法,加密强度为128位,基于硬件实现。
  • SM1的加密强度和性能,与AES相当。
SM2
  • SM2主要包括三部分:签名算法、密钥交换算法、加密算法
  • SM2用于替换RSA加密算法,基于ECC,效率较低。
SM3
  • SM3,即国产消息摘要算法。
  • 适用于商用密码应用中的数字签名和验证,消息认证码的生成与验证以及随机数的生成。
SM4
  • SM4是一个分组算法,用于无线局域网产品。
  • 该算法的分组长度为128比特,密钥长度为128比特。
  • 加密算法与密钥扩展算法都采用32轮非线性迭代结构。
  • 解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。
  • 它的功能类似国际算法的DES。

RSA加签验签代码实现

下面使用用的是SHA-256作为摘要算法,RSA作为签名验签算法,如下:

package com.demo.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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.HashMap;
import java.util.Map;

public class RSAUtil {
    private static final String signature_algorithm = "SHA256withRSA"; // 签名算法
    private static final String encryptAlgorithm = "RSA"; // 加密算法
    private static final String decryptAlgorithm = "RSA"; // 解密算法
    private static final String charset = "UTF-8";
    private static final int max_encrypt_block = 234; //2048位rsa单次最大加密长度
    private static final int max_decrypt_block = 256; //2048位rsa单次最大解密长度

    private static PublicKey sign_public_key = null;
    private static PrivateKey sign_private_key = null;

    private static PublicKey crypt_public_key = null;
    private static PrivateKey crypt_private_key = null;

    static {
        // 静态加载,提高效率,但是配置的修改都需要重启server才能生效
        // 对方base64后公钥字符串,用于验签
        String sign_pub_key_str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnBYMjGWVjlWk3u7aXDtKmGghWV5glVTYFQ3ijelj3DwHB71jZQDKMnPkCeHMJk6iMJ04r+akLbxiaMIhF/qx/RYfTvgrM9Y3ZNiK0PYTS2W8Iw2wjKDjOKO21N+R4jl/PugOYDi6Ru6dmQjKPeGiDtJAPvqa7tyUyBrB0F4X/h+FX+M+mTJ7TsBV+4cTgzJy5iz9nntS6ccMUJ92xBimqLwDUTDeRtm1+a2x3sIAOstCFOBsqyCXFWmOpElvGQcsMXvgw0DvNMG+M+pOEkqZoTt47Pp1FweOBqT6gT/q9xbAqFxTMUCI0XZan0mJ7MYBqA4ySqxg9J/uz4p+pOEshwIDAQAB";
        // 己方base64后私钥字符串,用于签名
        String sign_pri_key_str = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcFgyMZZWOVaTe7tpcO0qYaCFZXmCVVNgVDeKN6WPcPAcHvWNlAMoyc+QJ4cwmTqIwnTiv5qQtvGJowiEX+rH9Fh9O+Csz1jdk2IrQ9hNLZbwjDbCMoOM4o7bU35HiOX8+6A5gOLpG7p2ZCMo94aIO0kA++pru3JTIGsHQXhf+H4Vf4z6ZMntOwFX7hxODMnLmLP2ee1LpxwxQn3bEGKaovANRMN5G2bX5rbHewgA6y0IU4GyrIJcVaY6kSW8ZBywxe+DDQO80wb4z6k4SSpmhO3js+nUXB44GpPqBP+r3FsCoXFMxQIjRdlqfSYnsxgGoDjJKrGD0n+7Pin6k4SyHAgMBAAECggEASx1lTo94iLX4kPybgzVZcbzzB6Iekt7w2jkDZU4DK7KLo5Ll6W6W3+7buFG8wFapQQH4jNZO/l+hcE60RGj2DRj/Wi6eA+U8ZUC8lVFG+crs3mWxKAwpLVHEI++vshH/hZOBj5bdNlOQ7lvHkD4skjtmhahutTLcOux9hzwxCa76ZPeeznN/J+HI/CjI3J2JGRPDZmbcnezVdnJbQYY8K1YeuKIRFGkTh/sGIR1n3PDdxFPQSPv5VA/Ykd2IHXKTVoN+huozzxGIKbEThIvlBZo9CgXQ9+AnZRELZRLag+M+UPUhbeeqpaTQYXMPAmziEN24TU9DfkhUl9rD3vO0CQKBgQDcBfxcw0awDwRTVbWZBKc01vn+DhbWmt8GoJ2cITLvnR2FW8KgOFFMhCUjjmOgSBhGZnk8aKhMC1qQEd9e0VrmQ+0+IG0q8Q+lNf67CyHebVHBarcHcct68lyIRU498LfjUnbs3kswDrdR4+zzEDMpQwYq6/TYKY513iiWwlygQwKBgQC1m7KYPzYj3+wveErRgS2+6zngqVmXriWT8VFMAaqqs/hYlQ1Ho5U2e/tcZts4t1FfCgGPPM9vGkAc7Gn/sn6LSVQet1+VRfc2IT5AcPOutwP8c7PpsOGoZYWWXJadhg1JqZo3rxGOhEQ1XWFXk/wX9d0Sllyu35sD0GRusXRQbQKBgBfIXdr5EK7/MIyBezurERfZFPStOTLBUtI4klDKFeNorEQ6AvOmosMOlaUeQw6UPGt/sCMjfO2bXJKuG+L35kd1mDNa9fHqVLKa/4ngTizozCmIC3i2iDQl9nKUazyuxHHB/DDmZmIvdQlZBcfQPHd9UzFYiALFmyyKcwC4yaJZAoGAVWHqSaIOdjdk97x6kJ1HQKefAn0cXi/GAxRFwJJYBwGuFReesru5/2+y8fJ5xuSJIUG3EfzpGbchxXdxLoJg9GN5ZSeZjLjkTVK7zdhM+SuaeCp9v7UlouJ4OAU32r+Xp7ZRhzSL8JFG8EAC8AXnU+yID6EZ2i3O17A2R8SuhtECgYBT/D3FZxSDvfNC3GFIRfIvsXMtAuM4MBvT6Z9ucyYluVORkEXcSRT3PcCTusUUEzN6gk2tgzhhHYY5O23LC+svFEy/nE8nkcDsqupvzLEces0OwRfUIj2KrrHkGGjN+a5ioxu5ieO1VxoLu5HjP8shm/Oypfnk9A6x86Av3L3ScA==";

        // 对方base64后公钥字符串,用于加密
        String crypt_pub_key_str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnBYMjGWVjlWk3u7aXDtKmGghWV5glVTYFQ3ijelj3DwHB71jZQDKMnPkCeHMJk6iMJ04r+akLbxiaMIhF/qx/RYfTvgrM9Y3ZNiK0PYTS2W8Iw2wjKDjOKO21N+R4jl/PugOYDi6Ru6dmQjKPeGiDtJAPvqa7tyUyBrB0F4X/h+FX+M+mTJ7TsBV+4cTgzJy5iz9nntS6ccMUJ92xBimqLwDUTDeRtm1+a2x3sIAOstCFOBsqyCXFWmOpElvGQcsMXvgw0DvNMG+M+pOEkqZoTt47Pp1FweOBqT6gT/q9xbAqFxTMUCI0XZan0mJ7MYBqA4ySqxg9J/uz4p+pOEshwIDAQAB";
        // 己方base64后私钥字符串,用于解密
        String crypt_pri_key_str = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcFgyMZZWOVaTe7tpcO0qYaCFZXmCVVNgVDeKN6WPcPAcHvWNlAMoyc+QJ4cwmTqIwnTiv5qQtvGJowiEX+rH9Fh9O+Csz1jdk2IrQ9hNLZbwjDbCMoOM4o7bU35HiOX8+6A5gOLpG7p2ZCMo94aIO0kA++pru3JTIGsHQXhf+H4Vf4z6ZMntOwFX7hxODMnLmLP2ee1LpxwxQn3bEGKaovANRMN5G2bX5rbHewgA6y0IU4GyrIJcVaY6kSW8ZBywxe+DDQO80wb4z6k4SSpmhO3js+nUXB44GpPqBP+r3FsCoXFMxQIjRdlqfSYnsxgGoDjJKrGD0n+7Pin6k4SyHAgMBAAECggEASx1lTo94iLX4kPybgzVZcbzzB6Iekt7w2jkDZU4DK7KLo5Ll6W6W3+7buFG8wFapQQH4jNZO/l+hcE60RGj2DRj/Wi6eA+U8ZUC8lVFG+crs3mWxKAwpLVHEI++vshH/hZOBj5bdNlOQ7lvHkD4skjtmhahutTLcOux9hzwxCa76ZPeeznN/J+HI/CjI3J2JGRPDZmbcnezVdnJbQYY8K1YeuKIRFGkTh/sGIR1n3PDdxFPQSPv5VA/Ykd2IHXKTVoN+huozzxGIKbEThIvlBZo9CgXQ9+AnZRELZRLag+M+UPUhbeeqpaTQYXMPAmziEN24TU9DfkhUl9rD3vO0CQKBgQDcBfxcw0awDwRTVbWZBKc01vn+DhbWmt8GoJ2cITLvnR2FW8KgOFFMhCUjjmOgSBhGZnk8aKhMC1qQEd9e0VrmQ+0+IG0q8Q+lNf67CyHebVHBarcHcct68lyIRU498LfjUnbs3kswDrdR4+zzEDMpQwYq6/TYKY513iiWwlygQwKBgQC1m7KYPzYj3+wveErRgS2+6zngqVmXriWT8VFMAaqqs/hYlQ1Ho5U2e/tcZts4t1FfCgGPPM9vGkAc7Gn/sn6LSVQet1+VRfc2IT5AcPOutwP8c7PpsOGoZYWWXJadhg1JqZo3rxGOhEQ1XWFXk/wX9d0Sllyu35sD0GRusXRQbQKBgBfIXdr5EK7/MIyBezurERfZFPStOTLBUtI4klDKFeNorEQ6AvOmosMOlaUeQw6UPGt/sCMjfO2bXJKuG+L35kd1mDNa9fHqVLKa/4ngTizozCmIC3i2iDQl9nKUazyuxHHB/DDmZmIvdQlZBcfQPHd9UzFYiALFmyyKcwC4yaJZAoGAVWHqSaIOdjdk97x6kJ1HQKefAn0cXi/GAxRFwJJYBwGuFReesru5/2+y8fJ5xuSJIUG3EfzpGbchxXdxLoJg9GN5ZSeZjLjkTVK7zdhM+SuaeCp9v7UlouJ4OAU32r+Xp7ZRhzSL8JFG8EAC8AXnU+yID6EZ2i3O17A2R8SuhtECgYBT/D3FZxSDvfNC3GFIRfIvsXMtAuM4MBvT6Z9ucyYluVORkEXcSRT3PcCTusUUEzN6gk2tgzhhHYY5O23LC+svFEy/nE8nkcDsqupvzLEces0OwRfUIj2KrrHkGGjN+a5ioxu5ieO1VxoLu5HjP8shm/Oypfnk9A6x86Av3L3ScA==";

        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            sign_public_key = keyFactory.generatePublic(new X509EncodedKeySpec(Base64Utils.decodeFromString(sign_pub_key_str))); // 用于验签
            sign_private_key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64Utils.decodeFromString(sign_pri_key_str))); // 用于签名

            crypt_public_key = keyFactory.generatePublic(new X509EncodedKeySpec(Base64Utils.decodeFromString(crypt_pub_key_str))); // 用于加密
            crypt_private_key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64Utils.decodeFromString(crypt_pri_key_str))); // 用于解密
        } catch (Exception e) {
            //日志记录
        }
    }

    /**
     * 签名
     * @param param
     * @return
     */
    public static String sign(String param) {
        try {
            if (sign_private_key == null) {
                throw new RuntimeException("私钥未初始化");
            }
            Signature signature = Signature.getInstance(signature_algorithm);
            signature.initSign(sign_private_key);
            signature.update(param.getBytes(charset));
            return Base64Utils.encodeToString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名异常", e);
        }
    }

    /**
     * 加密
     * @param param
     * @return
     */
    public static String encrypt(String param) {
        if (crypt_public_key == null) {
            throw new RuntimeException("公钥未初始化");
        }
        if (StringUtils.isEmpty(param)) {
            throw new IllegalArgumentException("待加密数据为空");
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Cipher cipher = Cipher.getInstance(encryptAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, crypt_public_key);
            byte[] data = param.getBytes(charset);
            int inputLen = data.length;

            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen > max_encrypt_block + offSet) {
                    cache = cipher.doFinal(data, offSet, max_encrypt_block);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * max_encrypt_block;
            }
            return Base64Utils.encodeToString(out.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException("加密异常", e);
        }
    }

    /**
     * 验签
     * @param param
     * @param sign
     * @return
     */
    public static boolean veriSign(String param, String sign) {
        try {
            if (sign_public_key == null) {
                throw new RuntimeException("公钥未初始化");
            }
            Signature signature = Signature.getInstance(signature_algorithm);
            signature.initVerify(sign_public_key);
            signature.update(param.getBytes(charset));
            return signature.verify(Base64Utils.decodeFromString(sign));
        } catch (Exception e) {
            throw new RuntimeException("验签失败", e);
        }
    }

    /**
     * 解密
     * @param param
     * @return
     */
    public static String decrypt(String param) {
        if (crypt_private_key == null) {
            throw new RuntimeException("私钥未初始化");
        }
        if (StringUtils.isEmpty(param)) {
            throw new IllegalArgumentException("待解密数据为空");
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Cipher cipher = Cipher.getInstance(decryptAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, crypt_private_key);
            byte[] data = Base64Utils.decodeFromString(param);
            int inputLen = data.length;
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen > max_decrypt_block + offSet) {
                    cache = cipher.doFinal(data, offSet, max_decrypt_block);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                i++;
                out.write(cache, 0, cache.length);
                offSet = i * max_decrypt_block;
            }
            return new String(out.toByteArray(), charset);
        } catch (Exception e) {
            throw new RuntimeException("解密处理异常", e);
        }
    }

    public static void main(String[] args){
//        generateKey();
//        requestDemo();
        serverDemo();
    }

    /**
     * 发起请求
     */
    private static void requestDemo() {
        try {
            String param = new JSONObject().fluentPut("name", "张三").toJSONString();
            String data = encrypt(param);
            String sign = sign(data);
            JSONObject jsonObject = new JSONObject().fluentPut("sign", sign).fluentPut("data", data);

            System.out.println("加密后:"+jsonObject.toJSONString());
            HttpPost httpPost = new HttpPost("url");
            httpPost.setEntity(new StringEntity(jsonObject.toJSONString(), "application/json", "utf-8"));
            HttpClient httpClient = HttpClientBuilder.create().build();
            httpClient.execute(httpPost);
            ((CloseableHttpClient) httpClient).close();
        } catch (IOException ignore) {

        }
    }

    /**
     * 服务方
     * @return
     */
    private static String serverDemo() {

        // http获取请求头, 请求体
        String requestBody = "{\"data\":\"hlnWYqUGXLgZVPifPrVtEp//4GY6t6GM03m1exbDFhsnYGDyADlOREBDsOHvqgfZ3lPhZCKiva0tK5zgSTfbpXPymQxtOhx5x1PekLxx2G/myK74lErC5Vj5OY5oyI12o07TWbaqENATYldViCGf5pZ+Ms2LgYYLpfdVKoth2Xs80MEU/2RzivGp5f1CJST+aG5Vz4cFXWVqorhG3/9Kt+1d1sSv8VYaM0fREx1TBKzEzTq1G++tEMW/9eRPMj8YY5W2vo1JsdhgfzoAC8m/pdBbQ493ePJzMpdRI/Zt/bbnZVkGw5YYpI7saTrWDxjtpEeMaZ9RHyMWrusacAD1SA==\",\"sign\":\"gIC9PdGO99e4IXDP06LRz4pktULp5nQ50lI3nTL0pi8N9yjG6laODm+eOzTsSbsoSGeGuTaikaqGBTkN2Fmj5923K8jTJ/A0bFCuNlIpVaoTtC6r8S7J/whW63EPJ3JRWWjWZ2QWWWYNMofGLVZ4veps8Q02zyrW0pQh0Sc5TykaOO+rX3eu0wcXF4mep5CLutQnDCVBWBwTn1D3Lfhc42UsuSKvPpteCTOwI2mJ/m8swJg0lMvt/iDVBOSx55ItVEbg3OVmNvki4DTBsGuhy6inPQ5zF4S/SDVqseHL63g6OAb8EX/fW9GleFBuzcrs1zkYF4Gd0fUXm1N/uFsHqQ==\"}";

        // 接收请求获取参数
        JSONObject requestParams = JSONObject.parseObject(requestBody);

        /*取参数及签名*/
        String data = requestParams.getString("data");
        String sign = requestParams.getString("sign");

        /*验签*/
        boolean f = veriSign(data, sign);
        if (f) { // 验签通过才需要解密,否则认为非法请求
            /*解密*/
            String dec = decrypt(data);
            System.out.println("解密后:"+dec);
            /*业务逻辑*/
            JSONObject businessResult = doBusiness(dec);

            /*加密*/
            String responseParam = encrypt(businessResult.toJSONString());
            /*签名*/
            String responseSign = sign(responseParam);
            Map<String, String> response = new HashMap<>(2);
            response.put("data", responseParam);
            response.put("sign", responseSign);
            /*返回*/
            return JSONObject.toJSONString(response);
        }else {
            return null;
        }
    }

    private static JSONObject doBusiness(String dec) {
        /*响应结果*/
        return new JSONObject();
    }

    /**
     * 密钥生成
     */
    private static void generateKey() {

        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(encryptAlgorithm);
            keyPairGen.initialize(2048);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            System.out.println("privateKey: " + Base64Utils.encodeToString(privateKey.getEncoded()));
            System.out.println("publicKey:  " + Base64Utils.encodeToString(publicKey.getEncoded()));
        } catch (NoSuchAlgorithmException ignore) {

        }
    }

}

你可能感兴趣的:(安全)