专栏名称: 沉默王二
技术文通俗易懂,吹水文风趣幽默。学 Java,认准二哥的网站 javabetter.cn
目录
相关文章推荐
软件小妹  ·  一键隐藏,简直绝了! ·  14 小时前  
软件小妹  ·  一键隐藏,简直绝了! ·  14 小时前  
歸藏的AI工具箱  ·  上周重要的 AI 新闻都在这里了:AIGC ... ·  昨天  
计算机与网络安全  ·  人工智能安全认证专家 ·  6 天前  
阿枫科技  ·  算法备案通过了,说说踩过的坑 ·  5 天前  
酷玩实验室  ·  车圈大变天!激光雷达即将被判死刑? ·  6 天前  
51好读  ›  专栏  ›  沉默王二

某大厂被爆明文存储密码,真离谱!

沉默王二  · 公众号  · 科技自媒体 互联网安全  · 2024-10-01 14:04

正文

大家好,我是二哥呀。

从星球嘉宾 Guide 哥那里看到一则炸裂的消息,说 Facebook 的母公司 Meta 由于使用明文存储密码,导致被爱尔兰罚款 7.11 亿人民币。

截图来自 javaguide

是不是非常离谱?在不少小伙伴的刻板印象里,外企大厂总是福利好、技术好的代名词,然而还是犯这种低级错误了。

对密码加密其实属于一个非常初级的开发常识了,但意外的是我也经常碰到一些球友找我要技术派的注册密码,说自己忘记了,但讲真我真不知道啊,因为技术派的密码都是哈希加密后存储的(🤣),作为站长的我也不知道啊。

给大家看一下我本地的用户密码吧,存到数据库里其实是这样的:

截图来自二哥的技术派项目

password 这一栏是不可逆的,因为经过了哈希后加密。注意,加密是可逆的,因为有加密就有解密;但哈希是一种单向函数,相当于它把输入的数据转换成了固定长度的“指纹”,整个过程是不可逆的。

截图来自博客园的T2噬菌体

有些初学的小伙伴会把这里的哈希函数和 HashMap 中的哈希函数关联起来,会觉得那么简单的哈希结果(int) 适合拿来处理密码吗?

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

我这里必须强调的一点是,HashMap 中的 hash 函数是为了快速查找,它追求的是把键值均匀的散列在数组中,而用于处理密码的哈希算法具备这样的特点:

如果输入发生了一点改变,由此产生的哈希值会完全不同。

只有加密哈希算法才可以用来进行密码哈希加密,像 SHA256、SHA512、RIPEMD 和 WHIRLPOOL 都是加密哈希函数。

技术派中的加密算法用的是 Spring 的 DigestUtils.md5DigestAsHex() 方法:

MD5 由密码学家 Ronald Linn Rivest 设计,于 1992 年公开,但现在被证实无法防止碰撞攻击,因此更推荐使用 BCrypt 算法。

BCrypt 属于慢哈希算法,可以用来抵抗暴力破解和彩虹表攻击,如果系统已经集成 Spring Security 的话,可以直接使用其提供的 BCryptPasswordEncoder 工具类来完成密码的哈希算法。

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

看过该类源码的小伙伴应该了解它内部其实使用了一种加盐技术:通过在密码中加入一段随机字符串再进行哈希加密,这个被加的字符串称之为盐值

public String encode(CharSequence rawPassword) {
    String salt = getSalt();
    return BCrypt.hashpw(rawPassword.toString(), salt);
}

技术派中的加密其实也用到了加盐技术。当然了,盐值最好每次都是随机的,这样就会让相同的密码每次加密都会生成完全不同的密文字符串。

因为攻击者无法事先知道盐值,所以也就没办法预先计算查询表和彩虹表,从而让反向查表法攻击无法奏效。

①、反向查表法攻击的原理是,攻击者预先计算出常用密码的哈希值,然后将这些哈希值与存储在数据库中的哈希密码进行比对,从而找出对应的原始密码。

②、彩虹表是一种特殊的查表法,不是直接存储所有可能的密码和其对应的哈希值,而是通过压缩存储链条(即不同的哈希值之间的映射关系),在有限的存储空间内保留大量的哈希和密码对。

截图来自维基百科:有三个reduction函数的简化彩虹表

那通过加盐,就能很巧妙的解决这两种攻击,因为哈希后的密文每次都不一样,破解起来非常消耗算力:

hash("hello")                    = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007

密码加密其实也是面试中经常被问到的题目,之前一位球友在面试拼多多服务端暑期实习的时候就被问到了。

我们来模拟一下。

面试官老王问:“用户名密码登录时是如何加密的,和验证的?”

你(小二):

在技术派中,我使用了 Spring Security 的 BCrypt 算法对密码进行了加盐哈希,盐值是随机的,因此即使两个用户使用了相同的密码,生成的哈希结果也是不同的。

然后将加密后的哈希值(包含盐值)存储到 MySQL 数据库中。

当登录的时候,直接调用 BCryptPasswordEncoder 的 matches 方法进行比对,该方法会自动从存储的加密密码中提取盐值,再对用户输入的密码进行相同的哈希计算。源码主要在 BCrypt 类中。

代码示例如下所示:

// 创建一个 BCryptPasswordEncoder 实例
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

// 注册时加密密码
String rawPassword = "沉默王二是条狗";
String encodedPassword = passwordEncoder.encode(rawPassword);

// 打印加密后的密码(每次加密结果都不同)
System.out.println("加密后的密码: " + encodedPassword);

// 登录时验证密码
boolean isPasswordMatch = passwordEncoder.matches(rawPassword, encodedPassword);
System.out.println("密码验证结果: " + isPasswordMatch); // 输出为 true

面试官老王问:“加盐有什么好处?哈希有什么好处?”

你(小二):如果密码未加盐,常用的密码(如 “123456”)的哈希值是已知的,攻击者可以直接匹配彩虹表中的哈希值来获取密码。而通过加盐,即使是相同的密码,生成的哈希值也不同,无法通过彩虹表轻松破解。

哈希函数是单向的,意味着从哈希值无法轻易推导出原始密码。密码哈希后,即使数据库泄露,攻击者也不能直接从哈希值中恢复原始密码。

好,今天是国庆节第一天,希望大家都能玩的愉快,不过应该还有一部分小伙伴在学习,加密哈希算法就给大家讲这么多,应付节后的面试是绰绰有余了(😄)。

ending

一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 6300 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个 编程学习指南 + Java 项目实战 + LeetCode 刷题 +【简历精修】的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。

欢迎点击左下角【阅读原文】了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。

最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。

推荐文章
软件小妹  ·  一键隐藏,简直绝了!
14 小时前
软件小妹  ·  一键隐藏,简直绝了!
14 小时前
计算机与网络安全  ·  人工智能安全认证专家
6 天前
阿枫科技  ·  算法备案通过了,说说踩过的坑
5 天前
酷玩实验室  ·  车圈大变天!激光雷达即将被判死刑?
6 天前
读书小分队  ·  500万让你陪睡一晚,你愿意吗?
7 年前
Cocoa开发者社区  ·  CocoaChina文章征集 要写就要写得无限嗨
7 年前