java中各种加密算法的实践应用_java hex加密-程序员宅基地

技术标签: 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) 保密年限
80 80 1024 160 2010
112 112 2048 224 2030
128 128 3072 256 2040
192 192 7680 384 2080
256 256 15360 512 2120

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是为了保证密文解密后的数据就是明文。

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

小结:

求N N= p * q ;p,q为质数
求L L=lcm(p-1,q-1) ;L为p-1、q-1的最小公倍数
求E 1 < E < L,gcd(E,L)=1;E,L最大公约数为1(E和L互质)
求D 1 < 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();
   }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zengqingfa123/article/details/128720121

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签