提前声明:

你不会写这加密算法没关系啊,你会用就行。

要求就是:你可以不会写这个加密算法,但是你要知道加密流程,你要会用。

@Service
public class PasswordEncryptor{}

很好,请在service层中创建一个名字为PasswordEncryptor的服务类,用来负责密码的加密。

加密的方法有很多。

简单一点的,直接加密为MD5,或者使用base64进行编码到达伪加密的效果(毕竟base64编码是可以解码的)

在这里,用一种比较通常的方式进行加密

这种加密方式是一种结合了哈希函数和“盐”(Salt)的密码存储策略。以下是该加密方式的详细介绍:

  1. 哈希函数(Hashing Function)

    哈希函数是一种从任何大小的数据(通常是字符串)生成固定长度字符串的方法。在这个例子中,使用了SHA-256,它是一种常用的加密哈希函数。SHA-256生成的哈希值总是256位(32字节)长。哈希函数有一个重要的特性,那就是它们是单向的。这意味着,虽然从原始数据很容易生成哈希值,但从哈希值反向推导出原始数据却几乎是不可能的。

  2. 盐(Salt)

    “盐”是一个随机生成的数据,它与原始密码一起哈希处理。在这个例子中,盐的长度是16字节。盐的主要目的是防止所谓的“彩虹表”攻击。彩虹表是一种预先计算好的、从原始密码到其哈希值的映射。通过使用盐,即使两个用户使用了相同的密码,他们的哈希值也会是不同的,因为每个用户的盐都是不同的。这使得攻击者无法简单地使用预先计算好的彩虹表来查找原始密码。

  3. 加密过程

    在hashPassword方法中,首先生成一个新的随机盐。然后,将这个盐和密码一起哈希处理。最后,将盐和哈希值合并,并使用Base64编码以便于存储和传输。

  4. 验证过程

    在verifyPassword方法中,首先从存储的Base64编码字符串中解码出盐和哈希值。然后,使用相同的盐对用户输入的密码进行哈希处理。最后,比较计算出的哈希值和存储的哈希值是否相同。如果相同,那么密码就是正确的。

  5. 安全性

    这种加密方式提供了相当高的安全性。由于哈希函数的单向性,攻击者无法从哈希值中直接获取原始密码。同时,由于使用了盐,攻击者也无法使用彩虹表来查找原始密码。然而,需要注意的是,没有任何加密方式是绝对安全的。例如,如果攻击者能够获取到足够多的哈希值和对应的原始密码(这通常是通过某种形式的“钓鱼”攻击或恶意软件实现的),那么他们可能会使用这些信息来尝试“破解”哈希函数,尽管这在实践中是非常困难的。

代码如下:

package cc.xrilang.serversystem.service;

import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
@Service
public class PasswordEncryptor { // 盐的长度,这里设置为16字节
private static final int SALT_LENGTH = 16; // SecureRandom用于生成安全的随机数
private final SecureRandom secureRandom; // 构造函数,初始化SecureRandom实例
public PasswordEncryptor() {
this.secureRandom = new SecureRandom();
} // 生成随机的盐
private byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
return salt;
} // 哈希密码并附带盐一起存储
public String hashPassword(String password) throws NoSuchAlgorithmException {
// 生成随机的盐
byte[] salt = generateSalt();
// 使用盐对密码进行哈希处理
byte[] hash = hash(password.getBytes(StandardCharsets.UTF_8), salt); // 将盐和哈希值合并存储
byte[] saltedHash = new byte[salt.length + hash.length];
System.arraycopy(salt, 0, saltedHash, 0, salt.length);
System.arraycopy(hash, 0, saltedHash, salt.length, hash.length); // 使用Base64对合并后的数据进行编码,便于存储和传输
return Base64.getEncoder().encodeToString(saltedHash);
} // 使用SHA-256算法和盐对输入数据进行哈希处理
private byte[] hash(byte[] input, byte[] salt) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(salt);
return md.digest(input);
} // 验证密码是否正确
public boolean verifyPassword(String storedSaltedHash, String passwordToVerify) throws NoSuchAlgorithmException {
// 对存储的Base64编码的盐和哈希值进行解码
byte[] saltedHash = Base64.getDecoder().decode(storedSaltedHash); // 从解码后的数据中提取盐
byte[] salt = new byte[SALT_LENGTH];
System.arraycopy(saltedHash, 0, salt, 0, salt.length); // 从解码后的数据中提取哈希值
byte[] hash = new byte[saltedHash.length - salt.length];
System.arraycopy(saltedHash, salt.length, hash, 0, hash.length); // 使用提取的盐对用户输入的密码进行哈希处理
byte[] computedHash = hash(passwordToVerify.getBytes(StandardCharsets.UTF_8), salt); // 比较计算出的哈希值与存储的哈希值是否一致
return MessageDigest.isEqual(computedHash, hash);
} // 主函数,用于测试
public static void main(String[] args) {
PasswordEncryptor encryptor = new PasswordEncryptor();
String password = "userPassword123"; try {
// 对密码进行哈希处理并附带盐一起存储
String encryptedPassword = encryptor.hashPassword(password);
System.out.println("加密后的密码(包含盐): " + encryptedPassword); // 验证密码是否正确
boolean isVerified = encryptor.verifyPassword(encryptedPassword, password);
System.out.println("密码验证结果: " + isVerified); // 使用错误的密码进行验证
boolean isWrongPasswordVerified = encryptor.verifyPassword(encryptedPassword, "wrongPassword");
System.out.println("错误密码验证结果: " + isWrongPasswordVerified);
} catch (NoSuchAlgorithmException e) {
System.err.println("找不到哈希算法: " + e.getMessage());
}
}
}

接下来,我们在新增用户的时候,就要应用上这个加密

  // 增加用户
@PostMapping
public ResponseEntity<?> createUser(@RequestBody Users user) {
if (usersService.selectUserAccount(user.getUserAccount()) != null) {
return ResponseEntity.error("该账号已注册");
}
String password = user.getUserPassword();
//对密码进行加密
String plainPassword = user.getUserPassword();
String encryptedPassword = null;
try {
encryptedPassword = passwordEncryptor.hashPassword(plainPassword);
} catch (NoSuchAlgorithmException e) {
// 处理加密异常,这里可以记录日志并返回错误信息
return ResponseEntity.error("密码加密失败");
} // 设置加密后的密码到用户对象
user.setUserPassword(encryptedPassword); user.setUserStatus(1);
user.setUserRegTime(new Timestamp(System.currentTimeMillis()));
Users createdUser = usersService.createUser(user);
return ResponseEntity.success(createdUser);
}

那么,我们再编写一个登录接口吧。


// 登录接口
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String userAccount,@RequestParam String userPassword) {
// 从登录请求中获取用户名和密码
// System.out.println(userAccount);
// System.out.println(userPassword);
// 尝试从数据库中查找用户
Users user = usersService.selectUserAccount(userAccount); // 验证用户是否存在
if (user == null) {
return ResponseEntity.error("用户不存在");
}
try {
// 验证密码是否正确
boolean passwordMatches = passwordEncryptor.verifyPassword(user.getUserPassword(),userPassword);
if (!passwordMatches) {
// 密码不匹配
return ResponseEntity.error("密码错误");
}
}catch (NoSuchAlgorithmException e){
return ResponseEntity.error("密码验证失败");
}catch (Exception e){
return ResponseEntity.error(e.getMessage());
} return ResponseEntity.success(user); }

验证密码的时候,首先根据账号去获取数据里面加密的密码

加密的密码包括 盐值+哈希值

对存储的Base64编码的盐和哈希值进行解码

从解码后的数据中分别提取盐和哈市值

使用提取的盐对用户输入的密码进行哈希处理

比较计算出的哈希值与存储的哈希值是否一致

这就是判断流程了

当然不要忘记了修改密码的时候,也要对密码进行加密哦

    // 更新用户
@PutMapping
public ResponseEntity<Users> updateUser(@RequestBody Users user) {
//获取参数的内容
String userNickname = user.getUserNickname();
String userPassword = user.getUserPassword();
String userIdentity = user.getUserIdentity();
Timestamp userLoginTime = user.getUserLastLoginTime();
String remarks = user.getRemarks();
long userStatus = user.getUserStatus();
long userId = user.getUserId();
//根据ID查出原本的数据
Users u = usersService.readUser(user.getUserId());
if (u == null) {
return ResponseEntity.error("用户不存在");
} // 更新用户信息(此处代码保持不变)
//将需要修改的内容替换进去
if (userNickname != null) {
u.setUserNickname(userNickname);
}
if (userPassword != null) {
//密码要加密后存储 //对密码进行加密
String encryptedPassword = null;
try {
encryptedPassword = passwordEncryptor.hashPassword(userPassword);
} catch (NoSuchAlgorithmException e) {
// 处理加密异常,这里可以记录日志并返回错误信息
return ResponseEntity.error("密码加密失败");
} // 设置加密后的密码到用户对象
u.setUserPassword(encryptedPassword);
}
if (userIdentity != null) {
u.setUserIdentity(userIdentity);
}
if (userLoginTime != null) {
u.setUserLastLoginTime(userLoginTime);
}
if (remarks != null) {
u.setRemarks(remarks);
}
if (userStatus != 0) {
u.setUserStatus(userStatus);
}
Users updatedUser = usersService.updateUser(u);
return ResponseEntity.success(updatedUser);
}

登录试试看

故意输错密码:

故意输错账号

2024年1月Java项目开发指南9:密码加密存储的更多相关文章

  1. 转:Java项目开发规范参考

    Java项目开发规范参考 - KevinLee的博客 - 博客频道 - CSDN.NEThttp://blog.csdn.net/u011383131/article/details/51227860 ...

  2. IDEA 学习笔记之 Java项目开发深入学习(2)

    Java项目开发深入学习(2): 查找变量被用到的地方 编译当前文件 增加变量watch 注意:我使用了keymap (eclipse模板),所以很多快捷键和eclipse一样. F5单步调试进入函数 ...

  3. IDEA 学习笔记之 Java项目开发深入学习(1)

    Java项目开发深入学习(1): 定义编译输出路径: 继承以上工程配置 重新定义新的项目编译路径 添加source目录:点击添加,再点击移除: 编译项目: 常用快捷键总结: Ctrl+Space 代码 ...

  4. IDEA 学习笔记之 Java项目开发

    Java项目开发: 新建模块: 添加JDK: 导入本地Jars: 从远程Maven仓库下载: 创建package: 新建类/接口/枚举等: 字体太小,改字体: Duplicate Scheme 修改编 ...

  5. 《Maven在Java项目开发中的应用》论文笔记(十七)

    标题:Maven在Java项目开发中的应用 一.基本信息 时间:2019 来源:山西农业大学 关键词:Maven:Java Web:仓库:开发人员:极限编程; 二.研究内容 1.Maven 基本原理概 ...

  6. 收藏基本Java项目开发的书

    一.Java项目开发全程实录 第1章 进销存管理系统(Swing+SQL Server2000实现) 第2章企业内部通信系统(Swing+JavaDB实现) 第3章 企业人事管理系统( Swing+H ...

  7. Java项目开发中实现分页的三种方式一篇包会

    前言   Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用 ...

  8. Java项目开发

    项目开发整体构建: MVC+DAO设计模式 用面向对象的方式理解和使用数据库,一个数据库对应一个java项目 数据库--项目 表--类 字段--属性 表中的一条数据--类的一个对象 M:模型层 Jav ...

  9. DRF项目之实现用户密码加密保存

    在DRF项目的开发中,我们通过直接使用序列化器保存的用户信息时,用户的密码是被明文保存到数据库中. 代码实现: def create(self, validated_data): '''重写creat ...

  10. 《JAVA 从入门到精通》 - 正式走向JAVA项目开发的路

    以前很多时候会开玩笑,说什么,三天学会PHP,七天精通Nodejs,xx天学会xx ... 一般来说,这样子说的多半都带有一点讽刺的意味,我也基本上从不相信什么快速入门.我以前在学校的时候自觉过很多门 ...

随机推荐

  1. Android中VSYNC代表什么

    在 Android 中,VSYNC(Vertical Synchronization)是一个垂直同步信号,用于协调显示刷新和绘图操作.VSYNC 信号的主要作用是控制屏幕刷新频率与图形渲染的同步,以确 ...

  2. Linux 循环设备 loop疑惑

    什么是loop设备? loop设备是一种伪设备,是使用文件来模拟块设备的一种技术,文件模拟成块设备后, 就像一个磁盘或光盘一样使用.在使用之前,一个 loop 设备必须要和一个文件进行连接.这种结合方 ...

  3. vue 的常用指令以及作用 ·

    1. v-model 多用于表单元素实现双向数据绑定(同 angular 中的 ng-model) 2. v-for 格式: v-for="字段名 in(of) 数组 json" ...

  4. 还在使用昂贵的虚拟机?来试试 Devbox,便宜 6 倍!

    这篇小短文来介绍一下用虚拟机的场景是怎么被 Devbox 全方位碾压的. Devbox 唯一弱点是公网出口的地方不分配独立的 IP 地址,但是这对我们绝大多数场景是没有影响的,通过域名和端口访问我们的 ...

  5. 《使用Gin框架构建分布式应用》阅读笔记:p77-p87

    <用Gin框架构建分布式应用>学习第5天,p77-p87总结,总计11页. 一.技术总结 1.Go知识点 (1)context 2.on-premises software p80, A ...

  6. Octomap的学习

    什么是octomap? RGBD SLAM的目的有两个:估计机器人的轨迹,并建立正确的地图.地图有很多种表达方式,比如特征点地图.网格地图.拓扑地图等等.在<一起做>系列中,我们使用的地图 ...

  7. 分支定界方法(branch and cut,branch and price的基础)

    分支定界方法(branch and cut,branch and price的基础) 目录 1.基础版的分支定界算法(假设是min问题) 2.分支定界算法的步骤及其注意事项 2.1 具体的分支定界方法 ...

  8. VueJS实现迷糊查询

    原文请查看公共号 前置条件: 开发环境:windows 开发框架:vue2.5 ,vue-cli 4.0+ 编辑器:HbuilderX 兼容版本:vue2.5     Chrome 99.0.4844 ...

  9. WPS Excel中配置下拉多选(VBA)

    网上找到两种方案,一种利用数据选择其他单元格,也就是在其他单元格建数据.需求是模板,不合适 这里我用的VBA,踩了挺多坑,详细说下 首先更新WPS为最新版,确保可用VBA和JSA 确定使用VBA还是J ...

  10. 题解:洛谷P3745 期末考试(整数三分)

    题解:洛谷P3745 期末考试(整数三分) 题目传送门 题目大意:给出 \(n\) 个同学期望出成绩的时间限制 \(a_i\) 和 \(m\) 个学科公布成绩的初始时间 \(t_i\) ,1个同学每多 ...