首页 > 编程语言 >2024年1月Java项目开发指南9:密码加密存储

2024年1月Java项目开发指南9:密码加密存储

时间:2024-01-25 19:33:21浏览次数:37  
标签:加密 String 2024 密码 user 哈希 Java salt

提前声明:
你不会写这加密算法没关系啊,你会用就行。
要求就是:你可以不会写这个加密算法,但是你要知道加密流程,你要会用。

@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);
    }

image

登录试试看

image

故意输错密码:
image

故意输错账号
image

标签:加密,String,2024,密码,user,哈希,Java,salt
From: https://www.cnblogs.com/mllt/p/17987989/project202401-9

相关文章

  • java系统与文件操作
    1.目录文件操作创建File对象,后续操作皆基于File,而不是String路径importjava.io.File;importjava.io.FilenameFilter;Filedir=newFile("C:\\Users\\Desktop");//目录Filefile=newFile("C:\\Users\\Desktop\\text.docx");//文件Filedir_......
  • java收发邮件
    邮箱协议端口使用jakarta库发送邮件示例importcom.alibaba.fastjson2.JSON;importcom.alibaba.fastjson2.JSONObject;importcom.xin_admin.common.Result;importcom.xin_admin.security.AuthAnnotation;importjakarta.activation.DataHandler;importjakarta.act......
  • 2024年1月Java项目开发指南8:统一数据返回格式
    有时候返回一个字符串,有时候返回一串数字代码,有时候返回一个对象……不过怎么说,我们返回的内容往往具有三个1.消息代码code2.消息内容msg3.数据内容data接下来,我们要编写一个类,通过这个类,实现对所有返回内容进行格式化。先去添加个依赖 <dependency> <groupId>org.p......
  • THUWC & WC 2024 游记
    2024-01-25去重庆,要到宁波赶飞机,早上5:40起床,吃完早饭下楼等ZHY巨佬。ZHY巨佬昨天刚切了第六分块,还拿了个最优解(本来是rk2的,但rk1的用户被封禁了),准备在火车上颓题解。到宁波坐地铁到飞机场,在候机室看ZHY巨佬写题解。马上上飞机了FSB带着一堆初二巨佬过来(LYL,XY......
  • 笨办法学 Java(四)
    原文:LearnJavaTheHardWay译者:飞龙协议:CCBY-NC-SA4.0练习55:记录数组记录很棒,数组更好,但是当你把记录放入数组时,这个生活中几乎没有你不能编码的东西。1classStudent2{3Stringname;4intcredits;5doublegpa;6}78publicclass......
  • 2024年1月Java项目开发指南7:增删改查与接口测试
    我们之前,是从Controller层写到Service层,然后mapper层。接下来我们反过来,从mapper层写到Controller层两种方式都可以,你喜欢就行,甚至你先写service层也可以,全凭个人喜欢。在本文中,就不解释太多了,直接给出代码,对于关键地方,我会圈出来。如果有问题,可以直接在本文首发地址(博客园......
  • JAVA调用Python脚本执行
    SpringBoot-web环境<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>springboot--启动类@SpringBootApplication(ex......
  • 加密解密
    1加密算法如何选择 对称加密算法(如AES)使用相同的密钥进行加密和解密操作,加解密速度较快,适合大量数据的加密和解密。然而,对称加密算法的主要安全隐患在于密钥的分发和管理。如果密钥泄露,攻击者可以使用密钥对加密的数据进行解密。因此,对称加密算法在保护密钥的安全性方面需要......
  • java aspect 切面怎么获取 POST 数据
    javaaspect切面怎么获取POST数据/***切面*/@Aspect@ComponentpublicclassPostRequestBodyAspect{@Pointcut("execution(*com.example.controller.*.*(..))")publicvoidcontrollerMethods(){}......
  • html 中javascript 如何被调用
    在HTML中,JavaScript可以通过多种方式进行调用。1.内联脚本(InlineScript):直接将JavaScript代码写入到HTML文件的<script>标签中。示例代码如下所示:<!DOCTYPEhtml><html><head><title>使用内联脚本</title></head><body><!--HTML页面内容--><scripttyp......