java中各种加密算法的实践应用

1、前言

数字签名、信息加密是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、oauth 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的签名加密算法来达到业务目标。

2、加密算法概念

2.1 加密和解密

1)加密

数据加密 的基本过程,就是对原来为 明文 的文件或数据按 某种算法 进行处理,使其成为 不可读 的一段代码,通常称为 “密文”。通过这样的途径,来达到 保护数据 不被 非法人窃取、阅读的目的。

2)解密

加密逆过程解密,即将该 编码信息 转化为其 原来数据 的过程。

2.2 对称加密和非对称加密

加密算法分 对称加密 和 非对称加密,其中对称加密算法的加密与解密 密钥相同,非对称加密算法的加密密钥与解密密钥不同,此外,还有一类不需要密钥的散列算法。

常见的 对称加密 算法主要有 DES、3DES、AES 等,常见的 非对称算法 主要有 RSA、DSA 等,散列算法 主要有 SHA-1、MD5 等。

1)对称加密

对称加密算法是应用较早的加密算法,又称为 共享密钥加密算法。在 对称加密算法 中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行 加密 和 解密。这就要求加密和解密方事先都必须知道加密的密钥。
在这里插入图片描述
数据加密过程:在对称加密算法中,数据发送方 将 明文 (原始数据) 和 加密密钥 一起经过特殊 加密处理,生成复杂的 加密密文 进行发送。
数据解密过程:数据接收方 收到密文后,若想读取原数据,则需要使用 加密使用的密钥 及相同算法的 逆算法 对加密的密文进行解密,才能使其恢复成 可读明文。

2)非对称加密

非对称加密算法,又称为 公开密钥加密算法。它需要两个密钥,一个称为公开密钥 (public key),即 公钥,另一个称为 私有密钥 (private key),即 私钥。
在这里插入图片描述

如果使用 公钥 对数据 进行加密,只有用对应的 私钥 才能 进行解密。

如果使用 私钥 对数据 进行加密,只有用对应的 公钥 才能 进行解密。

3、常见的签名加密算法

3.1 MD5算法

MD5 用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,以防止被篡改。严格来说,MD5 不是一种 加密算法而是摘要算法。无论是多长的输入,MD5 都会输出长度为 128bits 的一个串 (通常用 16 进制 表示为 32 个字符)。

1)密文格式

MD5加密算法最终得到的结果在默认情况下是一个32位的字符串,字符串中包含了数字和小写英文字母。也有使用32字符串中从第9位到第24位的一个16位字符串表示加密结果的。将加密字符串中的字母全部使用大写形式,依然是MD5加密。

所以,MD5加密的结果可以有四种表现形式:32位小,32位大,16位小,16位大。32和16是指加密密文的字符长度,16位加密密文实际是32位加密密文截取第9位到第24位的部分得到的。大与小指的是密文中的字母是大写还是小写形式。

Java生成的MD5密文是32位小写形式的,MySQL本身提供了MD5加密函数,其结果也是32位小写。

2)Java自身包实现

@Test
public void test1() {
    /**
     * 0==cfcd208495d565ef66e7dff9f98764da
     * 1==c4ca4238a0b923820dcc509a6f75849b
     * 2==c81e728d9d4c2f636f067f89cc14862c
     * 3==eccbc87e4b5ce2fe28308fd9f2a7baf3
     * 4==a87ff679a2f3e71d9181a67b7542122c
     */
    for (int i = 0; i < 5; i++) {
        System.out.println(i + "==" + md5(String.valueOf(i)));
    }
}
public static String md5(String str) {
    byte[] secretBytes = null;
    try {
        secretBytes = MessageDigest.getInstance("md5").digest(str.getBytes());
    } catch (Exception e) {
        throw new RuntimeException("没有这个md5算法!");
    }
    String md5code = new BigInteger(1, secretBytes).toString(16);
    for (int i = 0; i < 32 - md5code.length(); i++) {
        md5code = "0" + md5code;
    }
    return md5code;
}

如果程序想要实现32位大、16位小、16位大的加密,并不需要重新构建算法,因为这三种加密结果都是32小的变种。16位加密只是对32位加密密文的截取,大写密文仅仅是将小写密文中的字母转大写即可。
在下面的程序中,我们使用md5表示32位小写加密,MD5表示32位大写加密,md516表示16位小写加密,MD516表示16位大写加密。

public static String md516(String inStr) {
    return md5(inStr).substring(8, 24);
}
public static String MD516(String inStr) {
    return md5(inStr).toUpperCase().substring(8, 24);
}
public static String MD5(String inStr) {
    return md5(inStr).toUpperCase();
}

3)使用commons-codec

apache提供了一个加密包commons-codec,里面提供了常用的编解码方法。MD5并不例外,也被commons-codec封装在里面。

引入依赖:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>

使用:

@Test
public void test2(){
    /**
     * 0==cfcd208495d565ef66e7dff9f98764da
     * 1==c4ca4238a0b923820dcc509a6f75849b
     * 2==c81e728d9d4c2f636f067f89cc14862c
     * 3==eccbc87e4b5ce2fe28308fd9f2a7baf3
     * 4==a87ff679a2f3e71d9181a67b7542122c
     */
    for (int i = 0; i < 5; i++) {
        System.out.println(i + "==" + md5Codec(String.valueOf(i)));
    }
}
public static String md5Codec(String plainText) {
    try {
        // md5加密方法使用规则
        return DigestUtils.md5Hex(plainText.getBytes("UTF-8"));
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        return null;
    }
}

public static String md516Codec(String plainText) {
    return md5Codec(plainText).substring(8, 24);
}

public static String MD516Codec(String plainText) {
    return md5Codec(plainText).toUpperCase().substring(8, 24);
}

public static String MD5Codec(String plainText) {
    return md5Codec(plainText).toUpperCase();
}

4)加盐处理

所谓加盐就是通过在密码任意固定位置插入特定的字符串再进行加密。

比如: MD5( 明文密码 + Salt)

MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'

测试验证:

@Test
public void test3(){
    /**
     * 0==e9113758e0ff881ab2770f05afde5552
     * 1==e511341dc05782269d3d859b5ff3939b
     * 2==667ab9fae0436dab2ca00f2fdd9cfa77
     * 3==2f4fe09bb4f6bb9ad8ee6e7579e8f5e1
     * 4==cf597769c9bc6fc458c48a5967a3eb6e
     */
    String salt="abc";
    for (int i = 0; i < 5; i++) {
        System.out.println(i + "==" + md5(String.valueOf(i)+salt));
    }
}

实际项目中,Salt 不一定要加在最前面或最后面,也可以插在中间嘛,也可以分开插入,也可以倒序,程序设计时可以灵活调整,都可以使破解的难度指数级增长。

5)实际项目中的使用

用户密码的存储

数据存储形式如下:

mysql> select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt                       | PwdHash                          |
+----------+----------------------------+----------------------------------+
| lisi     | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| zhangsa  | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |
+----------+----------------------------+----------------------------------+

明文密码+salt盐值加密得到PwdHash
登录验证的时候使用同样的加密算法比对PwdHash是否一样,判断是否登录

文件上传校验是否重复上传文件

引入hutool包

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.7</version>
</dependency>

文件服务器中,校验文件是否是重复上传,如果是,则引用原来的文件,节省空间。删除文件的时候需要判断当前文件是否被引用(通过md5值判断和查找文件),如果没有被引用,则删除。

@Test
public void test4(){
    /**
     * 0ce38a76c8ba5a1e932ea00bce358be0
     * 0ce38a76c8ba5a1e932ea00bce358be0
     * true
     */
    try {
        FileInputStream file1 = new FileInputStream(new File("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\java\\com\\slfx\\open\\api\\Md5Test.java"));
        FileInputStream file2 = new FileInputStream(new File("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\java\\com\\slfx\\open\\api\\Md5Test.java"));
        String md51=DigestUtil.md5Hex(file1);
        String md52= DigestUtil.md5Hex(file2);
        System.out.println(md51);
        System.out.println(md52);
        System.out.println(md51.equals(md52));
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
    }
}
网上下载文件校验文件完整性

哈希值这里主要是防篡改,防http劫持。如果是https下载哈希检验几乎是没必要的。再有加上哈希可能也就是防止下错东西和判断是否更新,比如filezilla-latest.tar.gz这种。各种自动更新的机制只用检查哈希值是否变了,来判断自己是否需要更新。

3.2 AES算法

AES、DES、3DES 都是对称的块加密算法,加解密的过程是 可逆的。常用的有 AES128、AES192、AES256 (默认安装的 JDK 尚不支持 AES256,需要安装对应的 jce 补丁进行升级 jce1.7,jce1.8)。

AES 加密算法是密码学中的 高级加密标准,该加密算法采用 对称分组密码体制,密钥长度的最少支持为 128 位、 192 位、256 位,分组长度 128 位,算法应易于各种硬件和软件实现。这种加密算法是美国联邦政府采用的 区块加密标准。

AES 本身就是为了取代 DES 的,AES 具有更好的 安全性、效率 和 灵活性。

1)代码实现

AES工具类
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
/**
 * @author zengqingfa
 * @className AESUtils
 * @description AES 对称算法加密/解密工具类
 * @create 2022/9/25 22:48
 */
public class AESUtils {
    /**
     * 密钥长度: 128, 192 or 256
     */
    private static final int KEY_SIZE = 128;
    /**
     * 加密/解密算法名称
     */
    private static final String ALGORITHM = "AES";
    /**
     * 随机数生成器(RNG)算法名称
     */
    private static final String RNG_ALGORITHM = "SHA1PRNG";
    /**
     * 生成密钥对象
     */
    private static SecretKey generateKey(byte[] key) throws Exception {
        // 创建安全随机数生成器
        SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM);
        // 设置 密钥key的字节数组 作为安全随机数生成器的种子
        random.setSeed(key);
        // 创建 AES算法生成器
        KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM);
        // 初始化算法生成器
        gen.init(KEY_SIZE, random);
        // 生成 AES密钥对象, 也可以直接创建密钥对象: return new SecretKeySpec(key, ALGORITHM);
        return gen.generateKey();
    }
    /**
     * 数据加密: 明文 -> 密文
     */
    public static byte[] encrypt(byte[] plainBytes, byte[] key) throws Exception {
        // 生成密钥对象
        SecretKey secKey = generateKey(key);
        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器(加密模型)
        cipher.init(Cipher.ENCRYPT_MODE, secKey);
        // 加密数据, 返回密文
        byte[] cipherBytes = cipher.doFinal(plainBytes);
        return cipherBytes;
    }
    /**
     * 数据解密: 密文 -> 明文
     */
    public static byte[] decrypt(byte[] cipherBytes, byte[] key) throws Exception {
        // 生成密钥对象
        SecretKey secKey = generateKey(key);
        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器(解密模型)
        cipher.init(Cipher.DECRYPT_MODE, secKey);
        // 解密数据, 返回明文
        byte[] plainBytes = cipher.doFinal(cipherBytes);
        return plainBytes;
    }
    /**
     * 加密文件: 明文输入 -> 密文输出
     */
    public static void encryptFile(File plainIn, File cipherOut, byte[] key) throws Exception {
        aesFile(plainIn, cipherOut, key, true);
    }
    /**
     * 解密文件: 密文输入 -> 明文输出
     */
    public static void decryptFile(File cipherIn, File plainOut, byte[] key) throws Exception {
        aesFile(plainOut, cipherIn, key, false);
    }
    /**
     * AES 加密/解密文件
     */
    private static void aesFile(File plainFile, File cipherFile, byte[] key, boolean isEncrypt) throws Exception {
        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 生成密钥对象
        SecretKey secKey = generateKey(key);
        // 初始化密码器
        cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secKey);
        // 加密/解密数据
        InputStream in = null;
        OutputStream out = null;
        try {
            if (isEncrypt) {
                // 加密: 明文文件为输入, 密文文件为输出
                in = new FileInputStream(plainFile);
                out = new FileOutputStream(cipherFile);
            } else {
                // 解密: 密文文件为输入, 明文文件为输出
                in = new FileInputStream(cipherFile);
                out = new FileOutputStream(plainFile);
            }
            byte[] buf = new byte[1024];
            int len = -1;
            // 循环读取数据 加密/解密
            while ((len = in.read(buf)) != -1) {
                out.write(cipher.update(buf, 0, len));
            }
            out.write(cipher.doFinal());    // 最后需要收尾
            out.flush();
        } finally {
            close(in);
            close(out);
        }
    }
    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }
}

测试验证:

@Test
public void test1() throws Exception {
    String content = "Hello world!";        // 原文内容
    String key = "123456";                  // AES加密/解密用的原始密码
    // 加密数据, 返回密文
    byte[] cipherBytes = AESUtils.encrypt(content.getBytes(), key.getBytes());
    // 解密数据, 返回明文
    byte[] plainBytes = AESUtils.decrypt(cipherBytes, key.getBytes());
    // 输出解密后的明文: "Hello world!"
    System.out.println(new String(plainBytes));
    /*
     * AES 对文件的加密/解密
     */
    // 将 文件demo.jpg 加密后输出到 文件demo.jpg_cipher
    AESUtils.encryptFile(new File("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\resources\\image.png"), new File("demo.jpg_cipher"), key.getBytes());
    // 将 文件demo.jpg_cipher 解密后输出到 文件demo.jpg_plain
    AESUtils.decryptFile(new File("demo.jpg_cipher"), new File("demo.jpg_plain"), key.getBytes());
    // 对比 原文件demo.jpg 和 解密得到的文件demo.jpg_plain 两者的 MD5 将会完全相同
    /**
     * 901d7bf517fb03e9967ce5cb508b47c0
     * 901d7bf517fb03e9967ce5cb508b47c0
     * true
     */
    try {
        FileInputStream file1 = new FileInputStream(new File("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\resources\\image.png"));
        FileInputStream file2 = new FileInputStream(new File("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\demo.jpg_plain"));
        String md51= DigestUtil.md5Hex(file1);
        String md52= DigestUtil.md5Hex(file2);
        System.out.println(md51);
        System.out.println(md52);
        System.out.println(md51.equals(md52));
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
    }
}
工具类方法返回字符串
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
 * AES工具类
 */
public class AesUtil {
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    /**
     * AES加密
     *
     * @param passwd  加密的密钥
     * @param content 需要加密的字符串
     * @return 返回Base64转码后的加密数据
     * @throws Exception
     */
    public static String encrypt(String passwd, String content) throws Exception {
        // 创建密码器
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        byte[] byteContent = content.getBytes("utf-8");
        // 初始化为加密模式的密码器
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(passwd));
        // 加密
        byte[] result = cipher.doFinal(byteContent);
        //通过Base64转码返回
        return Base64.encodeBase64String(result);
    }
    /**
     * AES解密
     *
     * @param passwd    加密的密钥
     * @param encrypted 已加密的密文
     * @return 返回解密后的数据
     * @throws Exception
     */
    public static String decrypt(String passwd, String encrypted) throws Exception {
        //实例化
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        //使用密钥初始化,设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(passwd));
        //执行操作
        byte[] result = cipher.doFinal(Base64.decodeBase64(encrypted));
        return new String(result, "utf-8");
    }
    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
        // javax.crypto.BadPaddingException: Given final block not properly padded解决方案
        // https://www.cnblogs.com/zempty/p/4318902.html - 用此法解决的
        // https://www.cnblogs.com/digdeep/p/5580244.html - 留作参考吧
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed(password.getBytes());
        //AES 要求密钥长度为 128
        kg.init(128, random);
        //生成一个密钥
        SecretKey secretKey = kg.generateKey();
        // 转换为AES专用密钥
        return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
    }
}

测试:

/**
 * 使用工具类{@link AesUtil}
 * @throws Exception
 */
@Test
public void test2() throws Exception {
    String content = "Hello world!";        // 原文内容
    String key = "123456";
    String encrypt = AesUtil.encrypt(key, content);
    //p8QfZnBvaBWALPgvzdBk+Q==
    System.out.println(encrypt);
    //解密 Hello world!
    System.out.println(AesUtil.decrypt(key,encrypt));
}
使用hutool封装的工具类

引入依赖:

<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.10</version>
</dependency>

测试验证:

/**
 * 使用hutool工具类
 * @throws Exception
 */
@Test
public void test3() throws Exception {
    String content = "Hello world!";        // 原文内容
    //key的长度有限制:16位
    String key = "f5k0f5w7f8g4er88";
    /**
     * 初始化加密(默认的AES加密方式)
     */
    SymmetricCrypto crypto = new SymmetricCrypto(SymmetricAlgorithm.AES, key.getBytes());
    //加密
    String encrypt = crypto.encryptHex(content);
    //aaaeea620dfb79c88f40260b20f862c7
    System.out.println(encrypt);
    //解密 Hello world!
    System.out.println(crypto.decrypt(encrypt));
}

hutool封装成工具类:

import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import java.nio.charset.StandardCharsets;
/**
 * AES加密方式算法工具类
 */
public class AesHutoolUtils {
    /**
     * KEY 随机的后续可更改
     */
    private static final byte[] key ="f5k0f7w7f8g4er88".getBytes(StandardCharsets.UTF_8);
    /**
     * 初始化加密(默认的AES加密方式)
     */
    private static final SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);

    /**
     * 加密
     * @param str 加密之前的字符串
     * @return
     */
    public static String encryptHex(String str){
        return aes.encryptHex(str);
    }
    /**
     * 解密
     * @param str 加密后的字符串
     * @return
     */
    public static String decryptStr(String str){
        return aes.decryptStr(str);
    }
}

2)项目中使用

接口参数加密

对于白名单的接口,对参数进行加密,返回给前端,前端传递给加密的参数给后端,如果后端解密失败,则无法访问此接口。一定程度上保证了接口的安全性。

生成对应能访问的链接:加解密使用hutool工具类完成

private static final String url = "http://localhost:8081/shortUrl/public/getById";
/**
 * 后台主动生成外网可以访问的链接,携带加密的参数
 *
 * @param id
 * @return
 */
@GetMapping("/getLinkUrl")
public String getLinkUrl(@RequestParam("id") Long id) {
    ShortUrl shortUrl = shortUrlService.getById(id);
    if (shortUrl == null) {
        throw new RuntimeException("数据不存在");
    }
    //加密id
    String s = AesHutoolUtils.encryptHex(String.valueOf(id));
    StringBuilder builder = new StringBuilder(url).append("?id=" + s);
    return builder.toString();
}

参数解密方法:

/**
 * 根据ID获取
 *
 * @param id
 * @return
 */
@GetMapping("/public/getById")
public ShortUrl publicGetById(@RequestParam("id") String id) {
    //解密
    String s;
    try {
        s = AesHutoolUtils.decryptStr(id);
    } catch (Exception e) {
        log.error("参数非法,无权限访问! id={}", id);
        throw new RuntimeException("参数非法,无权限访问! ");
    }
    ShortUrl shortUrl = shortUrlService.getById(Long.valueOf(s));
    return shortUrl;
}

访问:http://localhost:8081/shortUrl/getLinkUrl?id=1
访问参数id加密:

http://localhost:8081/shortUrl/public/getById?id=03a5a5602fd047d67374c3983d07985c

访问参数加密的链接:结果如下
在这里插入图片描述

如果参数不符合规则:
在这里插入图片描述

3.3 RSA算法

1)什么是RSA算法

RSA加密算法是一种非对称加密算法,所谓非对称,就是指该算法加密和解密使用不同的密钥,即使用加密密钥进行加密、解密密钥进行解密。 在RAS算法中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。

加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,由于无法计算出大数n的欧拉函数phi(N),所以不能根据PK计算出SK。

也就是说,对极大整数做因数分解的难度决定了RSA算法的可靠性。理论上,只要其钥匙的长度n足够长,用RSA加密的信息实际上是不能被解破的。

RSA算法通常是先生成一对RSA密钥,其中之一是保密密钥,由用户保存;另一个为公开密钥,可对外公开。为提高保密强度,RSA密钥至少为500位长,一般推荐使用1024位。这就使加密的计算量很大。为减少计算量,在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要。

**RSA密钥长度随着保密级别提高,增加很快。**下表列出了对同一安全级别所对应的密钥长度。

保密级别对称密钥长度(bit)RSA密钥长度(bit)ECC密钥长度(bit)保密年限
808010241602010
11211220482242030
12812830722562040
19219276803842080
256256153605122120

2)RSA加解密算法原理

RSA加密过程:

RSA的加密过程可以使用一个通式来表达:
在这里插入图片描述

也就是说RSA加密是对明文的E次方后除以N后求余数的过程。从通式可知,只要知道E和N任何人都可以进行RSA加密了,所以说E、N是RSA加密的密钥,也就是说E和N的组合就是公钥,我们用(E,N)来表示公钥:
在这里插入图片描述

不过E和N不并不是随便什么数都可以的,它们都是经过严格的数学计算得出的,关于E和N拥有什么样的要求及其特性后面会讲到。E是加密(Encryption)的首字母,N是数字(Number)的首字母。

RAS解密过程:

RSA的解密同样可以使用一个通式来表达:

在这里插入图片描述

也就是说对密文进行D次方后除以N的余数就是明文,这就是RSA解密过程。知道D和N就能进行解密密文了,所以D和N的组合就是私钥:
在这里插入图片描述

从上述可以看出RSA的加密方式和解密方式是相同的,加密是求“E次方的mod N”;解密是求“D次方的mod N”。此处D是解密(Decryption)的首字母;N是数字(Number)的首字母。

生成密钥对:

既然公钥是(E,N),私钥是(D,N),所以密钥对即为(E,D,N),但密钥对是怎样生成的?步骤如下:

  • 求N
  • 求L(L为中间过程的中间数)
  • 求E
  • 求D
    1)求N:

准备两个互质数p,q。这两个数不能太小,太小则会容易破解,将p乘以q就是N。如果互质数p和q足够大,那么根据目前的计算机技术和其他工具,至今也没能从N分解出p和q。换句话说,只要密钥长度N足够大(一般1024足矣),基本上不可能从公钥信息推出私钥信息。

N = p * q

2)求L:

L 是 p-1 和 q-1的最小公倍数,可用如下表达式表示

L = lcm(p-1,q-1)

3)求E:

E必须满足两个条件:E是一个比1大比L小的数,E和L的最大公约数为1;

用gcd(X,Y)来表示X,Y的最大公约数则E条件如下:

1 < E < L

gcd(E,L)=1

之所以需要E和L的最大公约数为1,是为了保证一定存在解密时需要使用的数D。现在我们已经求出了E和N也就是说我们已经生成了密钥对中的公钥了。

4)求D:

数D是由数E计算出来的,数D必须保证足够大。D、E和L之间必须满足以下关系:

1 < D < L

E*D mod L = 1

只要D满足上述2个条件,则通过E和N进行加密的密文就可以用D和N进行解密。简单地说条件2是为了保证密文解密后的数据就是明文。

现在私钥自然也已经生成了,密钥对也就自然生成了。

小结:

求NN= p * q ;p,q为质数
求LL=lcm(p-1,q-1) ;L为p-1、q-1的最小公倍数
求E1 < E < L,gcd(E,L)=1;E,L最大公约数为1(E和L互质)
求D1 < D < L,E*D mod L = 1

举例如下:

为了计算方便,p q 的值取小一旦,假设:p = 17,q = 19,

则:

(1)求N:N = p * q = 323;

(2)求L:L = lcm(p-1, q-1)= lcm(16,18) = 144,144为16和18对最小公倍数;

(3)求E:1 < E < L ,gcd(E,L)=1,即1 < E < 144,gcd(E,144) = 1,E和144互为质数,E = 5显然满足上述2个条件,故E = 5,此时公钥= (E,N)=(5,323)

(4)求D:求D也必须满足2个条件:1 < D < L,E*D mod L = 1,即1 < D < 144,5 * D mod 144 = 1,显然当D= 29 时满足上述两个条件。1 < 29 < 144,5*29 mod 144 = 145 mod 144 = 1,此时私钥=(D,N)=(29,323)

(5)加密:准备的明文必须是小于N的数,因为加密或者解密都要 mod N,其结果必须小于N。

假设明文 = 123,则 密文=(123的5次方)mod 323=225

(6)解密:明文=(225的29次方)mod 323 =123,所以解密后的明文为123。

3)java代码实现

封装工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 RSAEncrypt {

   private static Map<Integer, String> keyMap = new HashMap<Integer, String>();  //用于封装随机产生的公钥与私钥
   
   public static void main(String[] args) throws Exception {
      /**
       * 随机生成的公钥为:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMKFPRuulH/vll0IL6NdLvHLd3gHSQJgW7uZpClLruEVqKr/YwwF+mQDgnAq1AHaP82XE4JOlKSawNTUJNxtBsu9D/mm27aXEiZprRN5fCLh5nRKDuT+SdgjFNTjdkVkRJeUp/YesaxCazMrdcFQOTYYelAjp28YyMrbzSEwl3cwIDAQAB
       * 随机生成的私钥为:MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIwoU9G66Uf++WXQgvo10u8ct3eAdJAmBbu5mkKUuu4RWoqv9jDAX6ZAOCcCrUAdo/zZcTgk6UpJrA1NQk3G0Gy70P+abbtpcSJmmtE3l8IuHmdEoO5P5J2CMU1ON2RWREl5Sn9h6xrEJrMyt1wVA5Nhh6UCOnbxjIytvNITCXdzAgMBAAECgYAYckREdayQ4TlQ9/CQgejbyg96KY6rQeaIGtR8PoLoWWCIhi6TzEoirlrc+wuK/mCHDso/t7h1O6pl247wD/h0a9+iWfpSNTj4ypg35s4cFPC3l7PHz7ABMTo0HIETKv/J4HkPNixYdMTIm1Vq4mkvcwlEsYbFyIoMObi+XsqraQJBAN1lVPV53Nu7EpZwA/2fH33V1Ep30Xz8HBbgrQZFB2SdR6NpDnd/me+N8C3QJotVxJqcFA5eqBdtL2f9ipy0RBcCQQCiEGxfMuKpf+jmYg/fyQxTajjk5i8h97E0mDGWgUv9NZP7IBZvCDYHHXH32J/8ebJ26IbKK69Vv37e13yBK9UFAkA+T3+62wJzAVK47mvhHMDTPLRUBSb7o0UQl0l7Q0BSbAW2kyHUNiCQEIWxjyzZ+FiuHxFx/egXN86o2O4DLqUfAkAqYAD2I2gAyeEr3Bgqe3ctmGin8UgBqbI7/k94+vXTj17SGuHCxnLaCWjzVUD/0xJG5SXZVvLRiPCzc8SqQbvBAkBrnlJI9vG+f5R/lnRL1GgjWBSJXTa2jffnwPA6kwCgTE8fE8JPV5YRT9lAfO1hj0UAAn1vxB62NYE7mNzlKM43
       * df723820    加密后的字符串为:UJNu11tGDCPFdjg7+P+VEsCzBn0hZ/eI8IznTUMHbhmWTfpLJCgwb3Q8cWv3WvLDGfjMgH4ugM/wrCRQwlIhEmiBo+tYXo7t+njP7IvTExiOfHUXJLWm4aGPa74DED/S0O5apk1J+Kdx6eyHkc5CXKjuKLglxW05/yiUuOiJ+YM=
       * 还原后的字符串为:df723820
       */
      //生成公钥和私钥
      genKeyPair();
      //加密字符串
      String message = "df723820";
      System.out.println("随机生成的公钥为:" + keyMap.get(0));
      System.out.println("随机生成的私钥为:" + keyMap.get(1));
      String messageEn = encrypt(message,keyMap.get(0));
      System.out.println(message + "\t加密后的字符串为:" + messageEn);
      String messageDe = decrypt(messageEn,keyMap.get(1));
      System.out.println("还原后的字符串为:" + messageDe);
   }
   /** 
    * 随机生成密钥对 
    * @throws NoSuchAlgorithmException 
    */  
   public static void genKeyPair() throws NoSuchAlgorithmException {  
      // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象  
      KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");  
      // 初始化密钥对生成器,密钥大小为96-1024位  
      keyPairGen.initialize(1024,new SecureRandom());  
      // 生成一个密钥对,保存在keyPair中  
      KeyPair keyPair = keyPairGen.generateKeyPair();  
      RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥  
      RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥  
      String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));  
      // 得到私钥字符串  
      String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));  
      // 将公钥和私钥保存到Map
      keyMap.put(0,publicKeyString);  //0表示公钥
      keyMap.put(1,privateKeyString);  //1表示私钥
   }  
   /** 
    * RSA公钥加密 
    *  
    * @param str 
    *            加密字符串
    * @param publicKey 
    *            公钥 
    * @return 密文 
    * @throws Exception 
    *             加密过程中的异常信息 
    */  
   public static String encrypt( String str, String publicKey ) throws Exception{
      //base64编码的公钥
      byte[] decoded = Base64.decodeBase64(publicKey);
      RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
      //RSA加密
      Cipher cipher = Cipher.getInstance("RSA");
      cipher.init(Cipher.ENCRYPT_MODE, pubKey);
      String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
      return outStr;
   }
   /** 
    * RSA私钥解密
    *  
    * @param str 
    *            加密字符串
    * @param privateKey 
    *            私钥 
    * @return 铭文
    * @throws Exception 
    *             解密过程中的异常信息 
    */  
   public static String decrypt(String str, String privateKey) throws Exception{
      //64位解码加密后的字符串
      byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
      //base64编码的私钥
      byte[] decoded = Base64.decodeBase64(privateKey);  
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));  
      //RSA解密
      Cipher cipher = Cipher.getInstance("RSA");
      cipher.init(Cipher.DECRYPT_MODE, priKey);
      String outStr = new String(cipher.doFinal(inputByte));
      return outStr;
   }
}

在程序中,我们首先利用genKeyPair()函数生成公钥和私钥并将其保存到Map集合中。然后,基于产生的公钥对明文进行加密。针对已经已经加密的密文,我们再次使用私钥解密,得到明文。

使用hutool工具类

引入hutool工具pom文件依赖:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.7</version>
</dependency>

代码实现:

package com.slfx.open.api.utils;
import java.util.Base64;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
/**使用hutool工具类中的rsa工具*/
public class RSAHutoolUtil {
   /**
    * 获取公钥私钥密钥对
    * */
   static String getKeyPair(RSA rsa){
      StringBuilder rtnStb=new StringBuilder();
      //获得私钥
//    System.out.println(rsa.getPrivateKey());
      rtnStb.append("privateKey: ");
      rtnStb.append(rsa.getPrivateKeyBase64());
      rtnStb.append("\n");
      //获得公钥
//    System.out.println(rsa.getPublicKey());
      rtnStb.append("publicKey: ");
      rtnStb.append(rsa.getPublicKeyBase64());
      return rtnStb.toString();
   }
   
   //加密
   public static String getEncryptString(String str, RSA rsa){
      byte[] encrypt = rsa.encrypt(StrUtil.bytes(str, CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
      return Base64.getEncoder().encodeToString(encrypt);
   }
   
   //解密
   public static String getDecryptString(String str, RSA rsa){
      byte[] aByte = Base64.getDecoder().decode(str);
      byte[] decrypt = rsa.decrypt(aByte, KeyType.PrivateKey);
      return new String(decrypt, CharsetUtil.CHARSET_UTF_8);
   }
   
   public static void main(String[] args) {
      //System.out.println(getKeyPair(new RSA()));
      String priKey="MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKy5YfzOCX9IjdKemQGaVg0gfZcM0RJ5WcPfWorIw9Rpd0ZPkjMEW8boHbq7LHRgclGWSJe8je4k+W7hh51IjS0g0Dxg0ulIqVEtHrY0KxVJhtWN0ygPYq+Yyu3iseX7KxP2/psOojggr2KPJ6csJvQiVovXzyYY7heueuXmEQkRAgMBAAECgYA1FwY3Xl0s8nAlPPpqs7Wfc00nEJlyxDoDKrP51Jvcvk4bHnohlEVhbhc1PjL70mlRCpBlk19Mw7CnlErK3XIG2xVarhWCNnf9tml8/vQd871SU3qMngtwmpI5b26bj+CW3osmmFp2fh6ChfGAel74OCvPP8hth4cHMDl304oNAQJBAOhb2mNJzqzakQVx18IN2dV2gtiqS1gNGff6gvxepMwd2jq1KMQp5uD+aLwHf5DwLEOpJpV2wPKJDuM3AW/6n4UCQQC+TECZfeqsJe24+A9XqpcO3M1PH+oG8IsugNy/Lre/eBgyMjNqYVrmXP1tnBupdFvOxBMq3TUpyyAV4ZQufUsdAkEAlKEUq8gtdLIGa8YrbgywF3RMNE2SDkurHc1XdhkQLyS1UDJLVlc7QRZbZlMhjVOj7M/JR3gD6eAz7rFjsP5OhQJAGWa8SMDSDmCRttsF0UHoIAfBMAqmhn6dVXvF+48U3+377NdenZiZNZ68BWGNH7V0e8kYdeRJMGb4xXLzALGg0QJBAKy+RPYOO1lfaoYXm0EjzXuleONG4Yw4Mya+rEuax0ztgkU8d0of8cJ5SrnBCeGmHjSfCnCPY9STVir1OPxWauM=";
      String pubKey="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsuWH8zgl/SI3SnpkBmlYNIH2XDNESeVnD31qKyMPUaXdGT5IzBFvG6B26uyx0YHJRlkiXvI3uJPlu4YedSI0tINA8YNLpSKlRLR62NCsVSYbVjdMoD2KvmMrt4rHl+ysT9v6bDqI4IK9ijyenLCb0IlaL188mGO4Xrnrl5hEJEQIDAQAB";
      RSA rsa = new RSA(priKey, pubKey);
      String plainTxt="123";
      String encryptTxt=getEncryptString(plainTxt, rsa);
      //WAogiaXAmVDtSQ6wBDcHNc/k8KzFC/pM4wJgIDENogx8HQWoqzRk5rIJQGe11vHrTqZYEc1cE64DxCKYVgVqNc9zFePkuOncNznphznvkx0zmRm5Y/uicdmGUOPryuDI6Iikji6/XMZiD/kF81ZaowzUfy2GQyHMQ6rLiVFKXJg=
      System.out.println(encryptTxt);
      String decryptTxt=getDecryptString(encryptTxt, rsa);
      //123
      System.out.println(decryptTxt);
   }
}

4)项目中的使用场景

接口之间进行加签验签操作

签名工具类:

public class RsaUtils {
    /**
     * 秘钥对算法名称
     */
    private static final String ALGORITHM = "RSA";
    /**
     * 密钥长度
     */
    private static final int KEY_SIZE = 1024;
    /**
     * 签名算法
     */
    private static final String SIGNATURE_ALGORITHM = "Sha1WithRSA";
    /**
     * 随机生成 RSA 密钥对(包含公钥和私钥)
     */
    public static String[] generateKeyPair() throws Exception {
        // 获取指定算法的密钥对生成器
        KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);
        // 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
        gen.initialize(KEY_SIZE);
        // 随机生成一对密钥(包含公钥和私钥)
        /** 生成一个密钥对,保存在keyPair中  */
        KeyPair keyPair = gen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
        /** 得到私钥字符串 */
        String privateKeyString = new String(Base64.getEncoder().encode((privateKey.getEncoded())));
        return new String[]{publicKeyString, privateKeyString};
    }
    /**
     * 私钥签名(数据): 用私钥对指定字节数组数据进行签名, 返回签名信息
     */
    public static byte[] sign(byte[] data, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用私钥初始化签名工具
        sign.initSign(priKey);
        // 添加要签名的数据
        sign.update(data);
        // 计算签名结果(签名信息)
        byte[] signInfo = sign.sign();
        return signInfo;
    }
    /**
     * 公钥验签(数据): 用公钥校验指定数据的签名是否来自对应的私钥
     */
    public static boolean verify(byte[] data, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用公钥初始化签名工具
        sign.initVerify(pubKey);
        // 添加要校验的数据
        sign.update(data);
        // 校验数据的签名信息是否正确,
        // 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,
        // 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,
        // 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者(所有者)
        return sign.verify(signInfo);
    }
    /**
     * 私钥签名(文件): 用私钥对文件进行签名, 返回签名信息
     */
    public static byte[] signFile(File file, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用私钥初始化签名工具
        sign.initSign(priKey);
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            byte[] buf = new byte[1024];
            int len = -1;
            while ((len = in.read(buf)) != -1) {
                // 添加要签名的数据
                sign.update(buf, 0, len);
            }
        } finally {
            close(in);
        }
        // 计算并返回签名结果(签名信息)
        return sign.sign();
    }
    /**
     * 公钥验签(文件): 用公钥校验指定文件的签名是否来自对应的私钥
     */
    public static boolean verifyFile(File file, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用公钥初始化签名工具
        sign.initVerify(pubKey);
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            byte[] buf = new byte[1024];
            int len = -1;
            while ((len = in.read(buf)) != -1) {
                // 添加要校验的数据
                sign.update(buf, 0, len);
            }
        } finally {
            close(in);
        }
        // 校验签名
        return sign.verify(signInfo);
    }
    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }
    public static PublicKey getPublicKeyByStr(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] publicBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        return pubKey;
    }

    public static PrivateKey getPrivateKeyByStr(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] priBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(priBytes);
        KeyFactory priKeyFactory = KeyFactory.getInstance("RSA");
        PrivateKey priKey = priKeyFactory.generatePrivate(priKeySpec);
        return priKey;
    }

    /**
     * 签名方法
     *
     * @param appId       渠道内应用id
     * @param url         请求路径url
     * @param requestBody json参数请求体,无则传null
     * @param timeStamp   时间戳
     * @param privateKey  私钥
     * @return
     */
    public static String sign(String appId, String url, String requestBody, String timeStamp, String privateKey) throws Exception {
        //校验参数
        if (StringUtils.isBlank(appId)) {
            throw new CommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);
        }
        if (StringUtils.isBlank(url)) {
            throw new CommonException(ErrorCodeEnum.ERROR_URL_IS_NOT_EMPTY);
        }
        if (StringUtils.isBlank(timeStamp)) {
            throw new CommonException(ErrorCodeEnum.ERROR_TIME_STAMP_NOT_EMPTY);
        }
        if (StringUtils.isBlank(privateKey)) {
            throw new CommonException(ErrorCodeEnum.ERROR_PRIVATE_KEY_NOT_EMPTY);
        }
        TreeMap<String, String> resultMap = new TreeMap<String, String>();
        resultMap = urlSplit(url);
//        resultMap.put("appId",appId);
//        resultMap.put("timeStamp",timeStamp);
        //拼接请求body
        if (StringUtils.isNotBlank(requestBody)) {
            //将String转换成时间戳
            resultMap.put("bizContent", requestBody);
        }
        String dataStr = Common.forInSpiry(resultMap, appId, timeStamp);
        if (!StringUtils.isBlank(dataStr)) {
            PrivateKey priKey = getPrivateKeyByStr(privateKey);
            byte[] signInfo = sign(dataStr.getBytes(), priKey);
            dataStr = Base64.getEncoder().encodeToString(signInfo);
        }
        return dataStr;
    }
    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
//        String[] keyPair = generateKeyPair();
//        System.out.println(keyPair[0]);
//        System.out.println(keyPair[1]);
        //TODO 1、参数加签示例
        String appId = "slfx";
//        String url = "http://localhost:7091/ums/v1/user/app/findUserAppList?projectId=103672";
        String url = "http://localhost:8083/client/shortUrl/getById?id=1";
        String requestBody = "";
        String publicKey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLat0TW2i7x8tLwa7ekd7VZPI9bbivr3JvJ5D+jroxYh8KYziQA8GlfeTZ9obdlIkbWpCHOE4nL8V1iBscNEgbXAJqgZ5d12RXPU67UOV1P1I4wLPm8HfEFIqogjihlQ4uRlbpS5e2XFWm8X5OXV3yp4OyiVOp1AhA2Vczm9VVbwIDAQAB";
        String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMtq3RNbaLvHy0vBrt6R3tVk8j1tuK+vcm8nkP6OujFiHwpjOJADwaV95Nn2ht2UiRtakIc4TicvxXWIGxw0SBtcAmqBnl3XZFc9TrtQ5XU/UjjAs+bwd8QUiqiCOKGVDi5GVulLl7ZcVabxfk5dXfKng7KJU6nUCEDZVzOb1VVvAgMBAAECgYAcXob+kB0HM1IS+qwctlNRh4aRMqAzQFDBV89M8TLeeETD+hqFW0DCh9BG5a7hcDm+6t6FPUEJDgyH27eMM25qwri9Uu9sXYsvx+wzAEgh4iULhQBNV2BxB/JvsDMSzdR9iuPrkwP1wXQsgDcT66c2gPKkjXFAtZWPmqoEdwHdIQJBAOrTd1MVbe6DXydRCGmBFAqtbHCsk64lE3IY5NySvjjOMI9j65CFzr9Leek/UtnhKUnPHOnzq10wf7R/SnczpdUCQQDdwmMAd7QOT59hDhYKmGpUJ9UbJzCJhv+4aw1ngNUbSheAAtTBeA8fSEzziAaHUicihNDGUAZW2WbWX5wIyBwzAkBV4UCighPSRvllx5zr+Oj7frFKA9N+vNB/ydrpUEGBROc9ia19DXKvQ0syopYKWO5gr/5DEXp6emI6ANObBKiBAkEAh8kcF9A0J0Mn4yTd6DT1fnw5Z/vyI3PPJ1wdhBAF7UTL6O1S2vgpwkziGYhj/v4VynQq5hNgHlPWPVOj1li5swJBAJw+YgDP+25zZG6HJUWx6fhuSO0HGcIMFymt5MS6tcD2Qv5s2HF0JLUp4vzDUtumU3v+d5xtI39HnB1n3jrEMg4=";
        String timeStamp = "1663118969272";
        String sign = sign(appId, url, requestBody, timeStamp, privateKey);
        //g3b3ngji0h0s2n8bfFjODPO0FiT+UtjXwLt8vNOAX0CSYSWkxyVGh1hGtKrH5gCjSNs7ArmI49Tx/6LP70gAzekUSRatHYsQQJUWy741Fk4XWpV7CrcNEehit9Q1Ii6CmbO+PYk2hFE+U4wUEx/A8joKYy4kA3+pI2nGtq2zv90=
        System.out.println(sign);
        //校验签名:获取签名的数据
        Map<String,String> resultMap=new HashMap<>();
        resultMap.put("id","1");
        String dataStr = Common.forInSpiry(resultMap,appId,timeStamp);
        byte[] decode = Base64.getDecoder().decode(sign);
        //校验签名
        boolean verify = RsaUtils.verify(dataStr.getBytes(), decode, getPublicKeyByStr(publicKey));
        System.out.println("校验签名值:"+verify);
//        //TODO 2、密钥对生成示例
        String[] keyPair = genKeyPair();
        System.out.println("publicKey:"+keyPair[0]);
        System.out.println("privateKey:"+keyPair[1]);
    }
    /**
     * 截取url上的请求参数
     *
     * @param strURL
     * @return
     */
    private static String truncateUrlPage(String strURL) {
        String strAllParam = null;
        String[] arrSplit = null;
        //strURL=strURL.trim().toLowerCase();
        strURL = strURL.trim();
        arrSplit = strURL.split("[?]");
        if (strURL.length() > 1) {
            if (arrSplit.length > 1) {
                for (int i = 1; i < arrSplit.length; i++) {
                    strAllParam = arrSplit[i];
                }
            }
        }
        return strAllParam;
    }
    /**
     * 将url请求路径上的参数转成map型
     *
     * @param URL
     * @return
     */
    public static TreeMap<String, String> urlSplit(String URL) {
        TreeMap<String, String> mapRequest = new TreeMap<String, String>();
        String[] arrSplit = null;
        String strUrlParam = truncateUrlPage(URL);
        if (strUrlParam == null) {
            return mapRequest;
        }
        arrSplit = strUrlParam.split("[&]");
        for (String strSplit : arrSplit) {
            String[] arrSplitEqual = null;
            arrSplitEqual = strSplit.split("[=]");
            //解析出键值
            if (arrSplitEqual.length > 1) {
                //正确解析
                mapRequest.put(arrSplitEqual[0], arrSplitEqual[1]);
            } else {
                if (arrSplitEqual[0] != "") {
                    //只有参数没有值,不加入
                    mapRequest.put(arrSplitEqual[0], "");
                }
            }
        }
        return mapRequest;
    }
    /**
     * 随机生成密钥对
     *
     * @return 公钥,私钥
     * @throws NoSuchAlgorithmException
     */
    public static String[] genKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        /**  初始化密钥对生成器,密钥大小为96-1024位  */
        keyPairGen.initialize(1024, new SecureRandom());
        /** 生成一个密钥对,保存在keyPair中  */
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        /** 得到私钥字符串 */
        String privateKeyString = Base64.getEncoder().encodeToString((privateKey.getEncoded()));
        //keyMap.put(0,publicKeyString);  //0表示公钥
        //keyMap.put(1,privateKeyString);  //1表示私钥
        return new String[]{publicKeyString, privateKeyString};
    }
}

客户端加签:

@Component
public class HttpCallUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HttpCallUtil.class);
    @Resource
    OpenApiConfig openApiConfig;
    /**
     * get方法调用加签
     *
     * @param reqUrl
     * @param params
     * @param headers
     * @param flag
     * @return
     */
    public  String okHttpGet(String reqUrl, Map<String, Object> params, Map<String, String> headers, boolean flag) {
        OkHttpClient OKHTTP_CLIENT = new OkHttpClient();
        StringBuilder stringBuilder = new StringBuilder();
        //处理参数
        if (params != null && !params.isEmpty()) {
            params.keySet().forEach(res -> {
                if (StringUtils.isNotBlank(stringBuilder) || flag) {
                    stringBuilder.append("&");
                } else {
                    stringBuilder.append("?");
                }
                try {
                    stringBuilder.append(String.format("%s=%s", res, URLEncoder.encode(String.valueOf(params.get(res)), "UTF-8")));
                } catch (UnsupportedEncodingException e) {
                    LOGGER.error("系统异常!", e);
                }
            });
        }
        // 拼接参数
        String requestUrl = reqUrl + stringBuilder;
        Request.Builder reqUrlBuilder = new Request.Builder().url(requestUrl);
        if (CollectionUtil.isNotEmpty(headers)) {
            headers.entrySet().forEach(header -> {
                reqUrlBuilder.addHeader(header.getKey(), header.getValue());
            });
        }
        // 发送请求
        Request request = reqUrlBuilder.get().build();
        Response response = null;
        try {
            LOGGER.info("url:{},headers:{},调用参数:{}", requestUrl, headers, request);
            response = OKHTTP_CLIENT.newCall(request).execute();
            String result = response.body().string();
            LOGGER.info("url:{},返回结果:{}", requestUrl, result);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("HTTP GET同步请求失败 URL:" + reqUrl, e);
        } finally {
            response.close();
        }
    }
    /**
     * post方法调用加签
     *
     * @param reqUrl
     * @param params
     * @param contentType
     * @param headers
     * @return
     */
    public  String okHttpPost(String reqUrl, String params, String contentType, Map<String, String> headers) {
        OkHttpClient client = new OkHttpClient().newBuilder()
                .connectTimeout(80, TimeUnit.SECONDS)
                .addInterceptor(new RetryIntercepter(3))
                .readTimeout(80, TimeUnit.SECONDS).writeTimeout(80, TimeUnit.SECONDS)
                .build();
        MediaType mediaType = MediaType.parse(contentType);
        RequestBody body = RequestBody.create(mediaType, params);
        Request.Builder reqUrlBuilder = new Request.Builder().url(reqUrl);
        if (CollectionUtil.isNotEmpty(headers)) {
            headers.entrySet().forEach(header -> {
                reqUrlBuilder.addHeader(header.getKey(), header.getValue());
            });
        }
        Request request = reqUrlBuilder.post(body).build();
        try {
            LOGGER.info("url:{},headers:{},调用参数:{}", reqUrl, headers, body);
            Response response = client.newCall(request).execute();
            String result = response.body().string();
            LOGGER.info("url:{},返回结果:{}", reqUrl, result);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("HTTP POST同步请求失败 URL:" + reqUrl, e);
        }
    }

    /**
     * post发送消息
     *
     * @param url
     * @param postParams
     * @return
     * @throws Exception
     */
    public  String sendOpenApiPost(String url, String postParams) throws Exception {
        String timeStamp = String.valueOf(System.currentTimeMillis());
        //加签
        String sign = RsaUtils.sign(openApiConfig.getAppId(), url, postParams, timeStamp, openApiConfig.getPrivateKey());
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("appId",openApiConfig.getAppId());
        headerMap.put("timeStamp", timeStamp);
        headerMap.put("sign", sign);
        return okHttpPost(url, postParams, "application/json", headerMap);
    }

    /**
     * get发送消息
     *
     * @param url 调用的url
     * @param params 调用的参数
     * @return
     * @throws Exception
     */
    public  String sendOpenApiGet(String url, Map<String, Object> params) throws Exception {
        String timeStamp = String.valueOf(System.currentTimeMillis());
        //加签url处理
        String sign = RsaUtils.sign(openApiConfig.getAppId(), url, null, timeStamp, openApiConfig.getPrivateKey());
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("appId", openApiConfig.getAppId());
        headerMap.put("timeStamp", timeStamp);
        headerMap.put("sign", sign);
        return okHttpGet(url, params, headerMap, false);
    }
}

拦截器验签:

@Component
@Slf4j
public class ApiRequestInterceptor extends HandlerInterceptorAdapter {
    @Resource
    private AppSecretService appSecretService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle) throws Exception {
        //获取请求头参数
        Map<String, String> headerMap = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            headerMap.put(name, request.getHeader(name));
        }
        //获取到appId、timestamp、sign,请求头参数上的字段名称不区分大小写
        String appId = headerMap.get("appid");
        String timeStamp = headerMap.get("timestamp");
        String sign = headerMap.get("sign");
        log.info("请求url:{},appid:{},timestamp:{},sign:{}", request.getRequestURI(), appId, timeStamp, sign);
        //校验参数不能为空
        if (StringUtils.isBlank(appId)) {
            throw new CommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);
        }
        if (StringUtils.isBlank(timeStamp)) {
            throw new CommonException(ErrorCodeEnum.ERROR_TIME_STAMP_NOT_EMPTY);
        }
        if (StringUtils.isBlank(sign)) {
            throw new CommonException(ErrorCodeEnum.ERROR_SIGN_IS_NOT_EMPTY);
        }
        //校验appId
        AppSecret appSecret = appSecretService.getOne(new LambdaQueryWrapper<AppSecret>()
                .eq(AppSecret::getAppId, headerMap.get("appid"))
                .last("limit 1"));
        if (Objects.isNull(appSecret)) {
            throw new CommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);
        }
        //时间戳校验,防dos攻击
        Long time;
        try {
            time = Long.parseLong(timeStamp);
        } catch (NumberFormatException e) {
            setResponse(response, "时间戳格式异常");
            return false;
        }
        long currentTimeMillis = System.currentTimeMillis();
        log.info("time:{},currentTimeMillis:{}", time, currentTimeMillis);
        //(6000000/1000)s=6000s /60 =100minute
        if (Math.abs(time - currentTimeMillis) > 6000000) {
            setResponse(response, "时间戳过期");
            return false;
        }
        String requestURI = request.getRequestURI();
        String serverName = request.getServerName();//主机地址
        int port = request.getServerPort();//端口号
        String path = request.getContextPath();//带斜杠的项目名
        String queryString = request.getQueryString();//获取请求参数字符串
        //拼接对应的url
        String url = serverName + ":" + port + path + requestURI + queryString;
        //获取请求参数map
        SortedMap<String, String> sortMap = new TreeMap<>();
        //先判断是否是post请求
        String contentType = request.getContentType();
        try {
            if (contentType != null && contentType.indexOf("json") != -1 && "post".equalsIgnoreCase(request.getMethod())) {
                //获取到请求参数 request是经过包装的MyHttpServletRequestWrapper
                String json = IOUtils.toString(request.getInputStream(), "utf-8");
                sortMap.put("bizContent", json);
            } else {
                //获取到get请求上的参数
                Enumeration<String> params = request.getParameterNames();
                while (params.hasMoreElements()) {
                    String name = params.nextElement();
                    sortMap.put(name, request.getParameter(name));
                }
            }
        } catch (Exception e) {
            log.error("参数解析失败,参数不能为空", e);
            setResponse(response, "参数解析失败,参数不能为空");
            return false;
        }
        log.debug("第三方请求参数:{},签名signature:{}", JSON.toJSONString(sortMap), sign);
        if (sortMap == null || sortMap.size() == 0) {
            setResponse(response, "请求参数为空");
            return false;
        }
        String dataStr = Common.forInSpiry(sortMap, appId, timeStamp);
        byte[] decode = Base64.getDecoder().decode(sign);
        //校验签名
        boolean verify = RsaUtils.verify(dataStr.getBytes(), decode, RsaUtils.getPublicKeyByStr(appSecret.getPublicKey()));
        log.info("请求url:{},appid:{},timestamp:{},sign:{},签名校验结果:{}", request.getRequestURI(), appId, timeStamp, sign, verify);
        if (!verify) {
            log.error("平台签名错误,sign:{}", sign);
            setResponse(response, "平台签名错误");
            return false;
        }
        return super.preHandle(request, response, handle);
    }

    @SuppressWarnings("all")
    private void setResponse(HttpServletResponse response, String errorMessage) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println(JSON.toJSONString(errorMessage));
        writer.flush();
        writer.close();
    }
}
登陆使用公钥对密码进行加密传输

3.4 SHA1算法

1)SHA1加密算法介绍

SHA是一种数据加密算法,该算法经过加密专家多年来的发展和改进已日益完善,现在已成为公认的最安全的散列算法之一,并被广泛使用。该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。散列函数值可以说是对明文的一种“指纹”或是“摘要”,所以对散列值的数字签名就可以视为对此明文的数字签名。

安全散列算法SHA(Secure Hash Algorithm,SHA)是美国国家标准技术研究所发布的国家标准FIPS PUB 180,最新的标准已经于2008年更新到FIPS PUB 180-3。其中规定了SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512这几种单向散列算法。SHA-1,SHA-224和SHA-256适用于长度不超过264二进制位的消息。SHA-384和SHA-512适用于长度不超过2128二进制位的消息。

2)算法原理

SHA-1是一种数据加密算法,该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。

单向散列函数的安全性在于其产生散列值的操作过程具有较强的单向性。如果在输入序列中嵌入密码,那么任何人在不知道密码的情况下都不能产生正确的散列值,从而保证了其安全性。SHA将输入流按照每块512位(64个字节)进行分块,并产生20个字节的被称为信息认证代码或信息摘要的输出。

该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512 位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。

通过散列算法可实现数字签名实现,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。

3)java代码实现

import org.apache.commons.codec.digest.DigestUtils;
import java.security.MessageDigest;

public class SHA1Utils {

    public static String shaEncode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
    public static void main(String args[]) throws Exception {
        String str = "shenlongfeixian";
        System.out.println("原始:" + str);
        //e94777a3472b5e35562d3cbfd112556b95c80e91
        System.out.println("SHA后:" + shaEncode(str));
        //e94777a3472b5e35562d3cbfd112556b95c80e91
        System.out.println("common包工具类:" + DigestUtils.sha1Hex(str));
    }
}

SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
这些变体除了生成摘要的长度 、循环运行的次数等一些微小差异外,算法的基本结构是一致的。

使用SHA256:

利用Java自带的实现加密代码实现:

import org.apache.commons.codec.binary.Hex;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SHA256Utils {
    /**
     * 利用java原生的摘要实现SHA256加密
     *
     * @param str 加密后的报文
     * @return
     */
    public static String getSHA256StrJava(String str) {
        MessageDigest messageDigest;
        String encodeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes("UTF-8"));
            encodeStr = byte2Hex(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return encodeStr;
    }
    /**
     * 将byte转为16进制
     *
     * @param bytes
     * @return
     */
    private static String byte2Hex(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i = 0; i < bytes.length; i++) {
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length() == 1) {
                //1得到一位的进行补0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }
    public static void main(String args[]) throws Exception {
        String str = "shenlongfeixian";
        System.out.println("原始:" + str);
        //e961dffd040490157690aa2ac18e174236e180d9910cde27d344593268cf5778
        System.out.println("SHA后:" + getSHA256StrJava(str));
        //使用apache common包中的类实现
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
        String s = Hex.encodeHexString(hash);
        //e961dffd040490157690aa2ac18e174236e180d9910cde27d344593268cf5778
        System.out.println("common包工具类:" + s);
    }
}

4)项目中的使用场景

接口之间进行签名操作

签名生成工具类:

import lombok.extern.slf4j.Slf4j;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/* 
 * 
'============================================================================ 
'api说明: 
'createSHA1Sign创建签名SHA1 
'getSha1()Sha1签名 
'============================================================================ 
'*/
@Slf4j
public class SHA1Util {
    //微信公众号开发的获取时间戳
    public static String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }
    //创建签名SHA1  
    public static String createSHA1Sign(SortedMap<String, String> signParams) {
        StringBuffer sb = new StringBuffer();
        Set<Entry<String, String>> es = signParams.entrySet();
        Iterator<Entry<String, String>> it = es.iterator();
        while (it.hasNext()) {
            Entry<String, String> entry = (Entry<String, String>) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            sb.append(k + "=" + v + "&");
            //要采用URLENCODER的原始值!  
        }
        String params = sb.substring(0, sb.lastIndexOf("&"));
        log.info(params);
        return getSha1(params);
    }
    //Sha1签名  
    public static String getSha1(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));
            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            log.error("getSha1 error", e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 规范化转换
     *
     * @param sortedParamMap
     * @return
     */
    public static String canonicalize(SortedMap<String, String> sortedParamMap) {
        if (sortedParamMap.isEmpty()) {
            return "";
        }
        StringBuffer buffer = new StringBuffer();
        Iterator<Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
        while (iter.hasNext()) {
            Entry<String, String> kvpair = iter.next();
            buffer.append(percentEncodeRfc3986(kvpair.getKey()));
            buffer.append("=");
            buffer.append(kvpair.getValue());
            if (iter.hasNext()) {
                buffer.append("&");
            }
        }
        String canonical = buffer.toString();
        return canonical;
    }
    /**
     * Rfc3986</br>
     * 此处建议使用spring的encodeUri方法
     * http://docs.spring.io/spring/docs/4.0.x/javadoc-api/org/springframework/
     * web/util/UriUtils.html
     *
     * @param s
     * @return
     */
    private static String percentEncodeRfc3986(String s) {
        String out;
        try {
            out = URLEncoder.encode(s, "utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            out = s;
        }
        return out;
    }
    public static void main(String[] args) {
//     String str = "appId=8b8e9dce-0ade-4a38-8cb4-8acbc3726591";
//     String aa = str + "35390e1809b948cba5c2d6d73218e75f";
//     System.out.println(getSha1(aa));
        SortedMap<String, String> map = new TreeMap<String, String>();
//     String currentTimeStr = System.currentTimeMillis() + "";
//     System.out.println(currentTimeStr);
//     map.put("opfid", "8b8e9dce-0ade-4a38-8cb4-8acbc3726591");
//     map.put("nonce", "fsdhfa");
//     map.put("timestamp", currentTimeStr);
//     map.put("secret", "35390e1809b948cba5c2d6d73218e75f");
//     System.out.println(createSHA1Sign(map));
        //nonce=rabbit&timestamp=1531207831833&uid=356546565756756&opfid=95668370284249012219&appid=fa478580-e5d8-485d-ac75-f55b9a68c8c8&enterpriseid=be_A2FfURmb5&signature=2284cf811e990315a16b50d5384142bb07c48725
        map.put("opfid", "95668370284249012219");
        map.put("enterpriseid", "55306108c51369f023cf59b2");
        map.put("appid", "fa478580-e5d8-485d-ac75-f55b9a68c8c8");
        map.put("uid", "356546565756756");
        map.put("nonce", "rabbit");
        map.put("timestamp", "1531207831833");
        map.put("secret", "asdfsdafsdafsdafsd");
//     map.put("groupby", "dt");
//     map.put("page", "1");
//     map.put("pageSize", "10");
        //b1d6fce5dbc0f73f719fa304d51ddafb464ce20e
        System.out.println(createSHA1Sign(map));
    }
}  

在拦截器中处理签名的验证:

@Component
@Slf4j
public class ApiRequestInterceptor extends HandlerInterceptorAdapter{
    @Autowired
    private OpenPlatformUserService userService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handle) throws Exception {
        //获取请求参数map
        SortedMap<String, String> sortMap = new TreeMap<>();
        String signature = "";
        String timestamp = "";
        //先判断是否是post请求
        String contentType = request.getContentType();
        try {
            if (contentType != null && contentType.indexOf("json") != -1 && "post".equalsIgnoreCase(request.getMethod())) {
                String json = IOUtils.toString(request.getInputStream(),"utf-8");
                LinkedHashMap<String, Object> prameterMap = JSON.parseObject(json,LinkedHashMap.class);
                Boolean isArray =false;
                int indexOf =0;
                String arrKey="" ;
                for (Map.Entry<String, Object> entry : prameterMap.entrySet()) {
                   String name = entry.getKey();
                   Object valueObj = entry.getValue();
                   
                   if(Objects.equal(name, "signature")){
                        signature = valueObj.toString(); 
                        continue;
                    }
                   if(Objects.equal(name, "belong")){
                        continue;
                    }
                   if (Objects.equal(name, "timestamp")) {
                        timestamp = valueObj.toString();
                    }
                    if(isArray){
                        String substring = json.substring(indexOf, json.indexOf(entry.getKey()));
                        sortMap.put(arrKey, substring.substring(substring.indexOf("[") ,substring.lastIndexOf("]")+1));
                        isArray =false;
                    }
                 if (valueObj instanceof ArrayList ||valueObj instanceof JSONArray ||valueObj instanceof JSONObject || valueObj instanceof Map) {
                    //复杂对象使用json字符串加密
                        indexOf = json.indexOf(entry.getKey());
                        arrKey=entry.getKey();
                        isArray= true;
                 } else {
                    sortMap.put(entry.getKey(), valueObj.toString());
                 }
                }
                if(isArray){
                    String substring = json.substring(indexOf);
                    sortMap.put(arrKey, substring.substring(substring.indexOf("[") ,substring.lastIndexOf("]")+1));
                }
                Enumeration<String> params = request.getParameterNames();
                while(params.hasMoreElements()){
                    String name = params.nextElement();
                    if(Objects.equal(name, "signature")){
                        signature = request.getParameter(name);
                        continue;
                    }
                    if(Objects.equal(name.trim(), "belong")){
                        request.setAttribute(name.trim(),request.getParameter(name));
                        continue;
                    }
                    if (Objects.equal(name, "timestamp")) {
                        timestamp = request.getParameter(name);
                    }
                    sortMap.put(name, request.getParameter(name));
                }
            }else {
                Enumeration<String> params = request.getParameterNames();
                while(params.hasMoreElements()){
                    String name = params.nextElement();
                    if(Objects.equal(name, "signature")){
                        signature = request.getParameter(name);
                        continue;
                    }
                    if(Objects.equal(name.trim(), "belong")){
                        request.setAttribute(name.trim(),request.getParameter(name));
                        continue;
                    }
                    if (Objects.equal(name, "timestamp")) {
                        timestamp = request.getParameter(name);
                    }
                    sortMap.put(name, request.getParameter(name));
                }
            }
        } catch (Exception e) {
            log.error("参数解析失败,参数不能为空", e);
            setResponse(response,R.fail(ErrorCode.PARAM_NULL));
            return false;
        }
        
        log.debug("第三方请求参数:{},签名signature:{}", JSON.toJSONString(sortMap),signature);
        if (sortMap == null || sortMap.size() == 0) {
            setResponse(response,R.fail(ErrorCode.PARAM_NULL));
            return false;
        }
        String apiKey = sortMap.get("apiKey");
        //获取对接平台厂商ID
        String mftId = sortMap.get("mftId");
        //校验mftId
        if(StringUtils.isNotBlank(mftId)){
            OpenPlatformUser platformUser = userService.getOne(new QueryWrapper<OpenPlatformUser>().lambda().eq(OpenPlatformUser::getMftId, mftId).eq(OpenPlatformUser::getApiKey, apiKey));
            if(platformUser == null){
                setResponse(response,R.fail(ErrorCode.MFT_ID_ERROR));
                return false;
            }
            if(platformUser.getIsBlack() == 0){
                setResponse(response,R.fail(ErrorCode.USER_BLACK));
                return false;
            }
            if(request.getRequestURI().contains("/openFileUpload/")){
                return super.preHandle(request,response,handle);
            }
        }
        //校验平台是否存在
        if (apiKey == null) {
            setResponse(response,R.fail(ErrorCode.PARAM_ID_NULL));
            return false;
        }
        String secret = this.getSecretByAppKey(apiKey);
        //校验签名是否正确
       sortMap.put("secret", secret);
       String localSignature = SHA1Util.createSHA1Sign(sortMap);
        if(!Objects.equal(signature, localSignature)){
            log.error("平台签名错误,signature:{},localSignature:{}", signature, localSignature);
            setResponse(response,R.fail(ErrorCode.SIGNATURE_ERROR));
            return false;
        }
        //时间戳校验
        Long time;
        try {
            time = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            setResponse(response,R.fail(ErrorCode.TIMESTAMP_FORMAT_ERROR));
            return false;
        }
        long currentTimeMillis = System.currentTimeMillis();
        log.debug("time:{},currentTimeMillis:{}",time,currentTimeMillis);
        if (Math.abs(time - currentTimeMillis) > 6000000) {
            setResponse(response,R.fail(ErrorCode.TIMESTAMP_EXPIRE_ERROR));
            return false;
        }
        return super.preHandle(request,response,handle);
    }
   @SuppressWarnings("all")
    private void setResponse(HttpServletResponse response, R r) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();;
        writer.println(JSON.toJSONString(r));
        writer.flush();
        writer.close();
    }
   
   /*
    * 以/开头路径
    * 获得controller mappingurl
    * 取前两个
    */
   private static  String getMappingUrl(String restUrl){
      if(StringUtils.isNotBlank(restUrl)){
         String[] urlArray = restUrl.split("/");
         if(urlArray.length >= 3){
            return "/"+urlArray[1]+"/"+urlArray[2];
         }
      }
      return null;
   }
   
   /**
    * 校验appKey
    */
   private String getSecretByAppKey(String appKey){
        OpenPlatformUser openPlatformUser = userService.getOne(new QueryWrapper<OpenPlatformUser>().lambda().eq(OpenPlatformUser::getApiKey, appKey));
        if(openPlatformUser == null){
            return null;
        }
        return openPlatformUser.getSecret();
   }
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值