【Shiro 系列 06】Shiro 中密码加密

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 上篇文章和小伙伴们分享了 Realm 的认证策略问题,本文我想和小伙伴们来聊一聊密码的加密问题。

密码为什么要加密

2011 年 12 月 21 日,有人在网络上公开了一个包含 600 万个 CSDN 用户资料的数据库,数据全部为明文储存,包含用户名、密码以及注册邮箱。事件发生后 CSDN 在微博、官方网站等渠道发出了声明,解释说此数据库系 2009 年备份所用,因不明原因泄露,已经向警方报案。后又在官网网站发出了公开道歉信。在接下来的十多天里,金山、网易、京东、当当、新浪等多家公司被卷入到这次事件中。整个事件中最触目惊心的莫过于 CSDN 把用户密码明文存储,由于很多用户是多个网站共用一个密码,因此一个网站密码泄露就会造成很大的安全隐患。由于有了这么多前车之鉴,我们现在做系统时,密码都要加密处理。

密码加密我们一般会用到散列函数,又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。我们常用的散列函数有如下几种:

1.MD5 消息摘要算法

MD5消息摘要算法是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321中被加以规范。将数据(如一段文字)运算变为另一固定长度值,是散列算法的基础原理。1996年后被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

2.安全散列算法

安全散列算法(Secure Hash Algorithm)是一个密码散列函数家族,是 FIPS 所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。SHA 家族的算法,由美国国家安全局所设计,并由美国国家标准与技术研究院发布,是美国的政府标准,其分别是:

  • SHA-0:1993 年发布,是 SHA-1 的前身;
  • SHA-1:1995 年发布,SHA-1 在许多安全协议中广为使用,包括 TLS 和 SSL、PGP、SSH、S/MIME 和 IPsec,曾被视为是 MD5 的后继者。但 SHA-1 的安全性在 2000 年以后已经不被大多数的加密场景所接受。2017 年荷兰密码学研究小组 CWI 和 Google 正式宣布攻破了 SHA-1;
  • SHA-2:2001 年发布,包括 SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。虽然至今尚未出现对 SHA-2 有效的攻击,它的算法跟 SHA-1 基本上仍然相似;因此有些人开始发展其他替代的散列算法;
  • SHA-3:2015 年正式发布,SHA-3 并不是要取代 SHA-2,因为 SHA-2 目前并没有出现明显的弱点。由于对 MD5 出现成功的破解,以及对 SHA-0 和 SHA-1 出现理论上破解的方法,NIST 感觉需要一个与之前算法不同的,可替换的加密散列算法,也就是现在的 SHA-3。

Shiro 中如何加密

Shiro 中对以上两种散列算法都提供了支持,对于 MD5,Shiro 中生成消息摘要的方式如下:

Md5Hash md5Hash = new Md5Hash("123", null, 1024);

第一个参数是要生成密码的明文,第二个参数密码的盐值,第三个参数是生成消息摘要的迭代次数。

Shiro 中对于安全散列算法的支持如下(支持多种算法,这里我举一个例子):

Sha512Hash sha512Hash = new Sha512Hash("123", null, 1024);

这里三个参数含义与上文基本一致,不再赘述。shiro 中也提供了通用的算法,如下:

SimpleHash md5 = new SimpleHash("md5", "123", null, 1024);
SimpleHash sha512 = new SimpleHash("sha-512", "123", null, 1024);

当用户注册时,我们可以通过上面的方式对密码进行加密,将加密后的字符串存入数据库中。我这里为了简单,就不写注册功能了,就把昨天数据库中用户的密码 123 改成 sha512 所对应的字符串,如下:

cb5143cfcf5791478e057be9689d2360005b3aac951f947af1e6e71e3661bf95a7d14183dadfb0967bd6338eb4eb2689e9c227761e1640e6a033b8725fabc783

同时,为了避免其他 Realm 的干扰,数据库中我只配置一个 JdbcRealm ,配置方式参考本系列第四篇文章。

此时如果我不做其他修改的话,登录必然会失败,原因很简单:我登录时输入的密码是 123,但是数据库中的密码是一个很长的字符串,所以登录肯定不会成功。通过打断点,我们发现最终的密码比对是在 SimpleCredentialsMatcher 类中的 doCredentialsMatch 方法中进行密码比对的,比对的方式也很简单,直接使用了对用户输入的密码和数据库中的密码生成 byte 数组然后进行比较,最终的比较在 MessageDigest 类的 isEqual 方法中。部分逻辑如下:

protected boolean equals(Object tokenCredentials, Object accountCredentials) {
        ...
        ...
        //获取用户输入密码的byte数组
        byte[] tokenBytes = this.toBytes(tokenCredentials);
        //获取数据库中密码的byte数组
        byte[] accountBytes = this.toBytes(accountCredentials);
        return MessageDigest.isEqual(tokenBytes, accountBytes);
        ...
}

MessageDigest 的 isEqual 方法如下:

public static boolean isEqual(byte[] digesta, byte[] digestb) {
    if (digesta == digestb) returntrue;
    if (digesta == null || digestb == null) {
        returnfalse;
    }
    if (digesta.length != digestb.length) {
        returnfalse;
    }
    int result = 0;
    // time-constant comparison
    for (int i = 0; i < digesta.length; i++) {
        result |= digesta[i] ^ digestb[i];
    }
    return result == 0;
}

都是很容易理解的比较代码,这里不赘述。我们现在之所以登录失败是因为没有对用户输入的密码进行加密,通过对源代码的分析,我们发现是因为在 AuthenticatingRealm 类的 assertCredentialsMatch 方法中获取了一个名为 SimpleCredentialsMatcher 的密码比对器,这个密码比对器中比对的方法就是简单的比较,因此如果我们能够将这个密码比对器换掉就好了。我们来看一下 CredentialsMatcher 的继承关系:

14.jpg

我们发现这个刚好有一个 Sha512CredentialsMatcher 比对器,这个比对器的 doCredentialsMatch 方法在它的父类 HashedCredentialsMatcher,方法内容如下:

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials = hashProvidedCredentials(token, info);
    Object accountCredentials = getCredentials(info);
    return equals(tokenHashedCredentials, accountCredentials);
}

这时我们发现获取 tokenHashedCredentials 的方式不像以前那样简单粗暴了,而是调用了 hashProvidedCredentials 方法,而 hashProvidedCredentials 方法最终会来到下面这个重载方法中:

protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = assertHashAlgorithmName();
    returnnew SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}

这几行代码似曾相识,很明显,是系统帮我们对用户输入的密码进行了转换。了解了这些之后,那我只需要将 shiro.ini 修改成如下样子即可实现登录了:

sha512=org.apache.shiro.authc.credential.Sha512CredentialsMatcher
# 迭代次数
sha512.hashIterations=1024
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiroDemo
dataSource.username=root
dataSource.password=123
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
# 修改JdbcRealm中的credentialsMatcher属性
jdbcRealm.credentialsMatcher=$sha512
securityManager.realms=$jdbcRealm

如此之后,我们再进行登录测试,就可以登录成功了。

好了,密码加密我们先说到这里,下篇文章我们来聊聊密码加盐的问题。

本文案例下载地址:https://github.com/lenve/shiroSamples/archive/v6.zip


相关文章
|
3月前
|
存储 NoSQL 数据库
认证服务---整合短信验证码,用户注册和登录 ,密码采用MD5加密存储 【二】
这篇文章讲述了在分布式微服务系统中添加用户注册和登录功能的过程,重点介绍了用户注册时通过远程服务调用第三方服务获取短信验证码、使用Redis进行验证码校验、对密码进行MD5加密后存储到数据库,以及用户登录时的远程服务调用和密码匹配校验的实现细节。
认证服务---整合短信验证码,用户注册和登录 ,密码采用MD5加密存储 【二】
|
29天前
|
存储 Java 数据库
密码专辑:对密码加盐加密,对密码进行md5加密,封装成密码工具类
这篇文章介绍了如何在Java中通过加盐和加密算法(如MD5和SHA)安全地存储密码,并提供了一个密码工具类PasswordUtils和密码编码类PasswordEncoder的实现示例。
29 10
密码专辑:对密码加盐加密,对密码进行md5加密,封装成密码工具类
|
29天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
24 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
1月前
|
安全 算法 Java
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
本文提供了在数据库中对密码等敏感信息进行加盐加密的详细教程,包括手写MD5加密算法和使用Spring Security的BCryptPasswordEncoder进行加密,并强调了使用BCryptPasswordEncoder时需要注意的Spring Security配置问题。
116 0
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
|
29天前
|
存储 安全 Java
shiro学习二:shiro的加密认证详解,加盐与不加盐两个版本。
这篇文章详细介绍了Apache Shiro安全框架中密码的加密认证机制,包括不加盐和加盐两种加密方式的实现和测试。
68 0
|
2月前
|
存储 安全 算法
RSA在手,安全我有!Python加密解密技术,让你的数据密码坚不可摧
【9月更文挑战第11天】在数字化时代,信息安全至关重要。传统的加密方法已难以应对日益复杂的网络攻击。RSA加密算法凭借其强大的安全性和广泛的应用场景,成为保护敏感数据的首选。本文介绍RSA的基本原理及在Python中的实现方法,并探讨其优势与挑战。通过使用PyCryptodome库,我们展示了RSA加密解密的完整流程,帮助读者理解如何利用RSA为数据提供安全保障。
112 5
|
2月前
|
安全 数据安全/隐私保护 Python
情书也能加密?Python AES&RSA,让每一份数据都充满爱的密码
【9月更文挑战第8天】在这个数字化时代,情书不再局限于纸笔,也可能以电子形式在网络中传递。为了确保其安全,Python提供了AES和RSA等加密工具,为情书编织爱的密码。首先,通过安装pycryptodome库,我们可以利用AES对称加密算法高效保护数据;接着,使用RSA非对称加密算法加密AES密钥和IV,进一步增强安全性。即使情书被截获,没有正确密钥也无法解读内容。让我们用Python为爱情编织一张安全的网,守护每份珍贵情感。
46 2
|
6天前
|
安全 算法 网络安全
网络防御的艺术:揭秘加密技术与安全意识的重要性
【10月更文挑战第30天】在数字化时代,网络安全已成为我们生活中不可或缺的部分。本文旨在揭示网络安全漏洞的成因,探讨如何通过加密技术和提升安全意识来构建坚固的网络防线。文章将深入分析常见的安全威胁,并分享实用的防护策略,帮助读者在日益复杂的网络环境中保持警觉和安全。
54 29
|
4天前
|
存储 安全 算法
网络安全的屏障与钥匙:漏洞防御、加密技术与安全意识
【10月更文挑战第31天】在数字时代的海洋中,网络安全犹如灯塔指引着信息的安全航行。本文将探讨网络安全的三大支柱:网络漏洞的防御策略、加密技术的应用以及提高个人和组织的安全意识。通过深入浅出的分析,我们将了解如何构建坚固的网络防线,保护数据不受威胁,并提升整个社会对信息安全的认识和重视。
|
4天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【10月更文挑战第31天】本文将探讨网络安全和信息安全的重要性,以及如何通过理解和应用相关的技术和策略来保护我们的信息。我们将讨论网络安全漏洞、加密技术以及如何提高安全意识等主题。无论你是IT专业人士,还是对网络安全感兴趣的普通用户,都可以从中获得有用的信息和建议。
17 1
下一篇
无影云桌面