一、介绍
在 java 开发中,加密方式主要分为 对称加密、非对称加密、哈希算法(摘要算法) 三大类,各自有不同的应用场景(如数据传输、存储加密、签名验证等)。
二、哈希算法(摘要算法)
特点:
- 不可逆(无法从摘要反推原始数据);
- 相同输入必然得到相同输出,不同输入大概率得到不同输出(抗碰撞);
- 输出长度固定(与输入长度无关);
- 主要用于 数据完整性校验(如文件校验)、密码存储(如用户密码加密存储)。
常用算法及实现:
1. md5(message-digest algorithm 5)
- 输出长度:128 位(16 字节,通常转 32 位十六进制字符串);
- 特点:速度快,但安全性较低(已被破解,存在碰撞风险);
- 适用场景:非敏感数据的完整性校验(如文件校验),不推荐用于密码存储。
import java.security.messagedigest;
import java.security.nosuchalgorithmexception;
public class md5utils {
public static string md5(string input) {
try {
messagedigest md = messagedigest.getinstance("md5");
byte[] digest = md.digest(input.getbytes()); // 生成摘要字节数组
// 转16进制字符串
stringbuilder sb = new stringbuilder();
for (byte b : digest) {
sb.append(string.format("%02x", b)); // 02x确保不足两位补0
}
return sb.tostring();
} catch (nosuchalgorithmexception e) {
throw new runtimeexception("md5算法不存在", e);
}
}
public static void main(string[] args) {
system.out.println(md5("123456")); // 输出:e10adc3949ba59abbe56e057f20f883e
}
}2. sha 系列(secure hash algorithm)
- 包括 sha-1(160 位)、sha-256(256 位)、sha-512(512 位)等;
- 特点:安全性比 md5 高,sha-256/sha-512 抗碰撞性强,速度略慢于 md5;
- 适用场景:敏感数据的完整性校验、密码存储(推荐 sha-256 + 盐值)。
public class sha256utils {
public static string sha256(string input) {
try {
messagedigest md = messagedigest.getinstance("sha-256");
byte[] digest = md.digest(input.getbytes());
stringbuilder sb = new stringbuilder();
for (byte b : digest) {
sb.append(string.format("%02x", b));
}
return sb.tostring();
} catch (nosuchalgorithmexception e) {
throw new runtimeexception("sha-256算法不存在", e);
}
}
public static void main(string[] args) {
system.out.println(sha256("123456")); // 输出:8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c9
}
}3. 加盐哈希(salted hash)
- 问题:纯哈希算法存在 “彩虹表破解” 风险(通过预计算的哈希值反查原始密码);
- 解决:给原始数据添加 随机盐值(salt) 后再哈希,盐值需与哈希结果一起存储;
- 推荐方案:使用
pbkdf2、bcrypt、argon2(java 需引入第三方库如spring-security-crypto)。
import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;
public class bcryptutils {
private static final bcryptpasswordencoder encoder = new bcryptpasswordencoder();
// 加密(自动生成盐值)
public static string encrypt(string password) {
return encoder.encode(password);
}
// 校验(自动提取盐值比对)
public static boolean verify(string password, string encryptedpassword) {
return encoder.matches(password, encryptedpassword);
}
public static void main(string[] args) {
string pwd = "123456";
string encrypted = encrypt(pwd);
system.out.println("加密后:" + encrypted); // 格式如 $2a$10$xxx...(包含盐值)
system.out.println("校验结果:" + verify(pwd, encrypted)); // true
}
}所引入的依赖(maven):
<dependency>
<groupid>org.springframework.security</groupid>
<artifactid>spring-security-crypto</artifactid>
<version>5.7.3</version>
</dependency>三、对称加密
特点:
- 加密和解密使用 同一个密钥(对称密钥);
- 速度快、效率高,适合 大量数据加密(如文件加密、数据库字段加密);
- 缺点:密钥传输和存储风险高(需安全通道传输密钥)。
常用算法及实现:
1. aes(advanced encryption standard)
- 替代 des 的标准算法,安全性高、速度快;
- 密钥长度:128 位(默认)、192 位、256 位(需 jce 无限制权限文件);
- 模式:ecb(不推荐,无 iv)、cbc(需 iv 向量,推荐)、gcm(带认证,更安全);
- 填充方式:pkcs5padding(常用)。
public class aesutils {
// 密钥(16字节=128位,24字节=192位,32字节=256位)
private static final string key = "1234567890abcdef"; // 16字节
// iv向量(cbc模式必需,16字节,需与密钥长度一致)
private static final string iv = "abcdef9012345678";
// 算法/模式/填充
private static final string algorithm = "aes/cbc/pkcs5padding";
// 加密
public static string encrypt(string input) {
try {
secretkeyspec keyspec = new secretkeyspec(key.getbytes(), "aes");
ivparameterspec ivspec = new ivparameterspec(iv.getbytes());
cipher cipher = cipher.getinstance(algorithm);
cipher.init(cipher.encrypt_mode, keyspec, ivspec);
byte[] encrypted = cipher.dofinal(input.getbytes());
return base64.getencoder().encodetostring(encrypted); // 转base64方便存储传输
} catch (exception e) {
throw new runtimeexception("aes加密失败", e);
}
}
// 解密
public static string decrypt(string encryptedinput) {
try {
secretkeyspec keyspec = new secretkeyspec(key.getbytes(), "aes");
ivparameterspec ivspec = new ivparameterspec(iv.getbytes());
cipher cipher = cipher.getinstance(algorithm);
cipher.init(cipher.decrypt_mode, keyspec, ivspec);
byte[] encrypted = base64.getdecoder().decode(encryptedinput);
byte[] decrypted = cipher.dofinal(encrypted);
return new string(decrypted);
} catch (exception e) {
throw new runtimeexception("aes解密失败", e);
}
}
public static void main(string[] args) {
string input = "hello aes!";
string encrypted = encrypt(input);
system.out.println("加密后:" + encrypted);
string decrypted = decrypt(encrypted);
system.out.println("解密后:" + decrypted); // hello aes!
}
}2. des / 3des
- des:密钥长度 56 位,安全性低,已淘汰;
- 3des:使用 3 个 des 密钥(168 位),兼容性强,但速度慢;
- 推荐优先使用 aes。
四、非对称加密
特点:
- 生成一对密钥:公钥(公开) 和 私钥(保密);
- 公钥加密的数据只能用私钥解密,私钥加密的数据只能用公钥解密;
- 速度慢、效率低,适合 小量数据加密(如对称密钥传输、数字签名);
- 优点:无需安全通道传输密钥(公钥可公开)。
常用算法及实现:
1. rsa
- 应用最广泛的非对称加密算法;
- 密钥长度:1024 位(安全性不足)、2048 位(推荐)、4096 位(更安全但速度慢);
- 适用场景:密钥交换(如 aes 密钥用 rsa 加密传输)、数字签名、小数据加密。
public class rsautils {
// 密钥对(实际开发中需提前生成并存储,不硬编码)
private static keypair keypair;
static {
try {
// 生成rsa密钥对(2048位)
keypairgenerator generator = keypairgenerator.getinstance("rsa");
generator.initialize(2048);
keypair = generator.generatekeypair();
} catch (nosuchalgorithmexception e) {
throw new runtimeexception("rsa密钥对生成失败", e);
}
}
// 获取公钥(base64编码)
public static string getpublickey() {
return base64.getencoder().encodetostring(keypair.getpublic().getencoded());
}
// 获取私钥(base64编码)
public static string getprivatekey() {
return base64.getencoder().encodetostring(keypair.getprivate().getencoded());
}
// 公钥加密
public static string encryptbypublickey(string input, string publickeystr) {
try {
// 解码公钥
byte[] publickeybytes = base64.getdecoder().decode(publickeystr);
x509encodedkeyspec keyspec = new x509encodedkeyspec(publickeybytes);
keyfactory keyfactory = keyfactory.getinstance("rsa");
publickey publickey = keyfactory.generatepublic(keyspec);
// 加密(rsa加密长度有限制,2048位密钥最多加密245字节)
cipher cipher = cipher.getinstance("rsa/ecb/pkcs1padding");
cipher.init(cipher.encrypt_mode, publickey);
byte[] encrypted = cipher.dofinal(input.getbytes());
return base64.getencoder().encodetostring(encrypted);
} catch (exception e) {
throw new runtimeexception("rsa公钥加密失败", e);
}
}
// 私钥解密
public static string decryptbyprivatekey(string encryptedinput, string privatekeystr) {
try {
// 解码私钥
byte[] privatekeybytes = base64.getdecoder().decode(privatekeystr);
pkcs8encodedkeyspec keyspec = new pkcs8encodedkeyspec(privatekeybytes);
keyfactory keyfactory = keyfactory.getinstance("rsa");
privatekey privatekey = keyfactory.generateprivate(keyspec);
// 解密
cipher cipher = cipher.getinstance("rsa/ecb/pkcs1padding");
cipher.init(cipher.decrypt_mode, privatekey);
byte[] encrypted = base64.getdecoder().decode(encryptedinput);
byte[] decrypted = cipher.dofinal(encrypted);
return new string(decrypted);
} catch (exception e) {
throw new runtimeexception("rsa私钥解密失败", e);
}
}
public static void main(string[] args) {
string input = "aes密钥1234567890abcdef"; // 小数据示例
string publickey = getpublickey();
string privatekey = getprivatekey();
system.out.println("公钥:" + publickey);
system.out.println("私钥:" + privatekey);
string encrypted = encryptbypublickey(input, publickey);
system.out.println("公钥加密后:" + encrypted);
string decrypted = decryptbyprivatekey(encrypted, privatekey);
system.out.println("私钥解密后:" + decrypted); // aes密钥1234567890abcdef
}
}2. ecc(elliptic curve cryptography)
- 基于椭圆曲线数学,相同安全性下,密钥长度远小于 rsa(如 256 位 ecc ≈ 3072 位 rsa);
- 速度快、占用资源少,适合移动设备、物联网等场景;
- java 支持(需 jdk 1.7+),但使用较少,推荐在资源受限场景使用。
五、数字签名(非对称加密的延伸)
特点:
- 用于 身份认证 和 数据完整性校验(防止数据篡改和伪造);
- 原理:发送方用 私钥 对数据摘要签名,接收方用 公钥 验证签名。
常用算法:rsa-sha256、ecdsa
public class signatureutils {
private static final string algorithm = "sha256withrsa"; // rsa+sha256签名
// 私钥签名
public static string sign(string data, string privatekeystr) {
try {
byte[] privatekeybytes = base64.getdecoder().decode(privatekeystr);
pkcs8encodedkeyspec keyspec = new pkcs8encodedkeyspec(privatekeybytes);
keyfactory keyfactory = keyfactory.getinstance("rsa");
privatekey privatekey = keyfactory.generateprivate(keyspec);
signature signature = signature.getinstance(algorithm);
signature.initsign(privatekey);
signature.update(data.getbytes());
byte[] signbytes = signature.sign();
return base64.getencoder().encodetostring(signbytes);
} catch (exception e) {
throw new runtimeexception("签名失败", e);
}
}
// 公钥验签
public static boolean verify(string data, string signstr, string publickeystr) {
try {
byte[] publickeybytes = base64.getdecoder().decode(publickeystr);
x509encodedkeyspec keyspec = new x509encodedkeyspec(publickeybytes);
keyfactory keyfactory = keyfactory.getinstance("rsa");
publickey publickey = keyfactory.generatepublic(keyspec);
signature signature = signature.getinstance(algorithm);
signature.initverify(publickey);
signature.update(data.getbytes());
byte[] signbytes = base64.getdecoder().decode(signstr);
return signature.verify(signbytes);
} catch (exception e) {
throw new runtimeexception("验签失败", e);
}
}
public static void main(string[] args) {
string data = "需要签名的数据";
string privatekey = rsautils.getprivatekey(); // 复用rsa密钥对
string publickey = rsautils.getpublickey();
string sign = sign(data, privatekey);
system.out.println("签名:" + sign);
boolean verifyresult = verify(data, sign, publickey);
system.out.println("验签结果:" + verifyresult); // true
// 篡改数据后验签
boolean fakeverify = verify(data + "篡改", sign, publickey);
system.out.println("篡改后验签结果:" + fakeverify); // false
}
}六、常用加密方式选型建议
| 实际业务场景 | 推荐加密方式 | 使用原因 |
|---|---|---|
| 用户密码存储 | bcrypt/pbkdf2 | 不可逆、加盐、防彩虹表破解 |
| 数据库敏感字段(手机号 / 身份证) | aes-256-cbc/gcm | 可逆、速度快、适合短文本 |
| 大文件加密(合同 / 报表) | aes-256-gcm | 速度快、带认证、防篡改 |
| 客户端 - 服务器密钥传输 | rsa-2048 | 公钥公开、私钥保密、无需安全通道 |
| 第三方接口调用(防篡改 / 身份认证) | rsa-sha256 数字签名 | 验证身份、防止数据篡改和重放攻击 |
| 数据完整性校验(文件校验) | sha-256 | 不可逆、相同输入固定输出 |
七、注意
- 密钥管理:避免硬编码密钥,使用配置中心、密钥仓库(如 vault)存储;
- 算法选择:淘汰 md5、des、rsa-1024 等弱算法,优先选择 aes-256、rsa-2048+、sha-256+;
- 填充和模式:aes 避免使用 ecb 模式,推荐 cbc(需随机 iv)或 gcm(带认证);
- 盐值:密码存储必须加盐,盐值需随机且唯一(如 bcrypt 自动生成);
- 权限:aes-256 需 jce 无限制权限文件(jdk 8u161+ 默认开启)。
八、补充
1.什么是碰撞风险?
两个不同的输入数据(明文),经过加密 / 哈希计算后,得到了相同的输出结果(密文 / 摘要) —— 这种 “不同输入→相同输出” 的现象被称为 “哈希碰撞”,而碰撞风险就是这种现象发生的概率及带来的安全隐患。
举个简单的例子:
哈希算法就像一台 “压缩机器”,无论输入的是一张 10gb 的电影、一段文字,还是一个数字,都会被压缩成固定长度的 “压缩包”(摘要)。正常情况下,不同输入应该对应不同压缩包,但如果出现 “两个完全不同的文件,压缩后得到一模一样的压缩包”,这就是 “碰撞”;而 “碰撞风险” 就是你担心这种情况发生的概率,以及如果发生会造成什么问题。
本质:数学底层原因:“鸽巢原理”
哈希算法的输出长度是固定的(比如 md5 输出 128 位,sha-256 输出 256 位),而输入数据的可能性是无限的(比如任意长度的字符串、文件)。
这就像:用 100 个鸽巢(固定输出)容纳无限多的鸽子(输入数据)—— 无论鸽巢再多,最终必然会有 “多只鸽子挤同一个鸽巢” 的情况,这就是数学上的 “鸽巢原理”,也是碰撞风险存在的根本原因。
常见场景举例:
数字签名场景(非对称加密 + 哈希)
数字签名的核心是 “用私钥对数据摘要签名,公钥验签”:
- 若哈希算法存在碰撞风险,攻击者可找到两个不同数据(比如 “转账 0.01 元” 和 “转账 10000 元”),其摘要相同;
- 攻击者诱导用户对 “转账 0.01元” 签名,然后将签名用于 “转账 10000 元” 的请求中,服务端验签时因摘要相同而通过,造成用户财产损失。
2.什么是盐值?
盐值(salt)是一串随机生成、与原始数据(如密码)结合后再进行哈希计算的字符串—— 它就像烹饪时加的 “调味料”,相同的食材(原始密码)加不同的 “调味料”(盐值),会得到完全不同的 “成品”(哈希摘要)。
举个例子:
用户注册(加密存储)
- 用户输入明文密码:
123456ab!; - 系统生成随机盐值:
f8d2e1c3b4a59607(16 字节); - 计算哈希:
sha-256(密码 + 盐值)→ 得到摘要8d969eef...; - 存储结果:将 “盐值 + 哈希摘要” 一起存入数据库(如拼接为
f8d2e1c3b4a59607:8d969eef...)。
常见误区:
- 盐值需要保密?→ 可以不需要,盐值的作用是 “破坏哈希固定性”,而非 “加密”,必须明文存储才能校验;
- 盐值可以重复使用?→ 不推荐,同一系统中盐值重复会降低安全性,建议每个用户 / 每次修改密码都生成新盐值;
- 用用户 id、手机号当盐值?→ 不推荐,这类值可预测,攻击者可针对特定用户生成定制彩虹表,破解难度大幅降低;
- 盐值越长越好?→ 不是,16 字节(128 位)已足够安全,过长的盐值会增加存储和计算成本,性价比极低。
使用原则:盐值必须 “随机、唯一、足够长”,且明文存储。
简单场景:可手动用 securerandom 生成盐值,搭配 sha-256 使用;
实际开发场景:优先使用 bcrypt、pbkdf2 等自动加盐算法,无需关注盐值的生成和存储,安全性更高、使用更高效。
3.什么是彩虹表?
彩虹表(rainbow table)是一种预计算的、包含海量 “明文→哈希值” 映射关系的数据库
举个例子:
裸哈希场景:md5("123456") = e10adc3949ba59abbe56e057f20f883e;
彩虹表中会提前存储 e10adc3949ba59abbe56e057f20f883e → 123456 这个映射关系;
攻击者从数据库拿到该哈希值后,查询彩虹表,1 秒内就能得到明文 “123456”。
4.什么是 jce 无限制权限文件?(java 加密的 “权限解锁包”)
jce(java cryptography extension,java 加密扩展)是 java 平台提供的一套加密相关 api 和算法实现(如 aes、rsa 等)。而 jce 无限制权限文件(unlimited strength jurisdiction files),是用来解除 java 对 “强加密算法”(如 aes-256、rsa-4096)密钥长度限制的补丁文件
为什么需要 “解锁”?
早年由于国际加密技术出口管制政策(部分国家限制高强度加密算法出口),oracle jdk 默认内置了 “受限的 jce 策略文件”,对加密算法的密钥长度做了严格限制:
- aes 密钥长度最多只能用 128 位(aes-128),无法使用更安全的 192 位、256 位;
- 其他算法(如 3des、rc2)也有类似的密钥长度限制。
jce 无限制权限文件的适用范围:
| jdk 版本 | 是否需要手动安装? | 说明 |
|---|---|---|
| jdk 1.7 及以下 | 是(必须手动安装) | 需下载对应版本的无限制策略文件,替换系统文件 |
| jdk 1.8(8u161 之前) | 是(需手动安装) | 8u161 是关键版本,之后默认解锁 |
| jdk 1.8u161 及以上 | 否(默认解锁) | 无需手动操作,直接支持强加密算法 |
| jdk 9 及以上(jdk 11/17) | 否(默认解锁) | 彻底移除了 jce 策略限制,原生支持强加密 |
注意:openjdk(如 adoptopenjdk、eclipse temurin)默认无此限制(从诞生起就支持强加密),只有 oracle jdk 存在历史限制 —— 这也是很多项目选择 openjdk 的原因之一。
5.什么是iv/iv向量?
iv 向量(initialization vector,初始化向量),本质就是对称加密算法(如 aes、des)中,用于初始化加密流程的一串固定长度随机字节数组—— 它不是密钥,也无需保密,核心作用是让 “相同明文 + 相同密钥” 产生不同密文,破坏密文的规律性,抵御攻击。
可以把它理解为:对称加密的 “随机启动参数”—— 就像用同一台打印机(密钥)打印同一篇文档(明文),每次用不同的 “打印设置参数”(iv 向量),最终打印出的 “成品”(密文)会完全不同,避免攻击者通过重复密文推断明文。
对称加密若没有 iv 向量(如 aes-ecb 模式),会存在致命缺陷:相同明文 → 相同密文 ,即使明文和密钥完全相同,只要 iv 向量不同,最终密文就不同,攻击者无法通过密文的重复性推断明文,加密安全性大幅提升。
iv 向量的适用场景
| 加密模式 | 是否需要 iv 向量 | 说明 | 推荐度 |
|---|---|---|---|
| ecb(电子密码本模式) | 不需要 | 无 iv,相同明文→相同密文,易被破解 | 不推荐使用 |
| cbc(密码分组链接模式) | 需要 | 依赖 iv 启动加密,iv 随机即可,兼容性好 | 推荐(常用) |
| gcm(伽罗瓦 / 计数器模式) | 需要 | 不仅需要 iv,还自带 “认证标签”(防篡改),iv 推荐 12 字节(最优实践) | 推荐(更安全) |
| ctr(计数器模式) | 需要 | iv 作为计数器初始值,需保证同一密钥下不重复 | 可选(适合流式数据) |
误区:
- iv 向量是密钥? → 并不是,iv 仅用于初始化加密,无需保密;密钥是加密核心,必须严格保密(如用密钥仓库存储)。
- 用固定 iv 更方便? → 不建议,固定 iv 会让相同明文的密文一致,攻击者可通过密文规律破解,完全失去 iv 的意义。
- iv 越长越安全? → iv 长度必须与算法块大小一致(aes 固定 16 字节),过长 / 过短都会报错,且不会提升安全性。
- iv 可以重复使用? → 同一密钥下重复使用 iv 会导致密文被破解(如 cbc 模式的 “重放攻击” 漏洞)。
- iv 需要加密存储? → iv 可随密文公开传输 / 存储(如拼接在密文前),解密时必须提取原始 iv 才能还原明文。
总结
到此这篇关于java中常用的几种加密方式的文章就介绍到这了,更多相关java加密方式内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论