程序员密码学 2:区块和随机性 区块密码学 随机性 代码示例 BubblePress @AliasKepler

2025-06-10

程序员密码学 2:区块和随机性

区块加密

随机性

代码示例

BubblePress @AliasKepler

在本系列的上一篇文章,我介绍了为什么程序员掌握密码学基础知识非常重要,并给出了程序员应遵循的三条基本规则,以避免最常见的错误。然而,这些规则相当模糊,需要更具体的理解才能有效地应用。

在本期节目中,我们将探讨密码学中最重要的两个主题:区块密码学和安全随机性。我必须承认,要对如此宏大且激动人心的话题进行如此深入的总结确实非常困难,希望我做得不错!😄

对称加密与非对称加密

要了解什么是区块加密,我们需要首先讨论加密和解密数据的两种主要方式。

对称加密与非对称加密

在对称加密中,加密和解密都使用同一个密钥。在双方进行加密通信的情况下,他们需要一个彼此之间不为他人所知的共享密钥。这是对称加密的主要局限性,因为在互联网等环境下,双方之间没有共享密钥。

在非对称加密中,解密密钥与加密密钥不同。非对称加密主要用于多方通信,当通信双方没有共享密钥或安全的密钥发送通道时。在非对称加密中,每个参与者都有一对密钥,由公钥和私钥组成。顾名思义,公钥是公开的,任何人都可以访问。私钥则应保密,切勿与他人共享。这样,当有人想与你通信时,他们会使用你的公钥加密消息,只有知道私钥的人才能解密。

非对称加密听起来很实用,但正如我们将在第四部分讨论公钥和协议时看到的那样,它很少用于传输数据,通常用于共享密钥,然后使用密钥继续使用对称加密进行通信。主要原因是对称加密,尤其是分组密码,比基于数学的非对称密码效率高得多。

区块加密

分组密码学是目前默认使用的对称密码学,因此它将是本系列中唯一涵盖的对称密码学。大多数密码学课程会通过更简单的对称密码(例如OTP )和其他基于异或(XOR)的方法介绍该主题。从理论角度来看,研究这些方法及其攻击方式颇有趣味。然而,作为一名程序员,你不应该使用这些方法,所以我们将略过它们。

什么是区块加密

分组密码是一种能够使用对称密钥以安全的方式加密固定数量比特的加密算法。分组密码的解密和加密功能有所不同。市面上有许多不同的分组密码可供选择,其中许多被认为是安全的,但有些已经过时,不再安全。

您应该使用什么分组密码?

在选择分组密码时,我们应该特别注意确保它在当今仍然是安全的。例如,DES曾经是一种非常流行的分组密码,一度成为标准,一些老教程可能会使用它,让你觉得它是一个不错的选择。但事实并非如此。事实上,使用 DES 本身就已经违反了上一集提出的规则之一。有一个稍微“现代”的版本,叫做三重 DES 或 3DES,它基本上使用 DES 算法对内容进行三次加密,每次使用不同的密钥,实际上使密钥的大小增加了三倍。你可能会遇到 3DES,尤其是在一些老项目中。然而,它的安全性并不高,不应该在新项目中使用它。

就本文而言,我想说,作为一名程序员,你没有理由使用AES以外的任何算法。AES 是当前的标准,它足够安全,并且很可能在很长一段时间内都会保持这种安全,特别是如果你使用我推荐的 256 位密钥版本。事实上,AES 的使用非常广泛,以至于大多数现代处理器都为 AES 操作提供了专门的指令集,因此,除了安全之外,它还非常高效。

操作模式

现在我们已经选择了想要使用的分组密码,但这还不算完。AES 的分组大小为 16 字节,与密钥大小无关。因此,如果我们要加密长度超过 16 字节的信息,首先必须将消息分成 16 字节的块。如果最后一块的长度不正好是 16 字节,则需要添加填充。有多种组合分组密码的方法,能够仅使用一个密钥对分成多个块的长消息进行加密。我们称之为操作模式。

欧洲央行

对于那些不懂密码学的人来说,遇到这个问题时他们可能会给出这样的解决方案:

ECB模式

ECB 模式将消息拆分成 16 字节的块,并使用相同的密钥和算法对每个块进行加密。假设如果算法是安全的,那么没有密钥就无法解密任何块。

然而,ECB 存在一个问题,那就是它会泄露太多关于加密消息的信息。分组密码是确定性的,这意味着当你用相同的密钥和相同的算法加密同一个 16 字节分组时,它总是会返回相同的密文。所以,如果你加密包含重复序列的内容,这些重复序列在加密后也会显示出来。最著名的例子是加密一张企鹅图片的结果,其中相同颜色的部分被加密成相同的改变颜色,从而完全清晰地表明这是一张企鹅图片。

欧洲央行企鹅

假设一款视频会议应用决定为用户提供加密安全保障,并决定使用 ECB 作为其运行模式。你觉得这会是一个不错的选择吗?嗯哼

ECB 也容易受到其他攻击[1] [2]的影响,虽然了解这些攻击的细节并不重要,但重要的是永远不要在任何应用程序中使用 ECB。总有更好的选择。

加拿大广播公司

CBC模式

在上图中,我们可以看到 CBC 模式的加密过程。其中带叉的圆圈表示按位异或运算。在 CBC 中,前一个区块的密文在加密前会与后一个区块进行异或运算。这样做会根据前一个区块改变密文,从而避免 ECB 中不掩蔽模式的问题。如果我们使用 CBC 加密企鹅图像,我们只会看到类似随机噪声的内容。

在 CBC 中,我们还引入了初始化向量 (IV) 的概念。IV 是为每条消息生成的随机块,用于屏蔽模式中的第一个块。以下是上一集链接中与 IV 相关的一些规则:


不要重复使用 IV 和密钥对

使用相同的 IV 和密钥加密时,密文的第一个区块无法通过不同消息中的模式泄露信息。在其他模式(例如 OFB 和 CTR)下,重复使用 IV 会完全破坏安全性,使其很容易被破解。切勿重复使用 IV。

尽管必须是唯一的,IV 并不是秘密的,并且通常与加密消息一起存储或发送。

不要使用错误衍生的 IV

正如上一条规则所述,IV 并非秘密。但它们应该是不可预测的,这意味着它们应该是随机生成的。我们将在本文后面讨论随机性。

不要使用静态 IV

我猜这指的是不要将IV硬编码到代码中。如果硬编码,就意味着重复使用相同的IV,而我们已经说过这不是一个好主意。


如果使用得当,CBC 在大多数情况下都是比 ECB 更安全的替代方案。例如,CBC 非常适合在服务器中存储加密数据。

不过,有些情况下不应该使用 CBC,尤其是在客户端-服务器环境中传输 CBC 加密数据的情况下,服务器容易受到重放攻击。这与我最喜欢的加密攻击有关:填充预言攻击。它完美地展示了极少量的信息泄露如何导致加密系统彻底崩溃。

CBC 也不适用于加密非常大的文件或磁盘。由于其设计方式,加密过程无法并行执行(解密过程可以并行执行),并且在加密磁盘的情况下,如果不重新加密所有后续块,就无法更改当前块。

其他的

操作模式有很多种。我推荐的都是那些被 NIST 等标准机构认可的模式。我认为 CBC 是一个很好的学习操作模式示例,只要正确使用并适用于其适用的用例,它就绝对安全。针对特定问题,有专门的操作模式,例如为磁盘加密设计的操作模式。在讨论消息认证时,我们还会看到另一种操作模式 (GCM),我们将看到,在 CBC 不适用的客户端-服务器场景中,GCM 是一个更好的选择。

随机性

随机数

在使用密码学时,我们有时需要创建一些别人无法知晓的秘密,或者一些行为不可预测的数据。例如,在生成安全的区块加密密钥时,我们不仅要使其足够大,还要使其随机,因为如果密钥只是“AAAAAA……”,那么密钥的长度就无关紧要了。此外,初始化向量(IV)也需要随机生成。因此,我们需要一种用计算机生成随机位的方法。问题在于,计算机与随机性截然相反。处理器总是以相同的方式、可预测的方式执行指令。那么,计算机如何才能生成随机性呢?

伪随机数生成器

我们将计算机中的随机数生成器称为伪随机数生成器 (PRNG)。我们在名称开头添加“伪”一词,表示它们并非真正完全随机,即完全不可预测。但它们的行为方式是随机的,这意味着如果我们生成许多随机数,它们将遵循与真正随机过程相同的分布。

所有现代编程语言都包含通用的伪随机数生成器 (PRNG)。PRNG 可用于生成用于统计研究的随机数分布、随机抽样、掷骰子等用途……就这些用途而言,随机数生成的可重复性实际上非常有趣。例如,如果我们进行一项研究并随机抽取样本,我们希望样本是随机分布的,但我们希望能够共享代码,以便生成相同的样本。这些 PRNG 实际上是复杂的算法,可以确定性地生成随机分布。它们以种子作为初始输入,给定相同的种子,它们将生成相同的数字。

假设我们想要生成一个安全密钥,并使用标准伪随机数生成器 (PRNG)。如果我们真的希望密钥是随机的,首先需要一个真正的随机种子。问题是,要生成随机种子,我们需要一个无法用 PRNG 生成的数字,但它必须是随机的。使用 PRNG 时,一个常见的解决方案是使用当前时间戳作为种子。时间戳具有随时变化的特性,但它本身并非随机的。假设我们使用基于时间的 PRNG 生成安全密钥。现在假设攻击者知道我们在 2014 年生成了密钥,而这个时间区间并不短。根据谷歌的数据,一个日历年有 3.154e+7 秒,大约是 3100 万秒。攻击者只需尝试使用 3100 万个可能的种子来生成密钥,就能破解我们的密钥。对于计算机来说,使用 PRNG 生成 3100 万个随机密钥并不算多。我们实际上把 256 位密钥(115792089237316195423570985008687907853269984665640564039457584007913129639936 个可能的密钥)的安全性降低到了 20 位密钥的安全性。而且,使用毫秒(甚至纳秒)的时间戳也不是一个好的解决方案。

密码安全随机性

正如我们所见,为了使随机数生成能够安全地用于加密应用,我们需要对它们提出一些额外的要求。我们需要不依赖于初始输入且非确定性的随机数生成器。它们被称为密码安全 PRNG,包含在大多数流行的密码库中。它们使用各种外部随机源,通常是物理属性或用户交互。如果我们是物理学家,我们可能会讨论真正的随机性是否存在,或者考虑到整个宇宙的状态,我们是否可以决定未来。幸运的是,我们无需保护我们的系统免受无所不知的神灵的侵害,只需要它们用当前的技术抵御聪明的猴子,因此我们假设物理属性足够混乱和嘈杂,以至于无法预测。可以利用的参数包括处理器的温度、光标的移动,甚至熔岩灯的视频

在编写加密代码时,我们需要的任何随机性都由加密安全生成器提供,这一点非常重要,因为标准库提供的普通 PRNG 不适合这种用途。

代码示例

在下面的代码示例中,我将使用本期学习到的概念,提供一个加密/解密类的 TypeScript 实现。需要注意的是,我将使用 CBC,这意味着以下代码切勿用于客户端/服务器通信,因为存在重放攻击的可能性。此类代码适用于在数据库中存储加密数据或在计算机上存储机密文件等情况。

import * as crypto from "crypto";

export interface EncryptedData {
    iv: string;
    data: string;
}

export class AES_256_CBC {
    private readonly encryptionKey: Buffer;
    private readonly ALGORITHM = 'aes-256-cbc';

    constructor(key: string) {
        // Key is a base64 encoded 256bit key (32 bytes)
        // It should be stored safely as an environment variable
        // and never uploaded into version control
        this.encryptionKey = Buffer.from(key, 'base64');
    }

    public encrypt(text: string): EncryptedData {
        // We generate the iv with a secure PRNG
        const iv = crypto.randomBytes(16);
        // We create the cipher with algorithm aes-256-cbc
        const cipher = crypto.createCipheriv(this.ALGORITHM,
            this.encryptionKey, iv)
        let enc = cipher.update(text, 'utf8', 'base64');
        enc += cipher.final('base64');

        // We return both the encrypted text and the iv in base64.
        // If we want to store it in a DB which does not support
        // JSON format, we can serialize it by appending the data
        // to the iv and converting into a single string.
        return {
            iv: iv.toString('base64'),
            data: enc,
        }
    }

    public decrypt(enc: EncryptedData): string {
        const decipher = crypto.createDecipheriv(this.ALGORITHM,
            this.encryptionKey, Buffer.from(enc.iv, 'base64'));
        let str = decipher.update(enc.data, 'base64', 'utf8');
        str += decipher.final('utf8');
        return str;
    }

}
Enter fullscreen mode Exit fullscreen mode

本期内容就到这里😊。希望你现在能更好地理解使用区块加密时使用正确参数和操作模式的重要性,以及使用加密安全随机数的重要性。

在第 3 集中,我们将讨论如何使用消息认证来保护用于通信目的的加密,并讨论使用 JWT 实现登录的最佳方法。

鏂囩珷鏉ユ簮锛�https://dev.to/shierve/cryptography-for-programmers-2-blocks-and-randomness-3ho1
PREV
四个文件中的完整堆栈文件 1:数据库文件 2:服务器文件 3:组件文件 4:前端总结
NEXT
裤子可选:远程工作