SpringBoot中随机盐值+双重SHA256加密实战

 更新时间:2024年07月09日 10:00:33   作者:MoCrane  
本文主要介绍了SpringBoot中随机盐值+双重SHA256加密实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1.SHA-256和Salt

1.1.什么是SHA-256

SHA-256是一种信息摘要算法,也是一种密码散列函数。对于任意长度的消息,SHA256都会产生一个256bit长的散列值(哈希值),用于确保信息传输完整一致,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示。

SHA-256的具备以下几个关键特点:

  • 固定长度输出:无论输入数据的大小,SHA-256都会产生一个256位(32字节)的固定长度散列值。
  • 不可逆性:SHA-256的设计使得从生成的散列值无法还原原始输入数据。这种不可逆性在安全性上是非常重要的。
  • 抗碰撞性:找到两个不同的输入数据具有相同的散列值(碰撞)是极其困难的。虽然理论上碰撞可能发生,但SHA-256被设计得非常抗碰撞。

除了SHA-256之外,还有一个密码散列函数MD5,过去也常被用于密码加密,但MD5在安全性上低于SHA-256,现在已经很少用于密码加密了,本文不做考虑。

SHA-256 和 MD5 的比较:

特性SHA-256MD5
输出长度256 位(64 个十六进制字符)128 位(32 个十六进制字符)
安全性
计算速度较慢
抗碰撞能力
应用场景数据完整性校验、数字签名、密码存储、区块链曾用于文件校验、密码存储
推荐使用

1.2.什么是随机盐值

盐值(salt) 是一种在密码学和安全计算中常用的随机数据,用于增强密码散列的安全性。

随机盐值(random salt)是一种用于增强密码散列安全性的技术。它是一个随机生成的数据块,在将密码输入散列函数之前,将盐值与密码组合。通过引入随机盐值,可以有效地防止彩虹表攻击和相同密码散列值重复的问题。

盐值的作用:

  • 防止彩虹表攻击: 彩虹表是一个预计算的哈希值数据库,用于快速查找常见密码的哈希值。通过在密码哈希之前加入随机盐值,即使密码相同,其最终的哈希值也会不同,从而使彩虹表无效。
  • 避免散列值重复: 如果两个用户使用相同的密码,在没有盐值的情况下,他们的哈希值会相同。加入盐值后,即使密码相同,生成的哈希值也会不同,这有助于防止攻击者通过观察哈希值来推测用户是否使用了相同的密码。
  • 增加攻击难度: 盐值增加了密码哈希的复杂性。即使攻击者获取了存储的哈希值和盐值,他们仍需对每个盐值进行单独的破解,显著增加了破解的时间和计算成本。

1.3.如何进行加密操作

本文采用的加密方式是在前端采用md加密防止明文传输,后端对密码二次加密后再进行随机盐值的混入。

2.前端实现

引入md5.min.js

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" rel="external nofollow" />
    <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
    <!-- jquery-validator -->
    <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
    <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
    <!-- md5.js -->
    <script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
    <!-- common.js -->
    <script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<form name="loginForm" id="loginForm" method="post" style="width:50%; margin:0 auto">

    <h2 style="text-align:center; margin-bottom: 20px">用户登录</h2>

    <div class="form-group">
        <div class="row">
            <label class="form-label col-md-4">请输入手机号码</label>
            <div class="col-md-5">
                <input id="mobile" minlength="11" maxlength="11" name="mobile" class="form-control" type="text" placeholder="手机号码" required="true"/>
            </div>
        </div>
    </div>

    <div class="form-group">
        <div class="row">
            <label class="form-label col-md-4">请输入密码</label>
            <div class="col-md-5">
                <input id="password" name="password" class="form-control" type="password" placeholder="密码" required="true" />
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-md-5">
            <button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button>
        </div>
        <div class="col-md-5">
            <button class="btn btn-primary btn-block" type="submit" onclick="login()">登录</button>
        </div>
    </div>
</form>
</body>
<script>
    function login() {
        $("#loginForm").validate({
            submitHandler: function (form) {
                doLogin();
            }
        });
    }

    function doLogin() {
        var inputPass = $("#password").val();
        var salt = "1a2b3c4d";
        var str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4);
        var password = md5(str);

        $.ajax({
            url: "/login/doLogin",
            type: "POST",
            data: {
                mobile: $("#mobile").val(),
                password: password
            },
            success: function (data) {
                layer.closeAll();
                if (data.code == 200) {
                    layer.msg("成功");
                    console.log(data);
                    document.cookie = "userTicket=" + data.object;
                    window.location.href = "/goods/toList";
                } else {
                    layer.msg(data.message);
                }
            },
            error: function () {
                layer.closeAll();
            }
        });
    }
</script>
</html>

3.后端实现

3.1.导入Maven依赖

 <dependency>
     <groupId>commons-codec</groupId>
     <artifactId>commons-codec</artifactId>
     <version>1.15</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

3.2.密码加密

3.2.1.密码加盐

首先使用Apache的RandomStringUtils工具类,生成16位的盐值。然后将盐拼接到明文后面,进行SHA256加密。

这个加密后的SHA256是个固定64长度的字符串。

// 生成一个16位的随机数,也就是盐
String salt = RandomStringUtils.randomAlphanumeric(16);

// 将盐拼接到明文后,并生成新的sha256码
String sha256Hex = DigestUtils.sha256Hex(password + salt);

3.2.2.随机盐值混合

加盐后的SHA256码长度为80位,这里我们采用的盐值混合规则:将SHA-256散列值的每四个字符中间插入一个盐值字符,依次交替排列。

// 将盐混到新生成的SHA-256码中,之所以这样做是为了后期解密,校验密码
StringBuilder sb = new StringBuilder(80); // SHA-256是64个字符,加16个字符的盐,总共80个字符
for (int i = 0; i < 16; i++) {
    sb.append(sha256Hex.charAt(i * 4));
    sb.append(salt.charAt(i));
    sb.append(sha256Hex.charAt(i * 4 + 1));
    sb.append(sha256Hex.charAt(i * 4 + 2));
    sb.append(sha256Hex.charAt(i * 4 + 3));
}
return sb.toString();

这样就完成了加密的操作:密码加盐 + 盐值混合

3.3.密码解密

3.3.1.提取盐值和加盐密码

按照加密时采用的规则:将SHA-256散列值的每四个字符中间插入一个盐值字符,依次交替排列。

我们可以将盐值和加盐后的SHA-256码

// 提取盐值和加盐后的SHA-256码
StringBuilder sb1 = new StringBuilder(64);
StringBuilder sb2 = new StringBuilder(16);

for (int i = 0; i < 16; i++) {
    sb1.append(encrypted.charAt(i * 5));
    sb1.append(encrypted.charAt(i * 5 + 2));
    sb1.append(encrypted.charAt(i * 5 + 3));
    sb1.append(encrypted.charAt(i * 5 + 4));
    sb2.append(encrypted.charAt(i * 5 + 1));
}

String sha256Hex = sb1.toString();
String salt = sb2.toString();

3.3.2.比较密码

最后,将取出的盐值与原始密码再次加盐,再次得到加盐密码,与sha256Hex比较即可判断密码是否相同。

// 比较二者是否相同
return  DigestUtils.sha256Hex(password + salt).equals(sha256Hex);

3.4.完整工具类

public class SHA256Util {

    /**
     * 加密
     * 生成盐和加盐后的SHA-256码,并将盐混入到SHA-256码中,对SHA-256密码进行加强
     **/
    public static String encryptPassword(String password) {
        // 生成一个16位的随机数,也就是盐
        String salt = RandomStringUtils.randomAlphanumeric(16);

        // 将盐拼接到明文后,并生成新的sha256码
        String sha256Hex = DigestUtils.sha256Hex(password + salt);

        // 将盐混到新生成的SHA-256码中,之所以这样做是为了后期解密,校验密码
        StringBuilder sb = new StringBuilder(80); // SHA-256是64个字符,加16个字符的盐,总共80个字符
        for (int i = 0; i < 16; i++) {
            sb.append(sha256Hex.charAt(i * 4));
            sb.append(salt.charAt(i));
            sb.append(sha256Hex.charAt(i * 4 + 1));
            sb.append(sha256Hex.charAt(i * 4 + 2));
            sb.append(sha256Hex.charAt(i * 4 + 3));
        }
        return sb.toString();
    }

    /**
     * 解密
     * 从混入盐的SHA-256码中提取盐值和加盐后的SHA-256码
     **/
    public static boolean verifyPassword(String password, String encrypted) {
        // 提取盐值和加盐后的SHA-256码
        StringBuilder sb1 = new StringBuilder(64);
        StringBuilder sb2 = new StringBuilder(16);

        for (int i = 0; i < 16; i++) {
            sb1.append(encrypted.charAt(i * 5));
            sb1.append(encrypted.charAt(i * 5 + 2));
            sb1.append(encrypted.charAt(i * 5 + 3));
            sb1.append(encrypted.charAt(i * 5 + 4));
            sb2.append(encrypted.charAt(i * 5 + 1));
        }

        String sha256Hex = sb1.toString();
        String salt = sb2.toString();

        // 比较二者是否相同
        return  DigestUtils.sha256Hex(password + salt).equals(sha256Hex);
    }
}

到此这篇关于SpringBoot中随机盐值+双重SHA256加密实战的文章就介绍到这了,更多相关SpringBoot 随机盐值+双重SHA256加密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Knife4j的请求示例当中有很多空白行的问题解决办法

    Knife4j的请求示例当中有很多空白行的问题解决办法

    这篇文章主要介绍了Knife4j的请求示例当中有很多空白行的问题解决办法,按正常来说不应该有上方的空白,当然如果只是查看我也不至于非要解决他,主要是假如接口是json传参,调试界面都没办法修改参数,遇到同样问题的同学可以参考阅读本文
    2024-09-09
  • SpringCloud中的服务接口(api)

    SpringCloud中的服务接口(api)

    这篇文章主要介绍了SpringCloud中的服务接口(api),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • 详解Java 中 RMI 的使用

    详解Java 中 RMI 的使用

    这篇文章主要介绍了Java 中 RMI 的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • JavaCV与FFmpeg音视频流处理技巧总结大全

    JavaCV与FFmpeg音视频流处理技巧总结大全

    JavaCV是一个开源的Java接口,它为几个著名的计算机视觉库(如OpenCV、FFmpeg)提供了Java封装,这篇文章主要给大家介绍了关于JavaCV与FFmpeg音视频流处理技巧总结的相关资料,需要的朋友可以参考下
    2024-05-05
  • Java实现简单的万年历

    Java实现简单的万年历

    这篇文章主要为大家详细介绍了Java实现简单的万年历,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • Java实现删除PDF中指定页面

    Java实现删除PDF中指定页面

    这篇文章主要为大家详细介绍了如何使用一个免费的国产Java库来删除PDF中的指定页面或者删除PDF中的空白页,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-11-11
  • JavaSE图像验证码简单识别程序详解

    JavaSE图像验证码简单识别程序详解

    这篇文章主要为大家详细介绍了JavaSE图像验证码简单识别程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • 解决BigDecimal转long丢失精度的问题

    解决BigDecimal转long丢失精度的问题

    这篇文章主要介绍了解决BigDecimal转long丢失精度的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Java 模拟真正的并发请求详情

    Java 模拟真正的并发请求详情

    有时需要测试一下某个功能的并发性能,又不要想借助于其他工具,索性就自己的开发语言,来一个并发请求就最方便了。下文我们就来学习Java 如何模拟真正的并发请求
    2021-09-09
  • SpringBoot整合Netty服务端的实现示例

    SpringBoot整合Netty服务端的实现示例

    Netty提供了一套完整的API,用于处理网络IO操作,如TCP和UDP套接字,本文主要介绍了SpringBoot整合Netty服务端的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07

最新评论