API接口安全设计

简介

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

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

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

HTTP API接口安全性演进如下

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

使用token进行用户身份认证

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

具体说明如下:

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值的时间戳进行比较,差值超过一段时间则不予放行。

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 元,由于没有防篡改校验,导致多支付了金钱,造成了用户损失。因此我们在接口设计时必须考虑防篡改校验,加签、验签就是用来解决这个问题的。加签、验签是用来解决防篡改问题的。

相关概念

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

对称加密、非对称加密

  • 对称加密:加密和解密使用相同密钥的加密算法。 img
  • 非对称加密:非对称加密算法中加密和解密用的不是同一个秘钥,所以叫作非对称加密算法。在非对称加密算法每个用户都有两把钥匙,一把公钥一把私钥。公钥是对外发布的,所有人都看的到所有人的公钥,私钥是自己保存,每个人都只知道自己的私钥而不知道别人的。

img

非对称加密

什么是公钥私钥

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

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

加签验签

加签

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

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

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

img

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

加密和解密

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

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

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

常见加密相关算法简介

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

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

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

消息摘要算法

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

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

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)
MD5128无限≤18(发现碰撞)335
SHA-11602^64 − 1<63(发现碰撞)192
SHA-2242242^64 − 1112139
SHA-2562562^64 − 1128139
SHA-3843842^128 − 1192154
SHA-5125122^128 − 1256154
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());
    }
}

常用的签名算法:

MAC算法家族

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

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

对称加密算法

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

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。

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等国密算法。

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) {

        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/116751.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++11 initializer_list 轻量级初始化列表的使用场景(让自定义类可以用初始化列表的形式来实例化对象)

initializer_list 是 C11 中的一个特性&#xff0c;它允许你使用花括号 {} 中的值列表来初始化容器或数组。通常用于初始化标准库容器&#xff0c;比如 std::vector、std::set、std::map 以及数组。 场景一&#xff1a;用初始化列表初始化容器 std::vector<int> arr {…

【深度学习】pytorch——Autograd

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——Autograd Autograd简介requires_grad计算图没有梯度追踪的张量ensor.data 、tensor.detach()非叶子节点的梯度计算图特点总结 利用Autograd实…

scrapy+selenium框架模拟登录

目录 一、cookie和session实现登录原理 二、模拟登录方法-Requests模块Cookie实现登录 三、cookiesession实现登录并获取数据 四、selenium使用基本代码 五、scrapyselenium实现登录 一、cookie和session实现登录原理 cookie:1.网站持久保存在浏览器中的数据2.可以是长期…

Day20力扣打卡

打卡记录 数组中两个数的最大异或值&#xff08;位运算&#xff09; 链接 二进制位上从高位向低位进行模拟&#xff0c;看数组中是否有满足此情况的数字。具体题解 class Solution { public:int findMaximumXOR(vector<int>& nums) {int mx *max_element(nums.be…

【存档】vscode配置latex环境

原来在另一台电脑上找了个教程配了一遍&#xff0c;这次重新配的时候&#xff0c;那个教程作者更新过后&#xff0c;改成只有linux的脚本了&#xff0c;所以存档一下。真香警告, 2023年初的vscodelatex写作 - 知乎 (zhihu.com) 环境&#xff1a; win10/win11vscodelatex work…

【PyTorch实战演练】AlexNet网络模型构建并使用Cifar10数据集进行批量训练(附代码)

目录 0. 前言 1. Cifar10数据集 2. AlexNet网络模型 2.1 AlexNet的网络结构 2.2 激活函数ReLu 2.3 Dropout方法 2.4 数据增强 3. 使用GPU加速进行批量训练 4. 网络模型构建 5. 训练过程 6. 完整代码 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我…

分享81个工作总结PPT,总有一款适合您

分享81个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/13hyrlZo2GhRoQjI-6z31-w?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知识付…

IDEA创建Springboot多模块项目

一、创建父模块 File --> New --> Project &#xff0c;选择 “ Spring Initalizr ” &#xff0c;点击 Next Next Next --> Finish 二、创建子模块 右键根目录&#xff0c;New --> Module 选择 “ Spring Initializr ”&#xff0c;点击Next 此处注意T…

设置IDEA快捷生成方法头,类头注释

1.File->settings->editor->live templates进入Live Template界面进行设置&#xff1a; 下一步&#xff1a; 下一步&#xff1a; /*** Title: $title$* author: sunyanzeng* date: $datatime$*/在需要添加文件头的地方打出“aa”&#xff0c;回车&#xff0c;会自…

go语言 | grpc原理介绍(三)

了解 gRPC 通信模式中的消息流 gRPC 支持四种通信模式&#xff0c;分别是简单 RPC、服务端流式 RPC、客户端流式 RPC 和双向流式 RPC。 简单 RPC 在gRPC中&#xff0c;一个简单的RPC调用遵循请求-响应模型&#xff0c;通常涉及以下几个关键步骤和组件&#xff1a; 请求头&a…

Java自学第2课:Java语言基础知识要点

1 Java主类结构 任务&#xff1a;创建新项目名为item&#xff0c;包名为number&#xff0c;类名为first。 1.1 包声明 不指定包时&#xff0c;默认就是工程名&#xff0c;指定后&#xff0c;类文件可以分类了&#xff0c;是这意思吧。包就大概等于一个文件夹。而且在类文件中…

Educational Codeforces Round 2 D 计算几何

题目链接&#xff1a;Educational Codeforces Round 2 D 题目 给你两个圆。求它们相交处的面积。 输入 第一行包含三个整数 x1, y1, r1 (  - 109 ≤ x1, y1 ≤ 109, 1 ≤ r1 ≤ 109 ) - 第一个圆的圆心位置和半径。 第二行包含三个整数 x2, y2, r2 (  …

【IO多路转接】select编程模型

文章目录 1 :peach:五种IO模型:peach:1.1 :apple:阻塞IO:apple:1.2 :apple:非阻塞IO:apple:1.3 :apple:信号驱动IO:apple:1.4 :apple:IO多路转接:apple:1.5 :apple:异步IO:apple:1.6 :apple:同步通信&异步通信:apple:1.7 :apple:阻塞&非阻塞:apple:1.8 :apple:总结:app…

IDEA快捷键总结+常识积累

&#xff08;一&#xff09;常用快捷键总结 以下快捷键输入完成后按Tab键即可。 1、输入main public static void main(String[] args) {}2、输入sout System.out.println();3、输入fori for (int i 0; i < ; i) {}4、输入foreach&#xff08;增强for循环快捷键&#x…

蓝桥杯(C++ 扫雷)

题目&#xff1a; 思想&#xff1a; 1、遍历每个点是否有地雷&#xff0c;有地雷则直接返回为9&#xff0c;无地雷则遍历该点的周围八个点&#xff0c;计数一共有多少个地雷&#xff0c;则返回该数。 代码&#xff1a; #include<iostream> using namespace std; int g[…

Prometheus接入AlterManager配置企业微信告警(基于K8S环境部署)

文章目录 一、创建企业微信机器人二、配置AlterManager告警发送至企业微信三、Prometheus接入AlterManager配置四、部署PrometheusAlterManager(放到一个Pod中)五、测试告警 注意&#xff1a;请基于 PrometheusGrafana监控K8S集群(基于K8S环境部署)文章之上做本次实验。 一、创…

装修服务预约小程序的内容如何

大小装修不断&#xff0c;市场中大小品牌也比较多&#xff0c;对需求客户来说&#xff0c;可以线下咨询也可以线上寻找品牌&#xff0c;总是可以找到满意的服务公司&#xff0c;而对装修公司来说如今线下流量匮乏&#xff0c;很多东西也难以通过线下方式承载&#xff0c;更需要…

配置Raspberry自动连接WIFI,在无法查看路由器的校园网情况下使用自己电脑热点

1、开启电脑热点&#xff0c;并共享电脑WLAN2 打开控制面板->网络和Internet->网络连接 选择自己的校园网&#xff0c;我这里是WLAN2&#xff0c;右键属性&#xff0c;如下操作&#xff1a; 如果没有看到 本地连接*10类似的图标 则按如下操作&#xff1a;winx键&#x…

SpringBoot-WebSocket浏览器-服务器双向通信

文章目录 WebSocket 介绍入门案例 WebSocket 介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输。 应用场景&#xff1a; 视…

Android - 编译 openssl 踩坑之路

一、简述 如果你想快速在项目中使用上 openssl,可以使用网上其他开发者提供好的预编译库: OpenSSL(All):https://builds.viaduck.org/prebuilts/openssl/OpenSSL(3.1.*) :https://github.com/217heidai/openssl_for_android以上的预编译库可能最低只支持 API 21(即 Andro…