0


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自身包实现

@Testpublic 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>

使用:

@Testpublic 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();returnnull;
    }
}

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'

测试验证:

@Testpublicvoidtest3(){/**
     * 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值判断和查找文件),如果没有被引用,则删除。

@Testpublicvoidtest4(){/**
     * 0ce38a76c8ba5a1e932ea00bce358be0
     * 0ce38a76c8ba5a1e932ea00bce358be0
     * true
     */try{FileInputStream file1 =newFileInputStream(newFile("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\java\\com\\slfx\\open\\api\\Md5Test.java"));FileInputStream file2 =newFileInputStream(newFile("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工具类

importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importjavax.crypto.SecretKey;importjava.io.Closeable;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;importjava.security.SecureRandom;/**
 * @author zengqingfa
 * @className AESUtils
 * @description AES 对称算法加密/解密工具类
 * @create 2022/9/25 22:48
 */publicclassAESUtils{/**
     * 密钥长度: 128, 192 or 256
     */privatestaticfinalintKEY_SIZE=128;/**
     * 加密/解密算法名称
     */privatestaticfinalStringALGORITHM="AES";/**
     * 随机数生成器(RNG)算法名称
     */privatestaticfinalStringRNG_ALGORITHM="SHA1PRNG";/**
     * 生成密钥对象
     */privatestaticSecretKeygenerateKey(byte[] key)throwsException{// 创建安全随机数生成器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();}/**
     * 数据加密: 明文 -> 密文
     */publicstaticbyte[]encrypt(byte[] plainBytes,byte[] key)throwsException{// 生成密钥对象SecretKey secKey =generateKey(key);// 获取 AES 密码器Cipher cipher =Cipher.getInstance(ALGORITHM);// 初始化密码器(加密模型)
        cipher.init(Cipher.ENCRYPT_MODE, secKey);// 加密数据, 返回密文byte[] cipherBytes = cipher.doFinal(plainBytes);return cipherBytes;}/**
     * 数据解密: 密文 -> 明文
     */publicstaticbyte[]decrypt(byte[] cipherBytes,byte[] key)throwsException{// 生成密钥对象SecretKey secKey =generateKey(key);// 获取 AES 密码器Cipher cipher =Cipher.getInstance(ALGORITHM);// 初始化密码器(解密模型)
        cipher.init(Cipher.DECRYPT_MODE, secKey);// 解密数据, 返回明文byte[] plainBytes = cipher.doFinal(cipherBytes);return plainBytes;}/**
     * 加密文件: 明文输入 -> 密文输出
     */publicstaticvoidencryptFile(File plainIn,File cipherOut,byte[] key)throwsException{aesFile(plainIn, cipherOut, key,true);}/**
     * 解密文件: 密文输入 -> 明文输出
     */publicstaticvoiddecryptFile(File cipherIn,File plainOut,byte[] key)throwsException{aesFile(plainOut, cipherIn, key,false);}/**
     * AES 加密/解密文件
     */privatestaticvoidaesFile(File plainFile,File cipherFile,byte[] key,boolean isEncrypt)throwsException{// 获取 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 =newFileInputStream(plainFile);
                out =newFileOutputStream(cipherFile);}else{// 解密: 密文文件为输入, 明文文件为输出
                in =newFileInputStream(cipherFile);
                out =newFileOutputStream(plainFile);}byte[] buf =newbyte[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);}}privatestaticvoidclose(Closeable c){if(c !=null){try{
                c.close();}catch(IOException e){// nothing}}}}

测试验证:

@Testpublicvoidtest1()throwsException{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(newString(plainBytes));/*
     * AES 对文件的加密/解密
     */// 将 文件demo.jpg 加密后输出到 文件demo.jpg_cipherAESUtils.encryptFile(newFile("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\resources\\image.png"),newFile("demo.jpg_cipher"), key.getBytes());// 将 文件demo.jpg_cipher 解密后输出到 文件demo.jpg_plainAESUtils.decryptFile(newFile("demo.jpg_cipher"),newFile("demo.jpg_plain"), key.getBytes());// 对比 原文件demo.jpg 和 解密得到的文件demo.jpg_plain 两者的 MD5 将会完全相同/**
     * 901d7bf517fb03e9967ce5cb508b47c0
     * 901d7bf517fb03e9967ce5cb508b47c0
     * true
     */try{FileInputStream file1 =newFileInputStream(newFile("E:\\code\\gitee2\\springboot-examples\\business-biz-demo\\open-api-demo\\src\\test\\resources\\image.png"));FileInputStream file2 =newFileInputStream(newFile("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}}

工具类方法返回字符串

importorg.apache.commons.codec.binary.Base64;importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importjavax.crypto.SecretKey;importjavax.crypto.spec.SecretKeySpec;importjava.security.NoSuchAlgorithmException;importjava.security.SecureRandom;/**
 * AES工具类
 */publicclassAesUtil{privatestaticfinalStringKEY_ALGORITHM="AES";privatestaticfinalStringDEFAULT_CIPHER_ALGORITHM="AES/ECB/PKCS5Padding";/**
     * AES加密
     *
     * @param passwd  加密的密钥
     * @param content 需要加密的字符串
     * @return 返回Base64转码后的加密数据
     * @throws Exception
     */publicstaticStringencrypt(String passwd,String content)throwsException{// 创建密码器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转码返回returnBase64.encodeBase64String(result);}/**
     * AES解密
     *
     * @param passwd    加密的密钥
     * @param encrypted 已加密的密文
     * @return 返回解密后的数据
     * @throws Exception
     */publicstaticStringdecrypt(String passwd,String encrypted)throwsException{//实例化Cipher cipher =Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);//使用密钥初始化,设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE,getSecretKey(passwd));//执行操作byte[] result = cipher.doFinal(Base64.decodeBase64(encrypted));returnnewString(result,"utf-8");}/**
     * 生成加密秘钥
     *
     * @return
     */privatestaticSecretKeySpecgetSecretKey(finalString password)throwsNoSuchAlgorithmException{//返回生成指定算法密钥生成器的 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专用密钥returnnewSecretKeySpec(secretKey.getEncoded(),KEY_ALGORITHM);}}

测试:

/**
 * 使用工具类{@link AesUtil}
 * @throws Exception
 */@Testpublicvoidtest2()throwsException{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
 */@Testpublicvoidtest3()throwsException{String content ="Hello world!";// 原文内容//key的长度有限制:16位String key ="f5k0f5w7f8g4er88";/**
     * 初始化加密(默认的AES加密方式)
     */SymmetricCrypto crypto =newSymmetricCrypto(SymmetricAlgorithm.AES, key.getBytes());//加密String encrypt = crypto.encryptHex(content);//aaaeea620dfb79c88f40260b20f862c7System.out.println(encrypt);//解密 Hello world!System.out.println(crypto.decrypt(encrypt));}

hutool封装成工具类:

importcn.hutool.crypto.symmetric.SymmetricAlgorithm;importcn.hutool.crypto.symmetric.SymmetricCrypto;importjava.nio.charset.StandardCharsets;/**
 * AES加密方式算法工具类
 */publicclassAesHutoolUtils{/**
     * KEY 随机的后续可更改
     */privatestaticfinalbyte[] key ="f5k0f7w7f8g4er88".getBytes(StandardCharsets.UTF_8);/**
     * 初始化加密(默认的AES加密方式)
     */privatestaticfinalSymmetricCrypto aes =newSymmetricCrypto(SymmetricAlgorithm.AES, key);/**
     * 加密
     * @param str 加密之前的字符串
     * @return
     */publicstaticStringencryptHex(String str){return aes.encryptHex(str);}/**
     * 解密
     * @param str 加密后的字符串
     * @return
     */publicstaticStringdecryptStr(String str){return aes.decryptStr(str);}}

2)项目中使用

接口参数加密

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

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

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

参数解密方法:

/**
 * 根据ID获取
 *
 * @param id
 * @return
 */@GetMapping("/public/getById")publicShortUrlpublicGetById(@RequestParam("id")String id){//解密String s;try{
        s =AesHutoolUtils.decryptStr(id);}catch(Exception e){
        log.error("参数非法,无权限访问! id={}", id);thrownewRuntimeException("参数非法,无权限访问! ");}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)保密年限808010241602010112112204822420301281283072256204019219276803842080256256153605122120

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
  • 求D1)求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代码实现

封装工具类

importorg.apache.commons.codec.binary.Base64;importjavax.crypto.Cipher;importjava.security.KeyFactory;importjava.security.KeyPair;importjava.security.KeyPairGenerator;importjava.security.NoSuchAlgorithmException;importjava.security.SecureRandom;importjava.security.interfaces.RSAPrivateKey;importjava.security.interfaces.RSAPublicKey;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;importjava.util.HashMap;importjava.util.Map;publicclassRSAEncrypt{privatestaticMap<Integer,String> keyMap =newHashMap<Integer,String>();//用于封装随机产生的公钥与私钥publicstaticvoidmain(String[] args)throwsException{/**
       * 随机生成的公钥为: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 
    */publicstaticvoidgenKeyPair()throwsNoSuchAlgorithmException{// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象  KeyPairGenerator keyPairGen =KeyPairGenerator.getInstance("RSA");// 初始化密钥对生成器,密钥大小为96-1024位  
      keyPairGen.initialize(1024,newSecureRandom());// 生成一个密钥对,保存在keyPair中  KeyPair keyPair = keyPairGen.generateKeyPair();RSAPrivateKey privateKey =(RSAPrivateKey) keyPair.getPrivate();// 得到私钥  RSAPublicKey publicKey =(RSAPublicKey) keyPair.getPublic();// 得到公钥  String publicKeyString =newString(Base64.encodeBase64(publicKey.getEncoded()));// 得到私钥字符串  String privateKeyString =newString(Base64.encodeBase64((privateKey.getEncoded())));// 将公钥和私钥保存到Map
      keyMap.put(0,publicKeyString);//0表示公钥
      keyMap.put(1,privateKeyString);//1表示私钥}/** 
    * RSA公钥加密 
    *  
    * @param str 
    *            加密字符串
    * @param publicKey 
    *            公钥 
    * @return 密文 
    * @throws Exception 
    *             加密过程中的异常信息 
    */publicstaticStringencrypt(String str,String publicKey )throwsException{//base64编码的公钥byte[] decoded =Base64.decodeBase64(publicKey);RSAPublicKey pubKey =(RSAPublicKey)KeyFactory.getInstance("RSA").generatePublic(newX509EncodedKeySpec(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 
    *             解密过程中的异常信息 
    */publicstaticStringdecrypt(String str,String privateKey)throwsException{//64位解码加密后的字符串byte[] inputByte =Base64.decodeBase64(str.getBytes("UTF-8"));//base64编码的私钥byte[] decoded =Base64.decodeBase64(privateKey);RSAPrivateKey priKey =(RSAPrivateKey)KeyFactory.getInstance("RSA").generatePrivate(newPKCS8EncodedKeySpec(decoded));//RSA解密Cipher cipher =Cipher.getInstance("RSA");
      cipher.init(Cipher.DECRYPT_MODE, priKey);String outStr =newString(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>

代码实现:

packagecom.slfx.open.api.utils;importjava.util.Base64;importcn.hutool.core.util.CharsetUtil;importcn.hutool.core.util.StrUtil;importcn.hutool.crypto.asymmetric.KeyType;importcn.hutool.crypto.asymmetric.RSA;/**使用hutool工具类中的rsa工具*/publicclassRSAHutoolUtil{/**
    * 获取公钥私钥密钥对
    * */staticStringgetKeyPair(RSA rsa){StringBuilder rtnStb=newStringBuilder();//获得私钥//    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();}//加密publicstaticStringgetEncryptString(String str,RSA rsa){byte[] encrypt = rsa.encrypt(StrUtil.bytes(str,CharsetUtil.CHARSET_UTF_8),KeyType.PublicKey);returnBase64.getEncoder().encodeToString(encrypt);}//解密publicstaticStringgetDecryptString(String str,RSA rsa){byte[] aByte =Base64.getDecoder().decode(str);byte[] decrypt = rsa.decrypt(aByte,KeyType.PrivateKey);returnnewString(decrypt,CharsetUtil.CHARSET_UTF_8);}publicstaticvoidmain(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 =newRSA(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);//123System.out.println(decryptTxt);}}

4)项目中的使用场景

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

签名工具类:

publicclassRsaUtils{/**
     * 秘钥对算法名称
     */privatestaticfinalStringALGORITHM="RSA";/**
     * 密钥长度
     */privatestaticfinalintKEY_SIZE=1024;/**
     * 签名算法
     */privatestaticfinalStringSIGNATURE_ALGORITHM="Sha1WithRSA";/**
     * 随机生成 RSA 密钥对(包含公钥和私钥)
     */publicstaticString[]generateKeyPair()throwsException{// 获取指定算法的密钥对生成器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 =newString(Base64.getEncoder().encode(publicKey.getEncoded()));/** 得到私钥字符串 */String privateKeyString =newString(Base64.getEncoder().encode((privateKey.getEncoded())));returnnewString[]{publicKeyString, privateKeyString};}/**
     * 私钥签名(数据): 用私钥对指定字节数组数据进行签名, 返回签名信息
     */publicstaticbyte[]sign(byte[] data,PrivateKey priKey)throwsException{// 根据指定算法获取签名工具Signature sign =Signature.getInstance(SIGNATURE_ALGORITHM);// 用私钥初始化签名工具
        sign.initSign(priKey);// 添加要签名的数据
        sign.update(data);// 计算签名结果(签名信息)byte[] signInfo = sign.sign();return signInfo;}/**
     * 公钥验签(数据): 用公钥校验指定数据的签名是否来自对应的私钥
     */publicstaticbooleanverify(byte[] data,byte[] signInfo,PublicKey pubKey)throwsException{// 根据指定算法获取签名工具Signature sign =Signature.getInstance(SIGNATURE_ALGORITHM);// 用公钥初始化签名工具
        sign.initVerify(pubKey);// 添加要校验的数据
        sign.update(data);// 校验数据的签名信息是否正确,// 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,// 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,// 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者(所有者)return sign.verify(signInfo);}/**
     * 私钥签名(文件): 用私钥对文件进行签名, 返回签名信息
     */publicstaticbyte[]signFile(File file,PrivateKey priKey)throwsException{// 根据指定算法获取签名工具Signature sign =Signature.getInstance(SIGNATURE_ALGORITHM);// 用私钥初始化签名工具
        sign.initSign(priKey);InputStream in =null;try{
            in =newFileInputStream(file);byte[] buf =newbyte[1024];int len =-1;while((len = in.read(buf))!=-1){// 添加要签名的数据
                sign.update(buf,0, len);}}finally{close(in);}// 计算并返回签名结果(签名信息)return sign.sign();}/**
     * 公钥验签(文件): 用公钥校验指定文件的签名是否来自对应的私钥
     */publicstaticbooleanverifyFile(File file,byte[] signInfo,PublicKey pubKey)throwsException{// 根据指定算法获取签名工具Signature sign =Signature.getInstance(SIGNATURE_ALGORITHM);// 用公钥初始化签名工具
        sign.initVerify(pubKey);InputStream in =null;try{
            in =newFileInputStream(file);byte[] buf =newbyte[1024];int len =-1;while((len = in.read(buf))!=-1){// 添加要校验的数据
                sign.update(buf,0, len);}}finally{close(in);}// 校验签名return sign.verify(signInfo);}privatestaticvoidclose(Closeable c){if(c !=null){try{
                c.close();}catch(IOException e){// nothing}}}publicstaticPublicKeygetPublicKeyByStr(String publicKeyStr)throwsNoSuchAlgorithmException,InvalidKeySpecException{byte[] publicBytes =Base64.getDecoder().decode(publicKeyStr);X509EncodedKeySpec keySpec =newX509EncodedKeySpec(publicBytes);KeyFactory keyFactory =KeyFactory.getInstance("RSA");PublicKey pubKey = keyFactory.generatePublic(keySpec);return pubKey;}publicstaticPrivateKeygetPrivateKeyByStr(String privateKey)throwsNoSuchAlgorithmException,InvalidKeySpecException{byte[] priBytes =Base64.getDecoder().decode(privateKey);PKCS8EncodedKeySpec priKeySpec =newPKCS8EncodedKeySpec(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
     */publicstaticStringsign(String appId,String url,String requestBody,String timeStamp,String privateKey)throwsException{//校验参数if(StringUtils.isBlank(appId)){thrownewCommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);}if(StringUtils.isBlank(url)){thrownewCommonException(ErrorCodeEnum.ERROR_URL_IS_NOT_EMPTY);}if(StringUtils.isBlank(timeStamp)){thrownewCommonException(ErrorCodeEnum.ERROR_TIME_STAMP_NOT_EMPTY);}if(StringUtils.isBlank(privateKey)){thrownewCommonException(ErrorCodeEnum.ERROR_PRIVATE_KEY_NOT_EMPTY);}TreeMap<String,String> resultMap =newTreeMap<String,String>();
        resultMap =urlSplit(url);//        resultMap.put("appId",appId);//        resultMap.put("timeStamp",timeStamp);//拼接请求bodyif(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
     */publicstaticvoidmain(String[] args)throwsException{//        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=newHashMap<>();
        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
     */privatestaticStringtruncateUrlPage(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
     */publicstaticTreeMap<String,String>urlSplit(StringURL){TreeMap<String,String> mapRequest =newTreeMap<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
     */publicstaticString[]genKeyPair()throwsNoSuchAlgorithmException{// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象KeyPairGenerator keyPairGen =KeyPairGenerator.getInstance("RSA");/**  初始化密钥对生成器,密钥大小为96-1024位  */
        keyPairGen.initialize(1024,newSecureRandom());/** 生成一个密钥对,保存在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表示私钥returnnewString[]{publicKeyString, privateKeyString};}}

客户端加签:

@ComponentpublicclassHttpCallUtil{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(HttpCallUtil.class);@ResourceOpenApiConfig openApiConfig;/**
     * get方法调用加签
     *
     * @param reqUrl
     * @param params
     * @param headers
     * @param flag
     * @return
     */publicStringokHttpGet(String reqUrl,Map<String,Object> params,Map<String,String> headers,boolean flag){OkHttpClientOKHTTP_CLIENT=newOkHttpClient();StringBuilder stringBuilder =newStringBuilder();//处理参数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 =newRequest.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){thrownewRuntimeException("HTTP GET同步请求失败 URL:"+ reqUrl, e);}finally{
            response.close();}}/**
     * post方法调用加签
     *
     * @param reqUrl
     * @param params
     * @param contentType
     * @param headers
     * @return
     */publicStringokHttpPost(String reqUrl,String params,String contentType,Map<String,String> headers){OkHttpClient client =newOkHttpClient().newBuilder().connectTimeout(80,TimeUnit.SECONDS).addInterceptor(newRetryIntercepter(3)).readTimeout(80,TimeUnit.SECONDS).writeTimeout(80,TimeUnit.SECONDS).build();MediaType mediaType =MediaType.parse(contentType);RequestBody body =RequestBody.create(mediaType, params);Request.Builder reqUrlBuilder =newRequest.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){thrownewRuntimeException("HTTP POST同步请求失败 URL:"+ reqUrl, e);}}/**
     * post发送消息
     *
     * @param url
     * @param postParams
     * @return
     * @throws Exception
     */publicStringsendOpenApiPost(String url,String postParams)throwsException{String timeStamp =String.valueOf(System.currentTimeMillis());//加签String sign =RsaUtils.sign(openApiConfig.getAppId(), url, postParams, timeStamp, openApiConfig.getPrivateKey());Map<String,String> headerMap =newHashMap<>();
        headerMap.put("appId",openApiConfig.getAppId());
        headerMap.put("timeStamp", timeStamp);
        headerMap.put("sign", sign);returnokHttpPost(url, postParams,"application/json", headerMap);}/**
     * get发送消息
     *
     * @param url 调用的url
     * @param params 调用的参数
     * @return
     * @throws Exception
     */publicStringsendOpenApiGet(String url,Map<String,Object> params)throwsException{String timeStamp =String.valueOf(System.currentTimeMillis());//加签url处理String sign =RsaUtils.sign(openApiConfig.getAppId(), url,null, timeStamp, openApiConfig.getPrivateKey());Map<String,String> headerMap =newHashMap<>();
        headerMap.put("appId", openApiConfig.getAppId());
        headerMap.put("timeStamp", timeStamp);
        headerMap.put("sign", sign);returnokHttpGet(url, params, headerMap,false);}}

拦截器验签:

@Component@Slf4jpublicclassApiRequestInterceptorextendsHandlerInterceptorAdapter{@ResourceprivateAppSecretService appSecretService;@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handle)throwsException{//获取请求头参数Map<String,String> headerMap =newHashMap<>();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)){thrownewCommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);}if(StringUtils.isBlank(timeStamp)){thrownewCommonException(ErrorCodeEnum.ERROR_TIME_STAMP_NOT_EMPTY);}if(StringUtils.isBlank(sign)){thrownewCommonException(ErrorCodeEnum.ERROR_SIGN_IS_NOT_EMPTY);}//校验appIdAppSecret appSecret = appSecretService.getOne(newLambdaQueryWrapper<AppSecret>().eq(AppSecret::getAppId, headerMap.get("appid")).last("limit 1"));if(Objects.isNull(appSecret)){thrownewCommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);}//时间戳校验,防dos攻击Long time;try{
            time =Long.parseLong(timeStamp);}catch(NumberFormatException e){setResponse(response,"时间戳格式异常");returnfalse;}long currentTimeMillis =System.currentTimeMillis();
        log.info("time:{},currentTimeMillis:{}", time, currentTimeMillis);//(6000000/1000)s=6000s /60 =100minuteif(Math.abs(time - currentTimeMillis)>6000000){setResponse(response,"时间戳过期");returnfalse;}String requestURI = request.getRequestURI();String serverName = request.getServerName();//主机地址int port = request.getServerPort();//端口号String path = request.getContextPath();//带斜杠的项目名String queryString = request.getQueryString();//获取请求参数字符串//拼接对应的urlString url = serverName +":"+ port + path + requestURI + queryString;//获取请求参数mapSortedMap<String,String> sortMap =newTreeMap<>();//先判断是否是post请求String contentType = request.getContentType();try{if(contentType !=null&& contentType.indexOf("json")!=-1&&"post".equalsIgnoreCase(request.getMethod())){//获取到请求参数 request是经过包装的MyHttpServletRequestWrapperString 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,"参数解析失败,参数不能为空");returnfalse;}
        log.debug("第三方请求参数:{},签名signature:{}",JSON.toJSONString(sortMap), sign);if(sortMap ==null|| sortMap.size()==0){setResponse(response,"请求参数为空");returnfalse;}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,"平台签名错误");returnfalse;}returnsuper.preHandle(request, response, handle);}@SuppressWarnings("all")privatevoidsetResponse(HttpServletResponse response,String errorMessage)throwsIOException{
        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代码实现

importorg.apache.commons.codec.digest.DigestUtils;importjava.security.MessageDigest;publicclassSHA1Utils{publicstaticStringshaEncode(String inStr)throwsException{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 =newStringBuffer();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();}publicstaticvoidmain(String args[])throwsException{String str ="shenlongfeixian";System.out.println("原始:"+ str);//e94777a3472b5e35562d3cbfd112556b95c80e91System.out.println("SHA后:"+shaEncode(str));//e94777a3472b5e35562d3cbfd112556b95c80e91System.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自带的实现加密代码实现:

importorg.apache.commons.codec.binary.Hex;importjava.io.UnsupportedEncodingException;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;publicclassSHA256Utils{/**
     * 利用java原生的摘要实现SHA256加密
     *
     * @param str 加密后的报文
     * @return
     */publicstaticStringgetSHA256StrJava(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
     */privatestaticStringbyte2Hex(byte[] bytes){StringBuffer stringBuffer =newStringBuffer();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();}publicstaticvoidmain(String args[])throwsException{String str ="shenlongfeixian";System.out.println("原始:"+ str);//e961dffd040490157690aa2ac18e174236e180d9910cde27d344593268cf5778System.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);//e961dffd040490157690aa2ac18e174236e180d9910cde27d344593268cf5778System.out.println("common包工具类:"+ s);}}

4)项目中的使用场景

接口之间进行签名操作

签名生成工具类:

importlombok.extern.slf4j.Slf4j;importjava.io.UnsupportedEncodingException;importjava.net.URLEncoder;importjava.security.MessageDigest;importjava.util.Iterator;importjava.util.Map.Entry;importjava.util.Set;importjava.util.SortedMap;importjava.util.TreeMap;/* 
 * 
'============================================================================ 
'api说明: 
'createSHA1Sign创建签名SHA1 
'getSha1()Sha1签名 
'============================================================================ 
'*/@Slf4jpublicclassSHA1Util{//微信公众号开发的获取时间戳publicstaticStringgetTimeStamp(){returnString.valueOf(System.currentTimeMillis()/1000);}//创建签名SHA1  publicstaticStringcreateSHA1Sign(SortedMap<String,String> signParams){StringBuffer sb =newStringBuffer();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);returngetSha1(params);}//Sha1签名  publicstaticStringgetSha1(String str){if(str ==null|| str.length()==0){returnnull;}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[]=newchar[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];}returnnewString(buf);}catch(Exception e){
            log.error("getSha1 error", e);thrownewRuntimeException(e.getMessage(), e);}}/**
     * 规范化转换
     *
     * @param sortedParamMap
     * @return
     */publicstaticStringcanonicalize(SortedMap<String,String> sortedParamMap){if(sortedParamMap.isEmpty()){return"";}StringBuffer buffer =newStringBuffer();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
     */privatestaticStringpercentEncodeRfc3986(String s){String out;try{
            out =URLEncoder.encode(s,"utf-8").replace("+","%20").replace("*","%2A").replace("%7E","~");}catch(UnsupportedEncodingException e){
            out = s;}return out;}publicstaticvoidmain(String[] args){//     String str = "appId=8b8e9dce-0ade-4a38-8cb4-8acbc3726591";//     String aa = str + "35390e1809b948cba5c2d6d73218e75f";//     System.out.println(getSha1(aa));SortedMap<String,String> map =newTreeMap<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");//b1d6fce5dbc0f73f719fa304d51ddafb464ce20eSystem.out.println(createSHA1Sign(map));}}

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

@Component@Slf4jpublicclassApiRequestInterceptorextendsHandlerInterceptorAdapter{@AutowiredprivateOpenPlatformUserService userService;@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handle)throwsException{//获取请求参数mapSortedMap<String,String> sortMap =newTreeMap<>();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 instanceofArrayList||valueObj instanceofJSONArray||valueObj instanceofJSONObject|| valueObj instanceofMap){//复杂对象使用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));returnfalse;}
        
        log.debug("第三方请求参数:{},签名signature:{}",JSON.toJSONString(sortMap),signature);if(sortMap ==null|| sortMap.size()==0){setResponse(response,R.fail(ErrorCode.PARAM_NULL));returnfalse;}String apiKey = sortMap.get("apiKey");//获取对接平台厂商IDString mftId = sortMap.get("mftId");//校验mftIdif(StringUtils.isNotBlank(mftId)){OpenPlatformUser platformUser = userService.getOne(newQueryWrapper<OpenPlatformUser>().lambda().eq(OpenPlatformUser::getMftId, mftId).eq(OpenPlatformUser::getApiKey, apiKey));if(platformUser ==null){setResponse(response,R.fail(ErrorCode.MFT_ID_ERROR));returnfalse;}if(platformUser.getIsBlack()==0){setResponse(response,R.fail(ErrorCode.USER_BLACK));returnfalse;}if(request.getRequestURI().contains("/openFileUpload/")){returnsuper.preHandle(request,response,handle);}}//校验平台是否存在if(apiKey ==null){setResponse(response,R.fail(ErrorCode.PARAM_ID_NULL));returnfalse;}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));returnfalse;}//时间戳校验Long time;try{
            time =Long.parseLong(timestamp);}catch(NumberFormatException e){setResponse(response,R.fail(ErrorCode.TIMESTAMP_FORMAT_ERROR));returnfalse;}long currentTimeMillis =System.currentTimeMillis();
        log.debug("time:{},currentTimeMillis:{}",time,currentTimeMillis);if(Math.abs(time - currentTimeMillis)>6000000){setResponse(response,R.fail(ErrorCode.TIMESTAMP_EXPIRE_ERROR));returnfalse;}returnsuper.preHandle(request,response,handle);}@SuppressWarnings("all")privatevoidsetResponse(HttpServletResponse response,R r)throwsIOException{
        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
    * 取前两个
    */privatestaticStringgetMappingUrl(String restUrl){if(StringUtils.isNotBlank(restUrl)){String[] urlArray = restUrl.split("/");if(urlArray.length >=3){return"/"+urlArray[1]+"/"+urlArray[2];}}returnnull;}/**
    * 校验appKey
    */privateStringgetSecretByAppKey(String appKey){OpenPlatformUser openPlatformUser = userService.getOne(newQueryWrapper<OpenPlatformUser>().lambda().eq(OpenPlatformUser::getApiKey, appKey));if(openPlatformUser ==null){returnnull;}return openPlatformUser.getSecret();}}
标签: java 安全 网络

本文转载自: https://blog.csdn.net/zengqingfa123/article/details/128720121
版权归原作者 java神龙飞仙 所有, 如有侵权,请联系我们删除。

“java中各种加密算法的实践应用”的评论:

还没有评论