日常代码中加解密技术的使用

前言

日常使用的加密的时机,包括后端存储密码信息,API接口的认证鉴权,前后端敏感数据加密传输等等,为了保证信息的不被泄漏,也为了验证信息的准确性

base64编解码

用于将二进制数据转换为可打印的 ASCII 字符串

作用场景: 用于请求数据传输中,文件嵌入HTML/CSS,加密密钥传输等

第一种:当我们使用hutool编码实现,支持对inputStream,字符数组,文件等加密

less 复制代码
// 编码
Base64.encode("123".getBytes());
// 解码
Base64.decodeStr(encode);

第二种: 当我们使用java原生编码实现

ini 复制代码
// 编码为 Base64
String encodedString = Base64.getEncoder().encodeToString("123".getBytes());

// 解码为原始字符串
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
String decodedString = new String(decodedBytes);

注意: Base64只是编码方式,不是加密方式,不能代替加密算法,而且编码会增加33%的数据大小

对称加密

双方使用同一个密钥来进行加解密,加密速度快,但是问题就是密钥泄漏容易导致数据不安全。

目前市面上常用的加密算法:AES 比较推荐

AES实现

  • AES的工作模式包含: ECB(不太安全),CBC(推荐,需要提供IV),GCM(更安全,不需要填充方式)
  • AES的填充方式: 常用的有PKCS5PaddingPKCS7Padding
  • AES的密钥长度可以为128位(16字节),192位(24字节),256位(32字节)
  • AES的IV必须是16字节

第一种 :当我们使用hutool加密实现, 其中下文中的content为待加密的内容, 注意:hutool将对称加密都封装在symmetric包中 ,默认使用ECB 模式和 PKCS5Padding 填充方式

ini 复制代码
// 生成密钥,AES  默认密钥长度为 128位(keySize可以指定位数)
byte[] keyBytes = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
// 创建 AES 加密对象,使用 ECB 模式和 PKCS5Padding 填充方式
AES aes = SecureUtil.aes(keyBytes);
// 加密十六进制字符串结果
String encrypt = aes.encryptHex(content);
// 加密base64结果
String encrypt = aes.encryptBase64(content);
// 解密
String decryptStr = aes.decryptStr(encrypt);

如果你想使用密钥便于传输或者存储,可以转换成base64结果/Hex十六进制字符串。

scss 复制代码
// 将字节数组编码为 Base64 字符串
Base64.getEncoder().encodeToString(keyBytes);
// 十六进制字符串,可以使用 Hutool 的 HexUtil
String hexKey = HexUtil.encodeHexStr(encodedKey);

如果你需要修改其他加密模式CBC/填充模式NoPadding,需要手动构建AES对象, 注意使用CBC需要初始化响亮(IV)

ini 复制代码
private static final String INIT_VECTOR = "1234567890123456";

AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, keyBytes, INIT_VECTOR.getBytes());

第二种:当我用java原生来实现

KeyGenerator 来实现生成AES密钥, 也可以通过自己有了密钥通过使用 SecretKeySpec 将字节数组转换为密钥对象,

ini 复制代码
private static final String INIT_VECTOR = "1234567890123456";

// 生成AES密钥
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 设置密钥长度
SecretKey secretKey = keyGen.generateKey()

// 通过自己的密钥给定
byte[] keyBytes = "my-secret-key-123".getBytes(); // 必须是 16、24 或 32 字节
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

使用 Cipher 类初始化加密模式 , 然后配置工作模式和填充方式,最后加密以base64结果

ini 复制代码
// 加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(INIT_VECTOR.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
String result = Base64.getEncoder().encodeToString(encryptedBytes);

// 解密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(INIT_VECTOR.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
String result =  new String(decryptedBytes);

非对称加密

使用一对密钥(公钥和私钥),公钥加密,私钥解密。安全性能高,但速度可能会慢。

目前市面上常用的加密算法: RSA比较推荐

RSA 实现

现在主要应用的格式为PEM格式,公钥文件扩展名.pem, .pub. 私钥文件扩展名.pem, .key ,以-----BEGIN RSA PRIVATE KEY----------BEGIN PUBLIC KEY-----开头,以-----END RSA PRIVATE KEY----------END PUBLIC KEY-----结尾,中间是经过 Base64 编码的密钥数据

第一种 :当我们使用hutool加密实现

ini 复制代码
// 自动生成密钥对 , 对应base64结果的公钥和私钥
RSA rsa = new RSA();
// 如果是有公钥和私钥,可以自定义初始化
// RSA rsa = new RSA(privateKey, publicKey);
String publicKey = rsa.getPublicKeyBase64();
String privateKey = rsa.getPrivateKeyBase64();

通过调用encryptBase64来实现加密,decryptStr来实现解密

ini 复制代码
// 使用公钥加密
String encrypted = rsa.encryptBase64(plainText, KeyType.PublicKey);
// 使用私钥解密
String decrypted = rsa.decryptStr(encrypted, KeyType.PrivateKey);

如果有对于数据签名进行验签需求,可以使用sha256 with rsa签名,可以通过sign来实现

ini 复制代码
byte[] data = "12345".getBytes();
Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA);
// Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA,privateKeyBase64, publicKeyBase64);
//签名
byte[] signed = sign.sign(data);
//验证签名
boolean verify = sign.verify(data, signed);

第二种 :当我用java原生来实现,公钥通常以 X.509 标准格式进行编码,所以使用X509EncodedKeySpec

ini 复制代码
// 获取公钥 , 字符串publicKeyContent为公钥base64加密内容,剔除 BEGIN和END
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyContent);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey =  keyFactory.generatePublic(keySpec);

// 获取私钥,字符串privateKeyContent为私钥base64加密内容,剔除 BEGIN和END
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyContent);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

使用 Cipher 类初始化加密模式使用公钥/私钥,来进行加解密

ini 复制代码
// 加密,最后以base64结果输出
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
String encode = Base64.getEncoder().encodeToString(encryptedBytes);

// 解密,先将获取到的base64结果转换,最后还原字符串原文
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
String s = new String(decryptedBytes);

注意: 公钥可以对所有人公开获取,私钥是私密的只有密钥持有者才有。 使用公钥加密->只有私钥能解密 (对应场景:加密数据) 使用私钥加密->所有公钥都能解开 (对应场景:验签)

所以一般使用场景就是对数据加密,使用前端公钥加密,后端私钥才能解开。对于服务器发送的数据带有私钥签名,所有的公钥都能验证签名。

哈希算法

通过单向的加密方式,生成固定长度的摘要值,注意无法逆向还原数据,一般用于比对数据完整性校验。

目前市面上常用的加密算法: MD5,SHA系列,HMAC,bcrypt

MD5 实现

MD5 : 主要应用场景,还是针对验证数据完整性,防止内容被篡改(但容易收到碰撞攻击)

第一种使用原生java实现,

ini 复制代码
// 创建 MD5 摘要实例
MessageDigest md = MessageDigest.getInstance("MD5");

// 计算哈希值
md.update("12345".getBytes());
byte[] digest = md.digest();
// 后续在通过字节数组转换为十六进制字符串

第二种使用hutool实现

ini 复制代码
String s = SecureUtil.md5("12345");
// 也可自己定义构建实现
MD5 md5 = new MD5();
String s1 = md5.digestHex("12345");

SHA实现

SHA:主要场景: 密码存储,数字签名,验证数据完整性等。

目前SHA-1容易收到碰撞攻击,安全性不高。SHA-2目前是安全的,SHA-3是作为是作为前者SHA-2的备用

对于SHA-2的变种,不同的输出的长度

  • SHA-256 :输出 256 位(64 个十六进制字符)。
  • SHA-384 :输出 384 位(96 个十六进制字符)。
  • SHA-512 :输出 512 位(128 个十六进制字符)。

第一种使用java原生的实现 针对SHA-256

ini 复制代码
MessageDigest md = MessageDigest.getInstance("SHA-256");

// 计算哈希值
md.update("12345".getBytes());
byte[] digest = md.digest();
// 后续在通过字节数组转换为十六进制字符串

第二种使用hutool的实现

ini 复制代码
String sha256Hex = DigestUtil.sha256Hex("12345");

在密码存储等场景中,通常需要对输入数据进行加盐(Salt)处理以增加安全性, 可以将输入值和Salt字符串拼接来实现加密。

HMAC系列实现

HMAC系列 : 主要应用场景是验签等操作,需要密钥

常见的HMAC算法 :

  • HMAC-MD5
  • HMAC-SHA1
  • HMAC-SHA256
  • HMAC-SHA512

通过密钥配合来实现HMAC的加解密

第一种: 通过java原生实现

ini 复制代码
// 创建 Mac 实例
Mac mac = Mac.getInstance("HmacSHA256");
// 将密钥转换为 SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), algorithm);
// 初始化 Mac 对象
mac.init(secretKeySpec);
// 更新输入数据
mac.update(message.getBytes());
// 计算 HMAC 值
byte[] hmacBytes = mac.doFinal();

第二种:使用Hutool来实现

ini 复制代码
SecretKey secretKey = SecureUtil.generateKey(HmacAlgorithm.HmacSHA256.getValue())
Hmac hmac = new Hmac(HmacAlgorithm.HmacSHA256,secretKey)
hmac.digest("12345")

bcrypt实现

bcrypt: 主要应用场景密码存储和身份验证,由于计算速度慢,不适合存储哈希大量数据。

第一种:使用Spring Security 提供的 BCryptPasswordEncoder

xml 复制代码
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.8.0</version> <!-- 请根据需要选择最新版本 -->
</dependency>

注意: BCryptPasswordEncoder 内部已经实现了加盐机制,无需手动设置盐值。每次调用 encode 方法时,它会自动生成一个随机盐值,并将其嵌入到最终的哈希结果中

ini 复制代码
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 生成加密后的密码
String hashedPassword = encoder.encode("mySecurePassword123");
// 验证密码
boolean isMatch = encoder.matches(rawPassword, hashedPassword);

第二种通过hutool实现

arduino 复制代码
// 加密
String result = DigestUtil.bcrypt("12345")
// 匹配
boolean flag = DigestUtil.bcryptCheck("12345","hashed")

混合加密

混合加密结合了对称加密和非对称加密的优点,既保证了安全性,又提高了性能。

两者各自起到的作用为:

  1. 通过非对称加密(如RSA)传递会话密钥
  2. 通过对称加密(如AES)使用会话密钥来加解密传输数据

例如我们现在日常使用的HTTPS的加解密过程,具体流程步骤如下:

  • 客户端发起Https请求
  • 服务器返回对应证书(公钥和CA签名)
  • 客户端验证证书(有效期,是否受信任CA签发,证书和访问域名是否一致)
  • 密钥协商(对应我们上述所说的混合加密)
    • 生成随机数,使用证书公钥加密
    • 服务器用私钥解密,得到随机数
    • 客户端和服务端根据这个随机数和其他握手信息生成相同会话密钥
    • 后续通过这个密钥来加密传输数据
    • 直到会话结束,会话密钥被丢弃

综合应用场景

二进制内容的传输和展示

对于应用到二进制数据的传输,文件图片等信息

  • 针对图片内容,前端传输字符串的base64的编码数据
    • 前端传输的base64内容
  • 对于一些密钥信息存在二进制数组内容,可以通过直接将其转换为base64的文本形式
  • url的上的编码问题,可以通过base64来实现
ini 复制代码
// 后端获取到byte[]二进制内容 ,获取到流能后续操作
byte[] content = Base64.decodeStr(encode);
InputStream inputStream = new ByteArrayInputStream(imageBytes);

// 对于加密的数据可以通过base64的形式来传输
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
String base64Content = Base64.getDecoder().decode(byteArray)

前后端传输私密数据

例如在用户登录,支付信息,个人隐私数据等接口的调用上,可以增加混合加密。

  1. 首先后端通过非对称加密(RSA)生成一对公钥和私钥。公钥由前端存储,私钥由后端存储。
  2. 前端请求接口,通过算法生成AES的会话密钥
    • 前端使用RSA加密会话密钥
    • 前端使用AES的密钥来加密传输数据
    • 前端将加密的会话密钥和加密的传输数据发送给后端
  3. 后端通过私钥来解开加密的会话密钥,获得会话密钥,然后通过会话密钥来解开加密数据

验证身份

可以通过sha256 with ras来实现对数据签名, 然后通过对签名数据来验签,判定是否是可靠的用户传递的,以此来确定身份。

注意:传递的签名,是由私钥签名获得,验签名可以通过公钥。

ini 复制代码
Sign sign = SecureUtil.sign(SignAlgorithm.MD5withRSA,privateKeyBase64, publicKeyBase64);
//签名
byte[] signed = sign.sign(data);
//验证签名 ,通过数据和签名来验证
boolean verify = sign.verify(data, signed);

也可以使用 hmac-sha256

ini 复制代码
Hmac hmac = SecureUtil.hmacSha256(key)
byte[] digest = hmac.digest(data)
// 比较两个签名数组
hmac.verify(digest,digestToCompare)

密码的加密

对于注册用户的时候,对于密码的加密保存在后端,可以使用bcrypt的方式来加密进行存储。后续崽用户登录的时候,可以将这个登录的密码加密和数据库存储的密码比对是否一致即可。

ini 复制代码
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 生成加密后的密码
String hashedPassword = encoder.encode("mySecurePassword123");
// 验证密码
boolean isMatch = encoder.matches(rawPassword, hashedPassword);

标识文件哈希值

对于不同文件,我们在上传文件的时候不可能比对整个文件是否一致,而是通过比对哈希值是否一致性,就能判定是否两个文件相同。计算文件的哈希值需要参考的内容需要思考一下,

  1. 可以通过文件头部,中部和尾部的组合
  2. 可以随机性的都固定选取指定部分内容
  3. 结合一些文件大小等信息综合判定
scss 复制代码
// 读取文件的头部、中间和尾部
byte[] head = readBytes(file, 0, 1024); // 头部 1KB
byte[] middle = readBytes(file, fileSize / 2, 1024); // 中间 1KB
byte[] tail = readBytes(file, fileSize - 1024, 1024); // 尾部 1KB

// 将数组拼接
byte[] combined = concatenate(head, middle, tail);
// 最后计算哈希值
return calculateHash(combined, "SHA-256");

到底是使用md5还是sha呢?如果是对于想快速,安全性可以降低点的选择md5,如果对于安全性高的选择sha

参考第三方使用案例

微信支付

对于微信支付的验签名场景:

  1. 微信支付应答商户的请求时,商户需要验签(验证请求是来自微信支付)
  2. 接收微信支付的回调时,商户需要验签(验证请求是来自微信支付)

主要是通过:sha256 with rsa 来实现验签名,对于响应数据的结果拼接和签名比对是否签名正确

支付宝

  1. 对于支付宝api请求,需要用户传递支付宝对参数的签名,赋值在请求头上

主要通过对参数的排序,然后通过RSA来实现加密

  1. 对于支付宝api请求的处理结果,通过支付宝服务器的私钥进行加密

主要通过RSA来实现加密,用户通过支付宝公钥来实现验签名

shopee 和 tiktok

对于用户的API请求,使用对参数进行hmacsha256的加密

相关推荐
sugar__salt2 分钟前
多线程(1)——认识线程
java·开发语言
电商api接口开发4 分钟前
ASP.NET MVC 入门指南三
后端·asp.net·mvc
声声codeGrandMaster4 分钟前
django之账号管理功能
数据库·后端·python·django
妙极矣22 分钟前
JAVAEE初阶01
java·学习·java-ee
我的golang之路果然有问题31 分钟前
案例速成GO+redis 个人笔记
经验分享·redis·笔记·后端·学习·golang·go
碎叶城李白37 分钟前
NIO简单群聊
java·nio
嘻嘻嘻嘻嘻嘻ys42 分钟前
《Vue 3.3响应式革新与TypeScript高效开发实战指南》
前端·后端
暮乘白帝过重山1 小时前
路由逻辑由 Exchange 和 Binding(绑定) 决定” 的含义
开发语言·后端·中间件·路由流程
xxjiaz1 小时前
水果成篮--LeetCode
java·算法·leetcode·职场和发展
CHQIUU1 小时前
告别手动映射:在 Spring Boot 3 中优雅集成 MapStruct
spring boot·后端·状态模式