加密的艺术:密文的创建和校验

· 浏览次数 : 0

小编点评

**密码加密的步骤:** 1. 创建一个 BCryptEncoder 对象,指定工作因子。 2. 将密码加密为一个编码后的字符串。 3. 验证密码是否与编码后的字符串匹配。 **慢哈希的步骤:** 1. 创建一个 BCryptEncoder 对象,指定工作因子并设置延迟。 2. 使用慢哈希函数对明文进行加密。 3. 验证密码是否与加密后的字符串匹配。 **安全优势:** * **客户端加密:**客户端在进行密码加密之前可以进行加密处理,确保密文已在服务器端加密处理。 * **盐值和密码混合:**通过使用盐值和密码混合,可以提高安全性。 * **慢哈希:**使用慢哈希函数可以极大地降低破解密码的难度,使其变得不可行。 **建议:** * 使用 BCrypt 算法而不是 BCryptHash 算法,因为 BCrypt 算法速度更快。 * 设置合适的工作因子,以平衡安全性和性能。 * 对明文进行多个迭代的慢哈希,以提高安全性。

正文

概述

在我很喜欢的一部(根据真实事件改编)的电影《模仿游戏》里面:

模仿游戏

著名的科学家图灵带领他的团队,花费两年的时间,费劲九牛二虎之力,在找到德军的话术口令后才得以破解了德军通讯加密装置 “英格玛”,为第二次世界大战取得胜利打下的坚实的基础。那么德军使用的通讯加密究竟是一种怎样的技术,这是我们今天要探讨的数据加密技术。数据的保密是对数据加密、解密的统称,用学院派的说法就是,使用某种算法改变了信息原本的形态,使攻击者即使窃取了信息也因为没有对应的解密的方法也无法获取当信息的真实内容。这就是信息保密的目的,对于信息的保密,可以在三个环节进行,分别是:

  1. 在客户端进行保密
  2. 在传输时进行保密(最复杂,也最有效)
  3. 在服务端进行保密

加密的强度

在安全领域大家都知道安全是区分等级的,不同应用的敏感信息重要性不同,所以需要的安全等级也不同,这个世界上没有绝对的安全,安全等级不可能无止境的拉满,任何安全手段都可以破解(只要花费足够的成本),想要更高级别的安全等级,就要付出更高的成本(工作量,算力)等。例如常见的加密技术可以说明这一点。加密的强度从低到高,分别有:

一:哈希算法:最常见的加密手段,对明文密码使用 MD5 等哈希摘要算法进行不可逆的哈希计算进行加密,示例:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Hash {
    public static void main(String[] args) {
        String text = "yourPassword";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashBytes = md.digest(text.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                hexString.append(String.format("%02x", b));
            }
            System.out.println("MD5 Digest: " + hexString.toString());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

MD5 Digest: 65a8e27d8879283831b664bd8b7f0ad4

这种方式,安全等级低,弱密码容易被彩虹表(预先进行摘要好的哈希表,进行反向破译)破击。

二:哈希算法加盐:增强了基础的哈希算法,加上 salt 盐值混淆哈希计算,可以有效防御彩虹表的攻击,示例:

private static final String SALT = "YourFixedSalt";  // 固定盐值

private static String getSecurePassword(String passwordToHash) {
    String generatedPassword = null;
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 添加固定盐值
        md.update(SALT.getBytes());
        byte[] bytes = md.digest(passwordToHash.getBytes());
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        generatedPassword = sb.toString();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return generatedPassword;
}

这种方案的缺点是,但如果盐值泄露,那么破译所以密文也是一件很容易得事情,而且弱密码即使加了盐值,在强大算力的彩虹表面前,破译也不是一件难事。

三:动态盐加哈希:动态盐值有一个特点,就是每个盐值只使用一次,这种方式有点像就像我喜欢吃的那家酸菜鱼,他们家宣传的口号就是:油每次只用一次,本质上就是花费更高的成本换来更高的安全。示例:

public static void main(String[] args) {
    // 待加密的密码
    String passwordToHash = "yourPassword";
    // 生成动态盐值
    byte[] salt = getSalt();
    // 获取带盐的安全密码
    String securePassword = getSecurePassword(passwordToHash, salt);
    System.out.println("Secure Password: " + securePassword);
    System.out.println("Salt: " + bytesToHex(salt));
}

// 使用MD5加密密码,并结合盐值
private static String getSecurePassword(String passwordToHash, byte[] salt) {
    try {
        // 创建MD5摘要算法的 MessageDigest 对象
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 将盐值添加到摘要中
        md.update(salt);
        // 完成密码的哈希计算
        byte[] hashedBytes = md.digest(passwordToHash.getBytes());
        // 将哈希值转换为十六进制字符串
        return bytesToHex(hashedBytes);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }
}

// 生成一个随机的盐值
private static byte[] getSalt() {
    SecureRandom sr = new SecureRandom();
    byte[] salt = new byte[16];
    sr.nextBytes(salt);
    return salt;
}

// 将字节数组转换为十六进制字符串
private static String bytesToHex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02x", b));
    }
    return sb.toString();
}

动态盐值可以解决固定盐值带来的风险,如果由客户端动态生成盐值给服务端进行计算,那么 客户端如果安全的把动态盐值传输给服务端 就是另外一个问题,既然通信的信道是安全可靠的,那么传输动态盐值就没有意义,既然通信信道是不安全的,那么传输动态盐值也有被窃听的风险,也没有意义。这简直就是一个 “先有鸡,还是先有蛋” 的问题。

四:启动 HTTPS 信道:HTTPS 加密传输是目前的主流方案,但是启动 HTTPS 后安全信道后也并不能高枕无忧,也会带来一系列的问题,例如因为会遇到服务端使用自签名证书导致信息泄露风险,服务端证书更新不及时,证书过期的问题,还有 TLS 版本过低或密码学套件选用不当产生加密强度不足的风险。

五:外置的 MFA:例如银行等机构在涉及金额交易的时候,会要求客户使用外置的 U 盾,虚拟 MFA,手机验证码,人脸识别等外置设备来加强安全等级。一些关键企业或者军事机构甚至会开辟一条与公网隔绝的独立的内部网络进行信息通信来保证信息的安全。

u盾

通过以上示例是想要证明,对于安全和保密而言:这个世界上是没有绝对的安全,想要更高级别的安全等级,就要付出更高的成本 ,当然有人会挑刺的说,那我拔掉网线不联网最安全,虽然有一定的合理性,但这样封闭式的安全没有意义,所以不在我们讨论的范围之内。

客户端加密

对于大多数应用而言,要保证信息通信的安全,客户端只有启用 HTTPS 这一个方案可以选择。而且对于密码这样的敏感信息而言,个人认为最好是在客户端就可以尽快处理掉,以绝后患,原因如下:

  1. 服务端存储明文密码,数据库被攻破导致用户密码泄露的新闻已经屡见不鲜的,而且被拖库最严重的还是国内某最大的技术社区。。。
  2. 服务端把密码输入到日志,日志文件泄露或者被采集,导致用户密码泄露等等
  3. 避免中间人攻击,就算网络设备被劫持,信息被窃取,至少明文密码不会泄露

总之,明文密码最好在客户端就被消灭掉,越早处理越好,不要把明文传到服务端,传输的风险大,在防御上客户端除了启用 HTTPS 外,还要对明文密码进行摘要处理,从而保证敏感的安全。至于客户端应该如何进行加密,我们接下来开始讨论。

密文的创建和校验

之前说了在信息安全领域没有绝对的安全,需要多高的安全等级就要消耗多大的安全成本。对于大多数普遍的应用而言,启动 HTTPS 加密通信是在安全等级和安全成本之间的一个合适的平衡点。所以结合实际情况选择合适的方案就好。

BCrypt 算法

上面介绍无论如何对明文进行哈希计算,就算加盐都有被彩虹表暴力破解的可能。为了解决这个问题,引入慢哈希函数来解决可能是一个更理想的方案。慢哈希,就是在哈希计算和 salt 盐值之外增加一个计算时间 cost 的参数,慢哈希通过延长哈希计算时间和消耗的资源来有效的避免诸如彩虹表等暴力破解的攻击,提供系统的安全性,BCrypt 算法就是一个具有代表性的慢哈希函数。示例:

public class BCryptExample {

    public static void main(String[] args) {
        // 创建 BCryptPasswordEncoder 实例,可以指定工作因子,默认是 10
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        // 加密密码
        String originalPassword = "yourPassword";
        String encodedPassword = encoder.encode(originalPassword);
        System.out.println("Encoded Password: " + encodedPassword);

        // 校验密码
        boolean isMatch = encoder.matches(originalPassword, encodedPassword);
        System.out.println("Password matched: " + isMatch);
    }
}

如果我们把慢哈希计算的 cost 设置为 0.1 秒的时间,那么对所有由10位大小写字母和数字组成的弱密码(共62种字符)进行哈希计算一次,大约需要 8.39×10168.39×1016 秒。这等于大约 971.4 亿天,或者大约 2661 百万年的时间。这表明使用 BCrypt 和适当的工作因子可以极大增加破解密码的难度,使得暴力破解方法变得不可行。但是需要注意的是:

BCrypt 存在对计算资源和时间有很大的消耗,会明显降低服务端性能,只建议在客户端进行慢哈希处理

密文的创建

对于敏感信息加密阶段,可以参考以下方案进行处理:

密文的创建
  1. 用户创建密码,客户端接收用户的明文密码
  2. 客户端对密码使用固定盐值 + BCrypt 慢哈希进行加密后发给服务端
  3. 服务端接收密文,然后生成随机盐值,对密文进行二次加密
  4. 服务端将随机盐和二次密文存储到数据库

密文的校验

在对密文进行校验阶段,可以参考以下方案进行处理:

密文的校验

说明:

  1. 用户输入密码,客户端收到用户的明文密码
  2. 客户端对密码使用固定盐值 + BCrypt 慢哈希进行加密后发给服务端
  3. 服务端接收客户端密文,然后从数据库取出随机盐和二次密文
  4. 服务端使用随机盐对客户端密文进行加密,然后和自身的二次密文进行对比
  5. 密文内容相同,则表示密码校验通过

与加密的艺术:密文的创建和校验相似的内容:

加密的艺术:密文的创建和校验

这是我们今天要探讨的数据加密技术。数据的保密是对数据加密、解密的统称,用学院派的说法就是,使用某种算法改变了信息原本的形态,使攻击者即使窃取了信息也因为没有对应的解密的方法也无法获取当信息的真实内容。

加密的手机号,如何模糊查询?

前言 前几天,知识星球中有位小伙伴,问了我一个问题:加密的手机号如何模糊查询? 我们都知道,在做系统设计时,考虑到系统的安全性,需要对用户的一些个人隐私信息,比如:登录密码、身份证号、银行卡号、手机号等,做加密处理,防止用户的个人信息被泄露。 很早之前,CSDN遭遇了SQL注入,导致了600多万条明

加密 K8s Secrets 的几种方案

前言 你可能已经听过很多遍这个不算秘密的秘密了--Kubernetes Secrets 不是加密的!Secret 的值是存储在 etcd 中的 base64 encoded(编码) 字符串。这意味着,任何可以访问你的集群的人,都可以轻松解码你的敏感数据。任何人?是的,几乎任何人都可以,尤其是在集群的

2.13 PE结构:实现PE代码段加密

代码加密功能的实现原理,首先通过创建一个新的`.hack`区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的`ShellCode`汇编指令集,并将程序入口地址修正为`ShellCode`地址位置处,当解密功能被运行后则可释放加密的`.text`节,此时再通过一个`JMP`指令跳转到原始`OEP`位置,则可继续执行解密后的区段。

密钥封装和公钥加密的联系和区别?

密钥封装和公钥加密的联系和区别? 转载&学习:https://www.zhihu.com/question/443779639 先理解下面这句话: 密钥封装机制是面向的互联网应用,最大的用处是网络连接时建立双方的临时会话密钥。既然是应用就要考虑到网络传输的协议,协议支持的包大小是有限制的,如果公钥加

文章学习|存储加密和传输加密的审计要点

> 学习转载:[存储加密和传输加密的审计要点](https://mp.weixin.qq.com/s/VQxv9ui81zO6FMrzamzHVA) # 存储加密和传输加密的审计要点 近年来,随着移动互联网的高速发展,在人们享受网络带来便利的同时,信息安全也逐渐成为大众关注的热点。 2021年落地的

全同态加密是否完美?

学习文章:全同态加密是否完美?-2017 引言 分类&发展 第一代FHE ⚠️: 电路:算法可以用电路表示(实现) 电路深度:电路中任意路径所含有的最大电路门的个数 自举:在密态环境下下,利用计算密钥(加密的密钥)来计算解密电路,得到结果仍是密文。 第二代FHE ⚠️: 循环安全假设:在计算“加密密

介绍3种ssh远程连接的方式

摘要:SSH(安全外壳协议 Secure Shell Protocol,简称SSH)是一种加密的网络传输协议,用于在网络中实现客户端和服务端的连接,典型的如我们在本地电脑通过 SSH连接远程服务器。 本文分享自华为云社区《ssh 远程连接方式总结》,作者:嵌入式视觉。 SSH(安全外壳协议 Secu

【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题

问题描述 Azure Cache for Redis 服务在传输和存储数据时是如何加密呢? 问题回答 一:关于Azure cache for Redis服务在数据传输过程中是如何加密的? 为了确保在Azure cache for Redis和客户端应用程序之间传输的数据安全,需要启用TLS加密。Az

netty系列之: 在netty中使用 tls 协议请求 DNS 服务器

简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求消息。在请求的过程中并没有进行消息的加密,所以这种请求是不安全的。 那么有同学会问了,就是请求解析一个域名的IP地址而已,还需要安全通讯吗? 事实上,不加密的DNS查询消息是很危险的,如果你在访问一