引言
上篇文章Java中使用sha256算法对数据进行不可逆转换入库使用了sha256算法对数据进行不可逆转换入库,虽然sha256已经很安全了,但随着技术的发展,破解数据的方法也变得多样化,其中彩虹表(加密散列函数逆运算的预先计算好的表)攻击就是为了破解散列值而准备的,为了保护敏感信息,提高信息安全,就诞生了盐值的概念,本文将深入探讨Java中使用盐值Salt提高信息安全,包括其概念、作用和实践应用
盐值的概念
盐值(Salt)是一个随机生成的字符串序列,与用户密码结合在一起,然后进行哈希处理。这样能保证即使密码相同,经过哈希处理后生成的哈希值也会因盐值的不同而不同,这样可以有效抵御暴力破解和彩虹表(加密散列函数逆运算的预先计算好的表)攻击
盐值的作用
- 增加密码破解难度:因其随机性从而增加哈希值的复杂性和多样性
- 防止彩虹表(加密散列函数逆运算的预先计算好的表)攻击:由于盐值的存在,无法通过彩虹表找到密码对应的哈希值进行攻击
- 提高安全性:盐值会增加密码的复杂性
实践应用
封装盐值(Salt)工具类
在工具包中创建盐值(Salt)工具类
编写盐值(Salt)工具类
/**
* 盐值工具类
* @author muze
*/
public class SaltUtil {
/**
* 生成盐值
* @return 盐值
*/
public static String generateSalt() {
// 声明并初始化长度为16的字节数组,用于存储随机生成的盐值
byte[] saltBytes = new byte[16];
// 创建SecureRandom实例,用于生成强随机数
SecureRandom secureRandom = new SecureRandom();
// 将随机生成的盐值填充到字节数组
secureRandom.nextBytes(saltBytes);
// 将字节数组编码为Base64格式的字符串后返回
return SaBase64Util.encodeBytesToString(saltBytes);
}
}
修改用户表
因为我们要将盐值存储起来,所以需要在用户表中加字段,注意,这里会使用到SQL的DML语言,而不是重建表或在可视化界面进行操作,可以参考我的另一篇文章SQL中ALTER用法总结
ALTER TABLE t_user ADD COLUMN salt VARCHAR(30) COLLATE utf8mb4_0900_bin NOT NULL COMMENT '盐值';
运行结果
修改用户实体
/**
* 用户实体
* @author muze
*/
@Data
@TableName("t_user")
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 684552117916625567L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String password;
//************************ 新增盐值字段 ************************//
/**
* 盐值
*/
private String salt;
}
修改用户注册和登录接口实现
/**
* 用户业务实现层
* @author muze
*/
//************************ 新增日志打印 ************************//
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
/**
* 注入用户数据层
*/
@Autowired
private UserMapper userMapper;
@Override
public String login(UserLoginDTO userLoginDTO) {
// 取出用户名和密码
String username = userLoginDTO.getUsername();
String password = userLoginDTO.getPassword();
// 构建查询条件
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getUsername, username);
// 查询用户
User user = userMapper.selectOne(userLambdaQueryWrapper);
//************************ 修改用户校验逻辑 ************************//
if (user == null) throw new CustomException("用户名或密码错误");
//************************ 新增密码解密 ************************//
String decryptPassword;
try {
decryptPassword = RSAUtil.decryptByPrivateKey(password);
} catch (Exception e) {
//************************ 打印异常信息 ************************//
log.error("使用私钥解密密码时异常:", e);
//************************ 抛出自定义异常 ************************//
throw new CustomException("用户名或密码错误");
}
//************************ 新增获取盐值并融合密码后进行哈希处理 ************************//
String salt = user.getSalt();
String saltPassword = salt + decryptPassword;
String sha256Password = Sha256Util.calculateSha256(saltPassword);
// 如果用户为空或者解密后的输入密码与用户密码不匹配则返回:用户名或密码错误
if (!sha256Password.equals(user.getPassword())) throw new CustomException("用户名或密码错误");
// 使用SaToken的工具类StpUtil调用登录方法login,入参:用户id
StpUtil.login(user.getId());
// 返回:登录成功
return "登录成功";
}
/**
* 注册
* @param userRegisterDTO 用户注册请求实体
* @return 注册结果
*/
@Override
public String register(UserRegisterDTO userRegisterDTO) {
// 提取用户名和密码
String username = userRegisterDTO.getUsername();
String password = userRegisterDTO.getPassword();
// 构建查询条件
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getUsername, username);
// 查询用户
User dbUser = userMapper.selectOne(userLambdaQueryWrapper);
// 如果用户不为空,则抛出自定义异常"该用户已注册,请直接登录"
if (dbUser != null) throw new CustomException("该用户已注册,请直接登录");
//************************ 新增获取盐值并融合密码后进行哈希处理 ************************//
String salt = SaltUtil.generateSalt();
String saltPassword = salt + password;
// 计算密码sha256哈希值
String sha256Password = Sha256Util.calculateSha256(saltPassword);
// 构建并入库用户信息
User user = new User();
user.setUsername(username);
user.setPassword(sha256Password);
//************************ 新增盐值入库 ************************//
user.setSalt(salt);
userMapper.insert(user);
// 返回:注册成功
return "注册成功";
}
}
测试
启动项目,使用接口调试工具调用注册接口创建账号后再调用登录接口进行验证
注册
数据库
登录
到这里我们就一起完成了在Java中使用盐值(Salt)结合sha256算法对数据进行不可逆转换入库,从而提高信息安全,相信你已经掌握了,赶快去试试吧,希望这篇文章能对你有所帮助!
彩蛋:到这里我们其实已经完成了一个基本的Java单体项目架构了,细心的你肯定发现了,还差角色和权限,当我们解决了角色和权限的问题后就架构好了一个简单的Java项目了,剩下都就是根据业务去实现功能了,先冷静,不慌庆祝,敬请期待!