Java 常见加密算法用法详解

前言:

在项目开发当中,经常会去对接一些三方系统,或者服务与服务之间的调用,很多时候数据都是以明文的方式传输,了解过网络的同学应该都知道,这样的数据非常容易被拦截甚至是篡改,安全性极低,有时造成的后果是不可估量的,经常对接支付平台的小伙伴或多或少都见过在实际入参中还会拼接一个类似sign的字段,来对数据进行签名校验,即使数据被恶意篡改,服务端在验证不通过后可对此请求进行拦截销毁,有些平台甚至会对整个报文数据进行加密处理,以提高更高的安全性,本次就借助Hutool工具来简单讲解下市面上大多数使用到的加解密,以及加签验签的一些算法使用。

一、 Java中加解密分为哪些

Java中的加解密通常大体分为以下三种:

对称加密(symmetric):

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。

非对称加密(asymmetric):

非对称加密‌是一种使用一对密钥进行数据加密和解密的密码学方法。这一对密钥包括一个‌公钥‌和一个‌私钥‌。公钥可以公开分发,用于加密数据,而私钥则严格保密,用于解密数据‌。这种机制确保了信息的安全性,因为公钥无法推导出私钥,从而保证了加密的可靠性‌

摘要加密(digest):

摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的"摘要"便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。

现在我们对加解密有了一些基本的认识,那到底有哪些算法呢,如何去编写实现,下来就来详细讲解

二、 Java中加解密算法与代码编写

首先我们需要引入hutool依赖

复制代码
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.38</version>
</dependency>

Java中主要的加解密算法实现有以下几种:

1、对称加密(symmetric)

对于对称加密实现了以下几种算法:

  • AES
  • ARCFOUR
  • Blowfish
  • DES
  • DESede
  • RC2
  • PBEWithMD5AndDES
  • PBEWithSHA1AndDESede
  • PBEWithSHA1AndRC2_40

代码编写实现(以AES算法为例:):

java 复制代码
// 待加密明文
String content = "test中文";
System.out.println("原文:"+content);

// 随机生成密钥
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
// 密钥转base64方便存储
String keyBase64 = Base64.encode(key);
System.out.println("AES密钥为:"+keyBase64);

// 构建算法实例
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, Base64.decode(keyBase64));

// 加密为base64密文
String ciphertextBase64 = aes.encryptBase64(content);
System.out.println("AES加密后密文为:"+ciphertextBase64);
// 以base64方式解密为字符串
String decryptStr = aes.decryptStr(Base64.decode(ciphertextBase64));
System.out.println("AES解密后:"+decryptStr);

我们运行一下看看:

可以看到,明文被成功加密为我们看不懂的数据,然后再解密为真实数据,我们点进SymmetricAlgorithm类看一下:

可以看到上述所列出的均进行了实现

2、非对称加密(asymmetric)

对于非对称加密实现了以下几种算法:

  • RSA
  • DSA
java 复制代码
代码编写:

// 待加密明文
String content = "test中文";
System.out.println("原文:"+content);
// 随机生成密钥
KeyPair pair = SecureUtil.generateKeyPair(AsymmetricAlgorithm.RSA.getValue());
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
// 密钥转base64方便存储
String privateKeyBase64 = Base64.encode(privateKey);
String publicKeyBase64 = Base64.encode(publicKey);
System.out.println("RSA私钥为:"+privateKeyBase64);
System.out.println("RSA公钥为:"+publicKeyBase64);

// 构建算法实例
AsymmetricCrypto rsa = new AsymmetricCrypto(AsymmetricAlgorithm.RSA, Base64.decode(privateKeyBase64), Base64.decode(publicKeyBase64));

// 公钥加密为base64密文
String ciphertextBase64 = rsa.encryptBase64(content, KeyType.PublicKey);
System.out.println("RSA加密后密文为:"+ciphertextBase64);
// 私钥方式解密为字符串
String decryptStr = rsa.decryptStr(ciphertextBase64, KeyType.PrivateKey);
System.out.println("RSA解密后:"+decryptStr);

我们运行一下看看:

可以看到,明文被成功加密为我们看不懂的数据,然后再解密为真实数据,我们点进 AsymmetricAlgorithm类看一下:

可以看到上述所列出的均进行了实现

3、摘要加密(digest)

摘要加密又分为两种,一种是不需要密钥的算法,直接生成摘要信息,另一种是需要密钥的算法

3.1、不需要密钥的算法具体实现了以下几种:

MD2

MD5

SHA-1

SHA-256

SHA-384

SHA-512

代码编写:

复制代码
String testStr = "我是原文";
System.out.println("原文:"+testStr);

Digester md5 = new Digester(DigestAlgorithm.MD5);

// 摘要计算
String digestHex = md5.digestHex(testStr);
System.out.println("摘要加密后:"+digestHex);

我们运行一下看看:

可以看到摘要信息被计算了出来,我们点进DigestAlgorithm类看一下:

上述所列均进行了实现!

3.2、需要密钥的算法具体实现了以下几种(其实也是加强了安全性):
  • HmacMD5
  • HmacSHA1
  • HmacSHA256
  • HmacSHA384
  • HmacSHA512

代码编写:

复制代码
String testStr = "我是原文";
System.out.println("原文:"+testStr);

// 密钥可以自己定义
byte[] key = "password".getBytes();
HMac mac = new HMac(HmacAlgorithm.HmacMD5, key);

// 摘要计算
String digestHex = mac.digestHex(testStr);
System.out.println("摘要加密后:"+digestHex);

我们运行一下看看:

可以看到摘要信息被计算了出来,我们点进HmacAlgorithm类看一下:

上述所列均进行了实现!

以上三种加解密算法大部分已经讲解完成了,我们还听到过其他最多的名词就是《签名验签》,早期很多项目都在使用摘要算法做签名,比如MD5,SHA-256,但这种算法的弊端也都知道,任何懂算法的人都会用,完全无用,下来说一下目前常用的签名验签机制。

4、签名验签算法

常用大部分的签名机制都是摘要算法与非对称加密算法的组合,共同构成了核心的数字签名机制。这种组合方式实现了身份认证、数据完整性和不可否认性,其运作原理包括以下关键点:

  1. 摘要算法保障完整性‌

发送方先用哈希算法(如SHA-256)生成原始数据的唯一摘要值。该过程具备单向性:任何数据变动均会产生不同摘要值,从而验证内容是否被篡改。

  1. 非对称加密实现身份绑定‌

发送方使用自身私钥加密摘要值,生成数字签名。接收方通过公钥解密获取摘要值,由于私钥的唯一性,成功解密即可确认发送方身份

这也就说明了,它需要公私钥,此处的密钥取决于你使用的非对称加密算法,相应的,并非所有组合都适配,不同的摘要算法可能需要适当调整密钥的长度位数等。

比如RSA2(2048位长度的RSA密钥与SHA256的摘要算法)组合

对于常用签名算法,hutool工具也对jdk的实现进行了包装

具体实现了以下几种:

  • NONEwithRSA
  • MD2withRSA
  • MD5withRSA
  • SHA1withRSA
  • SHA256withRSA
  • SHA384withRSA
  • SHA512withRSA
  • NONEwithDSA
  • SHA1withDSA
  • NONEwithECDSA
  • SHA1withECDSA
  • SHA256withECDSA
  • SHA384withECDSA
  • SHA512withECDSA
    代码编写:
java 复制代码
// 随机生成密钥
KeyPair pair = SecureUtil.generateKeyPair(AsymmetricAlgorithm.RSA.getValue(), 2048);
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
// 密钥转base64方便存储
String privateKeyBase64 = Base64.encode(privateKey);
String publicKeyBase64 = Base64.encode(publicKey);

String testStr = "我是一段测试字符串";
System.out.println("原文:" + testStr);

Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKeyBase64, publicKeyBase64);
//签名
String signStr = Base64.encode(sign.sign(testStr));
System.out.println("签名:" + signStr);
//验证签名
boolean verify = sign.verify(testStr.getBytes(StandardCharsets.UTF_8), Base64.decode(signStr));
if (verify) {
    System.out.println("签名通过》》》》》》》》》》》");
}

我们运行一下看看:

可以看到摘要信息被计算了出来,我们点进SignAlgorithm类看一下:

上述所列均进行了实现!

当然你也可以使用你想要的其他组合方式,如果你的依赖支持的话!

到此常用的加解密与签名验签算法已经了解的差不多了,当然这只是java中自带的一些算法,我们也可以使用一些三方包来实现更多加密算法,比如使用最广泛的国密算法(即国家密码局认定的国产密码算法)

三、 什么是国密算法

为了保障国内商用密码的安全性,国家密码局制定了一系列自主可控的国产算法,即国密算法。国密算法广泛应用于金融、安防、政务等领域,不仅能够有效保护关键敏感数据的完整性、机密性和可用性,而且可以降低对国外加密算法的依赖性,提升国家整体信息安全水平。目前,国家密码管理局已发布了一系列国产商用密码标准算法,包括SM1、SM2、SM3、SM4、SM7、SM9以及祖冲之密码算法等。

Hutool针对Bouncy Castle做了简化包装,用于实现国密算法中的SM2、SM3、SM4。

下文将主要介绍国密算法中的常用算法SM2、SM3和SM4的实现和应用。

1、 国密算法需要引入Bouncy Castle库的依赖

复制代码
<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcpkix-jdk18on</artifactId>
  <version>1.78.1</version>
</dependency>

本次使用jdk1.8对应的版本

2、非对称加密SM2

代码实现:

复制代码
String text = "我是一段测试aaaa";
System.out.println("原文:"+text);

KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
// 密钥转base64方便存储
String privateKeyBase64 = Base64.encode(privateKey);
String publicKeyBase64 = Base64.encode(publicKey);

SM2 sm2 = new SM2(privateKeyBase64, publicKeyBase64);
// 公钥加密,私钥解密
String encryptStr = sm2.encryptBase64(text, KeyType.PublicKey);
System.out.println("密文:"+encryptStr);
String decryptStr = sm2.decryptStr(encryptStr, KeyType.PrivateKey);
System.out.println("解密后:"+decryptStr);

我们运行一下看看:

可以看到原文被成功加密成密文并解密出来

3、摘要加密算法SM3

代码实现:

复制代码
String text = "我是一段测试aaaa";
System.out.println("原文:"+text);

SM3 sm3 = new SM3();
String textSm3Hex = sm3.digestHex(text);
System.out.println("摘要算法后:"+textSm3Hex);

我们运行一下看看:

摘要数据已成功获取到!

4、对称加密SM4

代码实现:

复制代码
String text = "我是一段测试aaaa";
System.out.println("原文:"+text);

SecretKey secretKey = SecureUtil.generateKey("SM4");
byte[] sm4Key = secretKey.getEncoded();
// 密钥转base64方便存储
String sm4KeyBase64 = Base64.encode(sm4Key);

SM4 sm4 = new SM4(Base64.decode(sm4KeyBase64));
// 公钥加密,私钥解密
String encryptStr = sm4.encryptBase64(text);
System.out.println("密文:"+encryptStr);
String decryptStr = sm4.decryptStr(encryptStr);
System.out.println("解密后:"+decryptStr);

我们运行一下看看:

可以看到原文被成功加密成密文并解密出来

总结:

到这里我们就已经知道,所有的加解密算法都可以分为三类,《对称加密》,《非对称加密》,《摘要加密》,而常用的签名其实就是非对称与摘要算法的组合,不同的算法生成密钥与使用有自己的方式,签名也有特定的签名工具类,hutool均做了简单包装,使用起来非常方便,当然动手能力强的同学们也可以自己使用jdk来实现。

其他概念:

加盐:

在加解密当中有些同学可能听说过"加盐",这个词,这个盐可不是我们平常吃饭的盐,而是特定的"加料"

盐(Salt) 是一段随机生成的数据,用于与密码一起进行哈希计算,以确保即使用户的密码相同,存储在数据库中的哈希值也不同
如果不使用盐,多个用户的相同密码会生成相同的哈希值。这样,一旦攻击者拿到数据库,他可以轻松找到哪些用户的密码相同,甚至使用预先计算的哈希值表来快速破解常见的密码。当然这个随机数也需要我们进行存储,用于解密或恢复明文时使用。

编码方式:

Base64与hex均属于二进制数据的表示方式,而utf-8则是字符编码标准,base64与hex并不能作为数据的加密算法,因为它可以随时被解码,没有安全性可言,而大多数的密钥均是以base64编码方式转换的,这是因base64独有的特性,他可以使编码后数据膨胀约 ‌33%,而hex则为100%、hex容易使密钥乱码或丢失关键字节,所以强制要求以base64编码,但摘要算法通常以hex形式呈现,具体可自行研究。

如检查登陆中

java 复制代码
        // 校验用户账号为空
        if (StringUtils.isEmpty(vo.getUserAccount())) {
            throw new BaseException(BaseErrorCode.USER_ACCOUNT_IS_NULL.getCode(), BaseErrorCode.USER_ACCOUNT_IS_NULL.getMessage());
        }
        // 校验用户密码为空
        if (StringUtils.isEmpty(vo.getPassWd())) {
            throw new BaseException(BaseErrorCode.USER_PASSWORD_IS_NULL.getCode(), BaseErrorCode.USER_PASSWORD_IS_NULL.getMessage());
        }
        //检查用户名、密码是否正确
        SysUser sysUser = null;
        if (vo.getType() == null || vo.getType() == 1) {
            String passWd = SysUserPassWdSaltEnum.encrypt(vo.getPassWd());
            SysUser sysUserCondition = new SysUser();
            sysUserCondition.setPasswd(passWd);
            sysUserCondition.setUserAccount(vo.getUserAccount());
            List<SysUser> sysUserList = sysUserDomain.listByPara(sysUserCondition, null);
            if (CollectionUtils.isEmpty(sysUserList)) {
                throw new BaseException(SysUserErrorCode.ACCOUNT_OR_PASSWORD_ERROR.getCode(), SysUserErrorCode.ACCOUNT_OR_PASSWORD_ERROR.getMessage());
            }
            sysUser = sysUserList.get(0);
        }

 if (sysUser == null) {
            throw new BaseException(SysUserErrorCode.USER_IS_NOT_EXIST.getCode(), SysUserErrorCode.USER_IS_NOT_EXIST.getMessage());
        }
        LoginUserInfoResVO loginUserInfoResVO = new LoginUserInfoResVO();
        if (StringUtils.isNotEmpty(sysUser.getEmployeeCode())) {
            SysEmployee sysEmployee = sysEmployeeDomain.selectByEmployeeCode(sysUser.getEmployeeCode());
            if (null != sysEmployee) {
                loginUserInfoResVO.setEmployeeId(sysEmployee.getId());
            }
        }
        BeanUtils.copyProperties(sysUser, loginUserInfoResVO);
        loginUserInfoResVO.setUserId(sysUser.getId());
        loginUserInfoResVO.setUserCode(sysUser.getEmployeeCode());
        return BaseResult.buildSuccess(loginUserInfoResVO);

SysUserPassWdSaltEnum.encrypt 方法

复制代码
 /**
     * 加密密码
     *
     * @param passWd 密码
     * @return 密文密码
     */
    public static String encrypt(String passWd) {
        return SmUtil.sm3(passWd + SALT.getValue());
    }

这块进行用户名、密码校验的时候使用了摘要算法加盐查询校验

前段传输过来的一般是进行了对称加密、或者非对称加密,后端对密码进行使用加盐,摘要算法如(SM3)等算法进行存储

前端传输的psswd

Lxa2pm19PnyzHNUhv1j6Mv82SJuS71vumpAbfQo60h7e/YZtId6nKObD7+YCzMMb07Rwm4UYZXSorHMzlm8L1WB252WcvIGSqDEc6aqzcLAZwlemsTJ3FR3usNkKCPbujdzEzAqhN/2SyJyXyvBIfPF8S3irQA/DPE9WppSTIno=

数据库经过摘要算法如(SM3)等算法进行存储,保存的密码长度一般一致,如下

eb13d1e16bb20dcb80b1e8cfd74a967897e4382c9356bfff58bfda4a2b067e03

相关推荐
嵌入式-老费5 小时前
Easyx图形库应用(lua中的函数回调)
开发语言·lua
oak隔壁找我5 小时前
SpringBoot 开发必备基础工具类实现(纯JWT认证,无SpringSecurity)
java·后端
张较瘦_5 小时前
Springboot | 初识Springboot 从“手动做饭”到“点外卖”的编程革命
java·spring boot·后端
oak隔壁找我5 小时前
SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储
java·后端
喜欢吃燃面5 小时前
算法中的链表结构
开发语言·c++·学习·算法
十五年专注C++开发5 小时前
Fruit框架:C++依赖注入解决方案
开发语言·c++·依赖注入·fruit框架
Lovely Ruby5 小时前
七日 Go 的自学笔记 (一)
开发语言·笔记·golang
big狼王6 小时前
SonarQube本地化搭建及代码检测并导出报告PDF
java·pdf·sonarqube·sonarscanner
杨筱毅6 小时前
【Android】Handler/Looper机制相关的类图和流程图
android·java·流程图