密码学:从“加密”与“解密”说开去

12999 字
65 分钟
密码学:从“加密”与“解密”说开去

我们每天都在使用密码学,只是大多数时候并不会意识到它的存在。

打开一个 HTTPS 网站、登录账号、连接 SSH、验证软件签名、使用密码管理器,甚至只是下载一个带 SHA-256 校验值的文件,背后都离不开密码学。

很多人第一次接触这个领域时,会把“加密”理解为“把正常文字变成乱码”,把“解密”理解为“把乱码恢复回来”。这种理解并没有错,但它只触及了密码学最表面的一层。

真正的现代密码学,关心的不只是“别人能不能看懂内容”,还包括:

  • 数据有没有被篡改;
  • 对方是不是自己认为的那个人;
  • 文件是不是来自可信的发布者;
  • 密钥应该如何交换;
  • 密码应该如何保存;
  • 即使算法完全公开,系统是否仍然安全。

也正因为如此,“加密”和“解密”其实只是密码学体系中的两个入口。

从“明文”与“密文”开始#

最基本的加密过程,可以表示为:

明文 Plaintext
↓
加密算法 + 密钥
↓
密文 Ciphertext

例如,原始内容可能是:

Hello

经过某种加密算法处理后,则可能变成一串无法直接理解的数据:

7F A2 C9 13 ...

反过来,解密就是:

密文
↓
解密算法 + 正确的密钥
↓
明文

这里有三个最重要的概念:

  • 明文(Plaintext):原始、可理解的数据;
  • 密文(Ciphertext):经过加密后的数据;
  • 密钥(Key):控制加密和解密过程的关键参数。

现代密码学通常并不依赖“算法保密”。

恰恰相反,大量真正可靠的密码算法,例如 AES、RSA、SHA 系列,都拥有公开规范,任何人都可以研究其内部原理。

安全性真正依赖的是:

即使攻击者完全知道算法是如何工作的,只要不知道正确的密钥,仍然无法在可接受的成本内恢复原文。

这也是现代密码学最重要的思想之一。

为什么网络通信需要加密#

当浏览器访问一个网站时,数据并不是从你的电脑瞬间“跳”到服务器。

一次网络请求中间可能经过:

你的电脑
↓
家庭路由器
↓
运营商网络
↓
互联网中的多个节点
↓
目标服务器

如果所有数据都以明文形式传输,那么只要某个中间环节能够观察流量,就可能看到通信内容。

例如:

  • 用户名和密码;
  • Cookie;
  • API Token;
  • 聊天内容;
  • 文件内容;
  • 表单数据。

因此,加密首先解决的是一个非常直观的问题:

即使数据被截获,也不能让旁观者直接理解其中的内容。

这叫做机密性(Confidentiality)。

但仅仅“看不到”还远远不够。

假设攻击者虽然无法理解密文,却能够偷偷修改其中一部分,那么通信依然可能出现问题。因此,现代密码系统通常还需要保证:

  • 完整性(Integrity):数据没有被修改;
  • 身份认证(Authentication):通信对象确实是自己认为的那一方;
  • 不可否认性(Non-repudiation):某些情况下,可以证明某个操作确实由特定主体完成。

这也是为什么 HTTPS 并不仅仅是“给网页内容加密”。

凯撒密码:最早期的加密手段#

人类使用密码的历史远早于计算机。

最经典的例子之一是凯撒密码(Caesar Cipher)。

假设把所有英文字母向后移动三位:

A → D
B → E
C → F

那么:

HELLO

会变成:

KHOOR

只要知道“移动三位”这个规则,就可以反向恢复原文。

这种方法现在当然谈不上安全,因为英语字母只有 26 个,攻击者完全可以把所有偏移量都尝试一遍。

这种把所有可能情况逐一尝试的方法,就叫做:暴力破解(Brute Force)。

早期密码系统中,很多方案的安全性都高度依赖“别人不知道规则”。但随着密码学的发展,人们逐渐认识到:

如果一个系统必须依靠算法本身保密才能安全,那么它通常并不是真正可靠的密码系统。

密钥:现代加密真正的核心#

现代加密可以简单写成:

Ciphertext = Encrypt(Key, Plaintext)

解密则是:

Plaintext = Decrypt(Key, Ciphertext)

其中真正需要严格保护的是:

Key

也就是密钥。

可以把算法理解为一把公开设计的锁,而密钥才是真正决定谁能开锁的东西。

这就是所谓的 Kerckhoffs 原则,即:一个密码系统即使除了密钥之外的所有设计都被攻击者知道,也应该仍然保持安全。

“最好的加密就是公开?”:为什么算法不怕被看见#

这个问题确实有点反直觉:

人们千方百计想要加密一段数据,可这样的加密方法反而是公开的吗?

准确来说,现代密码学追求的并不是“算法越公开越安全”,而是:

一个真正可靠的密码系统,不应该把“攻击者不知道算法”当成安全前提。

这和日常生活中的直觉有些不同。

如果我们自己发明了一套加密规则,例如:

把每个字母先向后移动 7 位,再把字符串倒过来,然后每两个字符交换位置。

只要暂时没人知道规则,密文看起来可能确实很神秘。

但这种安全性依赖的是:

别人还没有猜到我们在做什么。

一旦规则泄露,系统可能立刻失去保护能力。

这种思路通常被称为依赖隐蔽性或“把算法藏起来”。它并不是说隐藏实现细节毫无价值,而是说:隐藏算法本身不能成为密码系统最后一道、甚至唯一一道防线。

现代密码学采用的是几乎相反的思路。

AES 的规范是公开的,SHA-256 的每一步运算是公开的,RSA 的数学原理也是公开的。攻击者完全可以知道:

  • 数据被怎样分块;
  • 每一轮进行了什么运算;
  • 公钥中包含哪些参数;
  • 哈希函数怎样处理输入;
  • 整个协议按照什么顺序执行。

真正需要保密的,是其中的密钥,或者更具体一点,是私钥。

可以把它类比成现实中的门锁。

一把可靠的锁,不应该要求:

“小偷绝对不能知道这把锁的内部结构。”

更合理的目标是:

“即使小偷已经拆过同型号的锁、拥有设计图、知道每个零件怎样运动,只要没有正确钥匙,仍然很难把这扇门打开。”

这正是“算法公开、密钥保密”的意义。

公开还有另一个非常重要的好处:任何人都可以尝试攻击它。

一种新的密码算法刚被提出时,即使设计者认为它非常牢固,也不能因此证明它真的安全。密码算法里可能隐藏着:

  • 某种设计者没有发现的数学规律;
  • 某些输入下异常薄弱的结构;
  • 比暴力破解更高效的攻击方法;
  • 参数选择上的缺陷;
  • 与实际实现结合后才会暴露的问题。

如果算法长期公开,世界各地的密码学家、安全研究人员,甚至攻击者都可以研究它,寻找这些弱点。

有些算法经不起这样的检验,很快就会退出历史舞台;另一些算法在多年甚至几十年的公开分析中,始终没有出现足以破坏其实际安全性的攻击,于是才逐渐获得信任。

因此,“公开”本身并不会自动让一个算法变安全,但它让算法能够经历真正严格的检验。

这也是为什么:

“没人知道怎么破解”与“因为没人知道算法,所以没人破解”是两件完全不同的事情。

前者意味着:攻击者知道规则,仍然找不到现实可行的办法。

这也是为什么现代密码学普遍不鼓励个人随意设计所谓的“私有加密算法”。一个自己设计的算法也许看起来足够复杂,但“我暂时看不出漏洞”并不能证明它安全。成熟算法最宝贵的地方之一,恰恰是它们已经接受了大量公开分析。

所以,标题中的问题也可以得出答案了:

优秀的密码算法不需要害怕公开。真正需要保密的是密钥,而不是算法本身。

当然,这并不代表把密钥、随机数种子、服务器私钥或实现中的敏感信息公开也没有关系。算法公开与秘密材料公开完全是两回事。

Note

“算法公开”说的是设计与规则可以公开审查,并不是要求把运行中的秘密材料一起公开。像私钥、会话密钥、密码、随机种子等,依然必须受到严格保护。

对称加密:一把钥匙负责加密和解密#

现代加密首先可以分成两大类,即:对称加密与非对称加密。

**对称加密(Symmetric Encryption)**的特点非常直接:

加密和解密使用同一个密钥。

例如 Alice 和 Bob 事先拥有同一个密钥:

Alice
↓
密钥 K + 加密算法
↓
密文
↓
密钥 K + 解密算法
↓
Bob

最著名的现代对称加密算法之一就是 AES(Advanced Encryption Standard),常见形式包括 AES-128、AES-192 和 AES-256,其中数字表示密钥长度。

如果只用一句话概括 AES 的思路,可以把它理解成:

把一小块数据反复“打散、替换、混合”,每一轮都掺入由密钥派生出的不同材料,直到原始结构被彻底打乱。

AES 并不是把整份文件一次性塞进一个巨大的公式里,而是以固定大小的数据块为基本单位处理。AES 的分组大小固定为 128 bit,也就是 16 字节。可以把这 16 字节想象成一个 4×4 的小方格,算法会在这个方格上进行多轮变换。

每一轮里最核心的直觉大致有几步:

  • 替换:把每个字节按照固定规则换成另一个值,打破输入与输出之间过于直接的关系;
  • 打乱位置:把原本相邻的数据搬到不同位置;
  • 混合:让一个位置的变化扩散到更多位置;
  • 加入轮密钥:把由原始密钥推导出的当前轮密钥混进数据。

这几步不断重复,就会产生密码学中常说的两种效果:混淆(Confusion)和扩散(Diffusion)。前者让“密钥和密文之间的关系”变得难以分析,后者让明文中一个很小的变化迅速影响到大量密文位。

例如,原文只改动一个字符,最后生成的密文往往会出现大面积变化,而不是只改动某一个位置。

AES-128、AES-192 和 AES-256 使用的核心结构相同,主要区别在于密钥长度以及执行轮数。密钥越长,可供攻击者穷举的空间越大,但计算开销也会稍有增加。

对称加密最大的优点是 速度快。

因此,它非常适合大量数据的实际加密工作,例如:

  • HTTPS 中的正文数据传输;
  • VPN 流量;
  • 硬盘加密;
  • 压缩包加密;
  • 数据库字段加密;
  • 文件加密。

但它也面临一个非常现实的问题:

双方第一次通信时,如何安全地把密钥交给对方?

如果直接通过一个尚未加密的网络连接发送密钥,那么攻击者只要截获这把密钥,后续所有加密就失去了意义。

这个问题推动了另一类密码体系的发展。

非对称加密:公钥与私钥#

非对称加密(Asymmetric Encryption),也叫公钥密码学(Public-key Cryptography)。

它不再只依赖一把密钥,而是使用一对彼此相关的密钥:

公钥 Public Key
私钥 Private Key

其中:

  • 公钥可以公开;
  • 私钥必须严格保密。

假设 Bob 生成了一对密钥,并把公钥公开。

Alice 想给 Bob 发送秘密内容时,可以使用 Bob 的公钥加密数据,而只有掌握对应私钥的 Bob 才能够完成相应的解密操作。

这带来了一个非常重要的能力:

双方不需要提前通过秘密渠道交换同一把密钥。

因此,公钥密码学很大程度上解决了传统对称加密中的密钥分发问题。

不过,现实中的非对称密码系统远比“公钥加密、私钥解密”这句话更复杂。现代协议中,公钥密码学经常承担的是身份认证、数字签名、密钥协商等工作,而不是直接加密全部业务数据。

RSA:最著名的公钥密码算法之一#

RSA 是密码学史上最著名的非对称算法之一。

它于 1977 年提出,名称来自三位设计者:

  • Ron Rivest;
  • Adi Shamir;
  • Leonard Adleman。

RSA 的安全性与大整数因数分解问题密切相关。

简单理解一下,假设现在有两个很大的质数 p 和 q,把它们相乘得到:

n = p × q

这个乘法非常容易做。

但如果只把一个足够大的 n 交给别人,让对方重新找出原来的 p 和 q,计算难度就会急剧上升。RSA 正是利用了这种“正向容易、逆向困难”的不对称性。

当然,真正的 RSA 并不是“把两个质数相乘以后直接拿来加密”这么简单。它还会利用模运算构造出一组彼此配合的公开参数和私有参数。

可以把它想象成一种特殊的数学锁:

  • 公钥中包含一套可以公开的参数,任何人都可以拿来执行某种正向运算;
  • 私钥中保存的是只有密钥持有者才知道的关键数学信息;
  • 没有私钥的人虽然知道算法、也知道公钥,但想从结果反推出对应秘密,仍然会面临非常困难的计算问题。

这里的关键并不是“公钥和私钥是一对完全相反的密码”,而是它们在数学上存在特殊关系:公开其中一部分信息,不等于公开另一部分信息。

RSA 既可以被用于某些加密场景,也可以构造数字签名。但现代协议会对原始 RSA 运算再叠加严格的填充方案,例如 OAEP 或 PSS,因为“直接拿数学公式处理原文”本身并不安全。

Note

RSA、RSA-OAEP、RSA-PSS 不是同一个概念。 RSA 是底层数学结构;OAEP 是常见的 RSA 加密填充方案,PSS 则用于 RSA 数字签名。实际系统讨论“RSA 是否安全”时,往往还必须看它具体搭配了什么方案和参数。

现代环境中常见的 RSA 密钥长度有:

2048 bit、3072 bit、4096 bit

不过,在现代 TLS 中,RSA 已经越来越少承担传统意义上的密钥交换任务,基于椭圆曲线的方案更加常见。

ECC:更现代的椭圆曲线密码学#

**ECC(Elliptic Curve Cryptography)**指的是椭圆曲线密码学。

它利用椭圆曲线上的数学难题构建密码系统。名字里的“椭圆”很容易让人误以为是在椭圆上画图,实际上这里说的是一类具有特定代数形式的曲线。

理解 ECC,不需要先掌握复杂公式。可以先凭借一个直觉:

在曲线上,从一个起点按照规则“走很多步”很容易;但只看到起点和终点,想反推出到底走了多少步,却非常困难。

在 ECC 中,这种“走很多步”的操作对应椭圆曲线上的标量乘法。给定一个公开起点 G 和一个秘密整数 k,计算:

P = k × G

是高效可行的。

但如果别人只知道 G 和 P,想反推出 k,就会遇到所谓的椭圆曲线离散对数问题。对于选得合适的曲线和参数,这个反推过程在现实中极其困难。

于是,秘密整数可以作为私钥,而计算出的曲线点可以作为公钥。公开公钥不会直接暴露私钥。

ECC 的一个重要优势是:

在相近安全强度下,它通常可以使用比 RSA 更短的密钥。

例如非常粗略地比较:RSA 3072-bit 与 ECC 256-bit 强度相当。

因此,椭圆曲线方案通常具有:

  • 密钥更短;
  • 传输开销更小;
  • 计算效率较高。

在 HTTPS、SSH、数字签名和密钥交换中,经常能看到:

ECDH、ECDHE、ECDSA、Ed25519、X25519

这些名称。

它们虽然都与现代公钥密码学有关,但承担的具体工作并不完全相同:

  • ECDH / X25519 更偏向于“协商共同秘密”;
  • ECDHE 中的 E 表示 Ephemeral,也就是临时密钥,常用于获得前向保密;
  • ECDSA / Ed25519 则主要用于数字签名。

也就是说,“ECC”更像是一整类数学工具,而不是某一个单独用途的算法。

Note

这里最容易混淆的是:ECC 是一类公钥密码学方法,ECDH / X25519 主要用于密钥协商,ECDSA / Ed25519 主要用于数字签名。 它们都和椭圆曲线有关,但用途并不相同。

为什么现实世界常常采用“混合加密”#

如果非对称密码学这么方便,为什么不直接用它加密所有数据?

原因很简单:

非对称密码学通常比对称密码学慢得多。

因此,现实中的安全协议通常会把两者结合起来。

大致过程是:

非对称密码学
↓
身份认证 / 密钥协商
↓
生成双方共享的会话密钥
↓
AES / ChaCha20 等对称算法
↓
大量业务数据传输

这种设计称为 混合加密系统(Hybrid Cryptosystem)。

这里还有一个非常关键的角色:Diffie-Hellman 密钥交换。

它解决的问题并不是“直接把一把密钥发给对方”,而是:

双方各自保留一个秘密值,只交换经过数学变换后的公开值,最后却能各自在本地算出同一个共享秘密。

旁观者虽然能看到双方公开交换的数据,却很难从这些公开值反推出双方各自的秘密,因此也难以算出最终的共享结果。

如果使用椭圆曲线实现这一思想,就得到 ECDH;如果每次连接都临时生成新的密钥对,则常见形式就是 ECDHE。

可以把它类比成“调色”:

  • Alice 有一种只有自己知道的秘密颜料 A;
  • Bob 也有自己的秘密颜料 B;
  • 双方先约定一种公开底色 G;
  • 各自把秘密颜色与底色混合后交换,变为 A + G & B + G ;
  • 再把收到的混合色与自己的秘密颜色继续混合,变为 A + G + B & B + G + A。

最终,两个人会得到同一种综合色;旁观者虽然看到了公开底色和中间交换的颜色,却很难倒推出双方各自真正的秘密颜色。

真实的 Diffie-Hellman 当然使用的是数学运算而不是颜料,但“公开交换材料,却不直接交换最终秘密”的直觉是相似的。

可以把它理解成:

非对称密码学负责“安全地建立关系”,对称密码学负责“高效地传输大量数据”。

这正是现代 HTTPS 的核心思路之一。

HTTPS 并不是简单地“用 RSA 加密网页”#

很多早期科普文章会把 HTTPS 描述成:

浏览器使用服务器的 RSA 公钥加密数据,服务器使用私钥解密。

这种说法虽然能帮助初学者理解“公钥与私钥”,但对于现代 TLS 来说已经过于简化。

一次现代 HTTPS 连接,首先会经过 TLS 握手。

大致可以理解为:

浏览器
│
│ ClientHello
▼
服务器
服务器
│
│ ServerHello
│ Certificate
▼
浏览器

浏览器首先需要确认一个关键问题:

正在通信的服务器,真的是自己想访问的那个网站吗?

这里就需要使用数字证书(Certificate)。

证书通常由受信任的 CA,即**证书颁发机构(Certificate Authority)**签发。

常见的 CA 包括:

Let's Encrypt、DigiCert、GlobalSign、Sectigo

浏览器验证证书后,双方还会完成密钥协商。

现代 TLS 中常见的密钥协商方案之一是:

ECDHE

双方最终获得用于当前连接的会话密钥。

真正大量传输 HTTP 数据时,则通常使用高效的对称加密,例如:

AES-GCM

或者:

ChaCha20-Poly1305

因此:

HTTP + TLS = HTTPS

HTTPS 实际上是多种密码学机制共同工作的结果,而并不是某一种“加密算法”的简单应用。

Note

因此,“HTTPS 就是用服务器公钥把所有网页内容加密”只适合作为非常早期的入门类比。现代 TLS 通常会先完成身份认证与密钥协商,再使用对称加密保护大量业务数据。

加密并不等于哈希#

接触密码学后,一个最容易混淆的概念就是:

Encryption 和 Hash 并不是同一回事。

加密具有可逆性:

明文
↓
加密
↓
密文
↓
解密
↓
明文

而哈希通常被设计成单向过程:

原始数据
↓
Hash
↓
摘要

例如 hello 可以通过 SHA-256 得到一个固定长度的哈希摘要。

从原文计算摘要非常容易,但不能像解密密文一样,从摘要中直接恢复原文。

以 SHA-256 为例,它可以被粗略理解成一个“不断搅拌信息的摘要机”。

输入数据会先被切分成固定大小的数据块,每一块都会进入一系列轮函数,与当前内部状态反复混合。处理完这一块后得到的新状态,又会带到下一块继续处理。所有数据处理结束后,最终留下 256 bit 的结果,也就是 SHA-256 摘要。

这里最重要的不是记住每一步位运算,而是理解它想达到的几个效果:

  • 输入再长,输出长度始终固定;
  • 输入发生极小变化,输出通常会大幅改变;
  • 已知摘要,很难反推出原文;
  • 想主动构造两份不同内容却得到同一摘要,也应该非常困难。

这种“输入只变一点,输出却像彻底重新洗牌”的现象叫做雪崩效应(Avalanche Effect)。

例如 hello 和 Hello 只差一个字母大小写,但它们的 SHA-256 摘要完全不同。

因此,哈希并不是“把数据压缩后还可以解压”的过程,而更像是给数据生成一个高度敏感、固定长度的数学指纹。

常见的现代哈希算法包括:

SHA-256、SHA-384、SHA-512、SHA-3、BLAKE2、BLAKE3

哈希的一个非常重要的用途,就是检查数据完整性。

为什么下载页面经常提供 SHA-256#

假设一个系统镜像下载页同时提供:

ubuntu.iso

和对应的:

SHA256: A91F...

下载完成后,你可以对本地文件重新计算 SHA-256。

如果结果也是:

A91F...

那么说明你手里的文件内容和发布方提供摘要时对应的文件一致。

即使文件只发生非常微小的变化,哈希结果通常也会完全不同。

不过需要注意:

哈希只能证明“数据与某个摘要一致”,并不能自动证明“这个摘要本身可信”。

如果攻击者既替换了下载文件,又能够替换网页上的 SHA-256,那么单纯的哈希校验同样会失效。

因此,更严格的软件分发体系通常还会加入数字签名。

Note

如果下载文件和对应的 SHA-256 都来自同一个已经被篡改的页面,那么“哈希一致”也无法证明文件来自真正的发布者。哈希负责检查内容是否一致,可信来源和数字签名负责补上“这个摘要是谁提供的”这一层。

数字签名:证明“是谁发的”和“有没有被篡改”#

数字签名并不是“把整个文件用私钥加密”。

这种“私钥加密、公钥解密”的说法在一些入门材料里很常见,但对于现代数字签名来说并不准确。

Note

更准确的理解是:签名算法利用私钥生成一段只有对应公钥才能验证的签名数据。它和“把整份消息反向加密一遍”不是一回事。

真实系统通常会先对消息计算哈希摘要,再使用私钥对这个摘要相关的数据执行签名算法。可以粗略理解为:

原始消息
↓
计算哈希
↓
消息摘要
↓
私钥 + 签名算法
↓
数字签名

验证方收到消息和签名后,会自己重新计算消息摘要,再使用公钥按照对应算法验证签名是否成立。

这样做有两个好处:一是无需对一份可能很大的文件直接执行昂贵的公钥运算;二是只要消息发生细微变化,摘要就会改变,原来的签名也就无法继续通过验证。

它真正解决的是:

如何证明一份数据确实与某个私钥持有者有关,并且签名之后没有被修改?

假设 Alice 拥有一对私钥和公钥。她使用私钥对消息的摘要生成签名,别人拿到原始消息、签名和 Alice 的公钥后,就可以验证签名是否匹配。

如果验证成功,可以说明:

  • 当前消息与签名对应;
  • 消息在签名之后没有发生相应篡改;
  • 签名由对应私钥生成。

数字签名被大量应用在:

  • HTTPS 证书;
  • 软件发布;
  • 操作系统软件包;
  • Git Commit;
  • SSH;
  • 应用程序签名。

例如 GitHub 中有时会在 Commit 旁边显示:

Verified

本质上就是平台成功验证了相应的数字签名。

这并不是说 Git Commit 被“加密”了,而是说明其签名能够通过验证。

为什么密码不应该直接“加密保存”#

“网站怎么保存用户密码”是密码学中非常典型的问题。

很多人的第一反应是:

把密码加密后保存到数据库。

但这通常不是正确做法。

如果系统保存的是可解密的密码,那么只要攻击者得到了解密密钥,所有用户密码都有可能被直接恢复。

更加合理的方式是保存:

密码的哈希结果。

用户注册时:

Password
↓
Password Hash
↓
数据库

用户登录时:

用户输入密码
↓
重新计算密码哈希
↓
与数据库中的结果比较

这样,系统本身并不需要保存用户的原始密码。

而且,真正用于密码存储的算法通常不是普通 SHA-256,而是专门设计的密码哈希算法,例如 Argon2、bcrypt、scrypt 和 PBKDF2。

为什么不直接使用 SHA-256?

因为 SHA-256 的设计目标之一就是 计算得非常快。对于普通文件完整性检查,这是一件好事,但对于密码来说,太快反而会帮助攻击者——攻击者可以在很短的时间内尝试大量候选密码。

密码哈希算法故意反其道而行。

它们会有意识地把一次计算变得更“贵”:

  • PBKDF2 的核心思路是重复计算很多轮,让每一次猜测都更耗时间;
  • bcrypt 同样通过可调成本参数提高重复计算的代价;
  • scrypt 不只消耗 CPU,还刻意要求较多内存,让大规模并行破解更昂贵;
  • Argon2 更进一步,把时间成本、内存成本和并行度都设计成可调参数。

对正常用户来说,登录时多花几十毫秒或几百毫秒通常没有明显感觉;但对于一个想每秒尝试数百万、数十亿个密码的攻击者来说,这种额外成本会被成倍放大。

所以密码哈希的目标并不是“越快越好”,而是:

对正常登录来说足够快,对大规模猜密码来说尽可能昂贵。

Tip

这也是为什么密码数据库泄露后,攻击者通常不是“解密哈希”,而是不断尝试候选密码,再比较计算结果。专门的密码哈希算法要做的,就是让每一次这样的尝试都更昂贵。

Salt:为什么同一个密码不应该得到同一个结果#

密码哈希中还有一个非常重要的概念:

Salt(盐)。

假设两个用户使用完全相同的密码:

password123

如果系统只是简单计算:

Hash(password123)

那么两个人数据库中的结果也会完全一样。

攻击者一眼就能发现:

这两个账户使用了相同密码。

为了避免这种情况,系统会为不同用户生成随机 Salt:

用户 A:
password123 + randomA
用户 B:
password123 + randomB

然后再计算密码哈希。

这样,即使两个人使用同一个密码,最终保存的结果也不同。

Salt 本身通常不需要保密。

它主要用于抵御预计算攻击,并避免相同密码产生完全相同的数据库记录。

注意:Base64 看起来像密文,但它不是加密#

另一个非常常见的误区是:

一串看不懂的字符,就是加密后的结果。

Base64 就特别容易让人产生这种错觉。

例如:

hello

经过 Base64 编码后会变成:

aGVsbG8=

原本熟悉的单词突然变成了一串看起来毫无规律的字符,甚至还带有 =,确实很像某种“密文”。

但 Base64 并不是加密,而是一种编码方式(Encoding)。

它最大的区别在于:

Base64 不需要密钥,也没有任何秘密规则。

任何知道“这是一段 Base64”的人,都可以按照公开规则直接把它还原。

例如:

aGVsbG8=
↓ Base64 Decode
hello

这里不存在“破解”过程,也不存在“猜密钥”。

做的仅仅是:

按照 Base64 的规则把数据重新解释回来。

Base64 为什么会变成这些奇怪的字符#

要理解这一点,首先要知道:计算机中的数据本质上都是二进制。

例如字符 hello 在计算机中,会先对应成一串字节。

以 ASCII / UTF-8 表示时:

h → 01101000
e → 01100101
l → 01101100
l → 01101100
o → 01101111

把它们连接起来,就是一长串 0 和 1。

Base64 接下来并不会对这些数据进行什么“保密运算”,而只是改变它们的分组方式。

普通字节以 8 bit 为一组,而 Base64 会把数据重新切成 6 bit 一组。

原因是:

2^6 = 64

6 bit 一共正好可以表示 64 种不同的值。

Base64 因此准备了一张包含 64 个字符的公开字符表:

A-Z
a-z
0-9
+
/

一共正好 64 个字符。

每一个 6 bit 的数值,都对应表中的一个字符。

于是原来的二进制数据:

01101000 01100101 01101100 ...

会被重新切分成:

011010 000110 010101 101100 ...

然后每一组再查表转换成对应字符,最终就得到:

aGVsbG8=

所以 Base64 看起来很陌生,并不是因为信息被“加密”了,而只是因为:

原来的字节被换了一种表示方式。

为什么还需要 Base64#

既然 Base64 不能保密,那为什么互联网中到处都能看到它?

因为它解决的根本就不是安全问题,而是:

如何让二进制数据方便地通过只适合文本的环境传输。

图片、压缩文件、密钥、证书等数据,本质上可能包含各种字节值。

但一些早期协议、文本格式或者配置文件,并不适合直接放入任意二进制内容。

Base64 可以把这些数据转换成一组非常普通的可打印字符,因此更容易:

  • 放进 JSON、XML 等文本格式;
  • 通过电子邮件传输附件;
  • 嵌入网页;
  • 保存证书或密钥数据;
  • 在文本协议中传输二进制内容。

例如网页里有时可以看到:

data:image/png;base64,...

这并不是“加密图片”,而是在把图片的二进制内容转换成 Base64 文本,以便直接嵌入 HTML 或 CSS。

PEM 格式的证书中也经常可以看到:

-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----

中间那一大串看起来很像密文的内容,本质上通常也是经过 Base64 编码后的二进制数据。

= 又是什么#

Base64 末尾经常会出现:

=

或者:

==

这也很容易让它看起来更像某种复杂密码。

实际上,这个符号通常只是填充(Padding)。

因为 Base64 每次会把 3 个字节,也就是 24 bit 数据,转换成 4 组 6 bit:

24 bit
=
6 + 6 + 6 + 6

如果最后剩下的数据不足 3 字节,就需要使用 = 补齐输出长度。

因此:

aGVsbG8=

末尾的 = 并不包含某种密码学含义,只是在告诉解码器:

最后一组数据没有填满。

而且某些 Base64 变体甚至可以省略这些 Padding。

Base64 和加密真正的区别#

两者最关键的区别就是:

Base64 没有秘密。

Base64的编码规则是公开可查的,任何人都可以从 Base64 编码反推出原始信息。

而真正的加密数据,攻击者即使知道使用了什么算法,也会因为缺少密钥而无法解密。

因此:

Base64 ≠ Encryption

更加准确地说:

编码解决“怎么表示数据”,加密解决“谁能看懂数据”。

同样,URL Encoding、Hex Encoding、Unicode Encoding 等也都属于数据表示或转换方式,而不是加密。

所以以后看到一串“不像正常文字”的内容时,不能仅凭它看起来很乱,就判断它已经经过加密。

它也可能只是:

  • Base64;
  • 十六进制;
  • URL Encoding;
  • 压缩后的数据;
  • 二进制数据的文本表示。

真正判断一段数据是否经过加密,关键不在于它“看起来有多乱”,而在于:

恢复原始信息时,是否需要某个只有特定通信方掌握的秘密。

Note

Base64 常见特征包括只出现 A-Z、a-z、0-9、+、/,末尾有时带一个或两个 =,但这些都只能作为“可能是 Base64”的线索。不能仅凭外观判断一段数据到底是编码、压缩还是加密。

编码、加密、哈希和数字签名有什么区别#

把几个最容易混淆的概念放在一起,会更加清楚:

技术是否可逆是否使用密钥主要用途
Encoding是否数据表示与传输
Encryption是是保护机密性
Hash通常否否摘要、完整性检查
MAC否是完整性与身份认证
Digital Signature用公钥验证私钥签名身份、来源与完整性

这张表更适合把前文出现过的几个概念放在一起对照:它们虽然经常同时出现在一个安全系统里,但解决的问题并不相同。

密钥长度为什么重要#

密码系统的安全性与密钥空间密切相关。

例如 AES-128 的密钥理论上有:

2^128

种可能。

也就是大约:

340,282,366,920,938,463,463,374,607,431,768,211,456

种组合。

因此,传统意义上逐个尝试所有 AES-128 密钥在现实中并不可行。

AES-256 的密钥空间则更大:

2^256

所以在真正的攻击中,攻击者往往不会选择“把所有密钥试一遍”这种最笨的方法。

相比之下,他们更愿意尝试:

  • 弱密码;
  • 密钥泄露;
  • 配置错误;
  • 程序漏洞;
  • 随机数缺陷;
  • 社会工程;
  • 协议实现漏洞。

这也引出了密码学中一个非常重要的现实:

一个安全的算法,不等于一个安全的系统。

Tip

看到“使用 AES-256”时,最多只能说明某个环节采用了 AES-256。密钥怎么生成、怎么保存、Nonce 是否正确使用、协议有没有认证、程序有没有漏洞,这些同样会决定整个系统最终是否安全。

算法没有被破解,系统仍然可能被攻破#

假设一个程序使用 AES-256。

算法本身没有问题。

但开发者却直接把密钥写在源码中:

KEY = "MySecretKey..."

攻击者根本不需要破解 AES。

只需要把程序反编译,找到密钥即可。

又比如:

  • 私钥文件权限配置错误;
  • Token 被写进日志;
  • 密钥通过不安全的渠道发送;
  • 随机数发生器可以被预测;
  • Nonce 被错误重复使用。

这些问题都可能让一个“使用顶级密码算法”的系统彻底失效。

因此,现实中的密码安全不仅关心数学算法,还必须同时关心:

算法安全吗?
密钥长度足够吗?
密钥如何生成?
密钥保存在哪里?
密钥如何交换?
随机数可靠吗?
实现有没有漏洞?
参数有没有重复?

密码学是整个安全系统的一部分,而不是安全系统的全部。

为什么密码学如此依赖随机数#

现代密码系统中经常会出现:

Key
IV
Nonce
Salt
Challenge
Token

其中很多参数都需要依赖随机数生成。

但密码学中的随机数不能随便使用普通的伪随机函数。

例如一些编程环境中的普通:

Math.random()

通常并不是为密码学设计的。

密码系统需要的是:

CSPRNG(Cryptographically Secure Pseudorandom Number Generator)

也就是:

密码学安全伪随机数生成器。

它的重要要求之一是:

即使攻击者观察到之前的一部分输出,也不应该能够轻易预测后续结果。

Linux 中常见的安全随机来源包括:

/dev/urandom

而现代编程语言和操作系统通常也会提供专门的密码学安全随机 API。

如果随机数可以被预测,那么很多看起来“密钥长度很长”的安全系统仍然可能迅速崩溃。

IV 和 Nonce 到底是什么#

这是实际接触实际密码算法时,经常会遇到的概念。

它们通常不是密码,也不是秘密密钥。

它们是加密过程中额外使用的参数。

例如使用:

AES-GCM

时,通常需要提供一个 Nonce。

一个非常重要的原则是:

对同一个密钥来说,某些算法要求 Nonce 绝对不能重复。

如果错误地重复使用 Nonce,可能会严重破坏加密系统的安全性。

Note

“Nonce 不能重复”与“Nonce 必须随机”不是完全相同的要求。 不同算法对 Nonce 的生成方式有不同规定;有些方案最关键的是在同一密钥下保持唯一,而不是单纯追求“看起来随机”。实际使用时应遵循具体算法或协议的规范。

因此,实际的现代加密远不是:

AES(password, file)

这么简单。

更加完整的结构可能是:

Key
Nonce
Plaintext
AAD
↓
AES-GCM
↓
Ciphertext
Authentication Tag

其中 Authentication Tag 用来帮助检测数据是否被修改。

Warning

对 AES-GCM 来说,在同一密钥下重复使用不该重复的 Nonce 会造成严重安全问题,甚至可能同时破坏机密性和完整性。因此,Nonce 管理不是“附加细节”,而是算法正确使用的一部分。

只加密还不够:AEAD#

早期密码系统有时只关注:

让别人看不到内容。

但后来人们越来越意识到:

看不到,并不意味着改不了。

攻击者可能完全无法理解一段密文,却仍然能够修改它的某些字节。

如果系统不能发现这种篡改,就可能带来严重问题。

因此现代密码协议越来越倾向使用:

AEAD(Authenticated Encryption with Associated Data)

即:带关联数据的认证加密。

常见方案包括 AES-GCM 和 ChaCha20-Poly1305。

它们的思路不是“先随便加密,再额外附带一个校验码”,而是把加密和认证组合成一个完整过程。

以 AES-GCM 为例,可以把结果粗略理解成两部分:

  • Ciphertext:真正的密文,用于隐藏原始内容;
  • Authentication Tag:认证标签,用来检查密文以及相关数据有没有被篡改。

接收方解密时,并不是只要“密钥正确”就直接接受结果,而是还要验证认证标签。如果标签不匹配,就说明数据、Nonce、附加认证数据中的某一部分可能发生了变化,整个结果应当被视为无效。

ChaCha20-Poly1305 采用的是另一组内部算法:ChaCha20 负责生成密钥流并完成加密,Poly1305 负责生成认证标签。虽然内部数学结构与 AES-GCM 不同,但对使用者来说承担的目标非常相似:同时提供机密性和完整性。

因此,这类“既保密,又能检查是否被篡改”的设计,已经成为现代网络协议中的主流思路。

把整个密码学体系放在一起看#

看到这里,可以发现现代密码学并不是在寻找“世界上最强的一种加密算法”。

AES、RSA、ECC、SHA-256、Argon2 看起来都属于“密码学”,但它们解决的根本不是同一个问题。把它们放在一起比较谁“更强”,就像比较门锁、身份证、指纹、保险箱和快递封条谁更安全一样——它们本来就承担不同职责。

更实用的理解方式,是先问:

我现在到底想解决什么问题?

想解决的问题需要的工具常见算法或方案
不让别人看懂数据对称加密AES、ChaCha20
在公开网络上协商共同秘密密钥交换Diffie-Hellman、ECDH、X25519
建立公钥与私钥体系公钥密码学RSA、ECC
判断数据有没有变化哈希SHA-256、SHA-3、BLAKE2
证明消息来自某个私钥持有者数字签名RSA-PSS、ECDSA、Ed25519
同时保证保密和防篡改AEADAES-GCM、ChaCha20-Poly1305
安全保存用户密码密码哈希 / KDFArgon2、scrypt、bcrypt、PBKDF2

从这张表里可以看到,“密码学算法”并不等于“加密算法”。

SHA-256 不负责把密文解回来;Argon2 也不是拿来加密文件的;Ed25519 的主要任务不是隐藏内容,而是生成和验证签名;Diffie-Hellman 更不是把一段正文变成密文,而是帮助双方建立共享秘密。

它们真正的关系更像是一套工具箱:

密码学
│
├─ 保密
│ ├─ AES
│ └─ ChaCha20
│
├─ 建立共同秘密
│ ├─ Diffie-Hellman
│ ├─ ECDH
│ └─ X25519
│
├─ 摘要与完整性
│ ├─ SHA-256
│ └─ SHA-3
│
├─ 身份与签名
│ ├─ RSA-PSS
│ ├─ ECDSA
│ └─ Ed25519
│
├─ 认证加密
│ ├─ AES-GCM
│ └─ ChaCha20-Poly1305
│
└─ 密码存储
├─ Argon2
├─ bcrypt
├─ scrypt
└─ PBKDF2

真正的安全系统,往往就是从这个工具箱里拿出几样东西组合起来。

用 HTTPS 再把这些工具串一次#

前面分别介绍这些概念时,它们可能仍然显得有些零散。HTTPS 正好可以把其中很多工具连接起来。

一次现代 HTTPS 连接,可以粗略理解成下面这条链:

服务器证书 + 数字签名
↓
浏览器确认服务器身份
↓
ECDHE 等密钥协商
↓
双方得到共同的会话密钥
↓
AES-GCM / ChaCha20-Poly1305
↓
后续 HTTP 数据被加密并认证

这里每一个环节解决的问题都不同。

数字证书和数字签名主要解决的是:“我连接的真的是这个网站吗?”

ECDHE 解决的是:“双方怎样在有人旁观的网络上建立共同秘密?”

AES-GCM 或 ChaCha20-Poly1305 解决的是:“建立连接以后,大量网页数据怎样高效地保密,同时还能检测篡改?”

哈希函数则会在证书、签名、协议内部的多个位置参与计算和验证。

所以,HTTPS 的安全并不是来自某一个“超级算法”,而是来自多种密码学工具各司其职,再由 TLS 协议把它们组合起来。

同样的思路也能看到在 SSH、VPN、软件签名、密码管理器等系统中。

因此,当我们以后看到一句:

“这个系统使用了 AES-256。”

其实还不能据此判断整个系统是否安全。

我们还需要继续问:

  • 密钥是怎么生成的?
  • 密钥又是怎么交换的?
  • 数据有没有认证和完整性保护?
  • 对方的身份怎么确认?
  • 私钥保存在哪里?
  • 随机数和 Nonce 是否正确使用?
  • 协议和程序实现有没有漏洞?

这正是现代密码学从“一个加密算法”发展为“一整套安全系统”的地方。

Tip

如果以后再遇到一个陌生的密码学名词,可以先问一句:它到底是在解决“保密、完整性、身份、密钥交换,还是密码存储”中的哪一个问题? 往往比先记住算法名字更容易建立整体理解。

结语#

如果只是刚刚进入密码学领域,最值得先记住的其实不是某个算法的数学细节,而是以下几组区别:

加密 ≠ 哈希
加密 ≠ 编码
公钥 ≠ 私钥
密码 ≠ 密钥
哈希 ≠ 密码哈希
加密 ≠ 数字签名

这些概念一旦分清,之后再去理解 HTTPS、SSH、软件签名和密码存储,就会容易很多。

同时也应该记住现代密码学非常重要的一条原则:

真正可靠的安全系统,并不是依靠“别人不知道算法”,而是依靠公开、经过长期检验的算法,以及正确生成、交换、保存和使用的密钥。

所以,“最好的加密就是公开?”并不是说“公开以后自然就会安全”,而是说:足够成熟的密码算法,应当能够在所有规则都公开、所有攻击者都可以研究它的情况下,仍然把安全性牢牢建立在密钥和经过验证的数学难题之上。

从这个角度看,“加密”并不是把信息变成乱码这么简单。

它更像是一场围绕机密性、完整性、身份与信任展开的系统工程。

而我们每天使用的 HTTPS、SSH、数字证书和软件签名,正是这套系统在现实世界中的具体体现。

参考资料#

本文主要参考 NIST、IETF、RFC Editor、OWASP 等机构发布的密码学标准、协议规范与安全实践资料:

  1. NIST — FIPS 197: Advanced Encryption Standard (AES)
    AES 的官方标准,介绍 AES-128、AES-192、AES-256,以及 128 bit 分组、密钥扩展和各轮变换等基本结构。 NIST:FIPS 197 — Advanced Encryption Standard (AES)

  2. NIST — FIPS 180-4: Secure Hash Standard (SHS)
    介绍 SHA-224、SHA-256、SHA-384、SHA-512 等安全哈希算法,以及消息摘要在完整性检测中的用途。 NIST:FIPS 180-4 — Secure Hash Standard

  3. NIST — FIPS 186-5: Digital Signature Standard (DSS)
    NIST 数字签名标准,介绍数字签名在数据完整性、签名者身份认证等方面的作用,并规定相关签名算法。 NIST:FIPS 186-5 — Digital Signature Standard

  4. RFC 8017 — PKCS #1: RSA Cryptography Specifications Version 2.2
    RSA 密码体系的重要规范,介绍 RSA 的基础运算、密钥表示、RSA-OAEP 加密以及 RSA-PSS 数字签名等机制。 RFC Editor:RFC 8017 — RSA Cryptography Specifications

  5. NIST SP 800-56A Rev. 3 — Recommendation for Pair-Wise Key-Establishment Schemes Using Discrete Logarithm Cryptography
    介绍基于离散对数问题的密钥建立方法,包括 Diffie-Hellman、椭圆曲线 Diffie-Hellman 等,是理解 ECDH、密钥协商和共享秘密的重要资料。 NIST:SP 800-56A Rev. 3 — Key Establishment

  6. RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA)
    介绍 EdDSA 数字签名体系,以及 Ed25519、Ed448 等具体方案的密钥生成、签名和验证过程。 RFC Editor:RFC 8032 — EdDSA / Ed25519

  7. RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3
    TLS 1.3 协议规范,介绍现代 HTTPS 中的握手、证书认证、密钥协商、会话密钥以及认证加密等机制。 RFC Editor:RFC 8446 — TLS 1.3

  8. NIST SP 800-38D — Galois/Counter Mode (GCM) and GMAC
    介绍 GCM 认证加密模式,是理解 AES-GCM 如何同时提供数据机密性和完整性认证的重要资料。 NIST:SP 800-38D — GCM and GMAC

  9. RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols
    介绍 ChaCha20 流密码、Poly1305 消息认证器以及两者组合形成的 ChaCha20-Poly1305 AEAD 方案。 RFC Editor:RFC 8439 — ChaCha20 and Poly1305

  10. OWASP Cheat Sheet Series — Password Storage Cheat Sheet
    介绍为什么密码应当使用单向密码哈希而非可逆加密保存,并讨论 Salt、Argon2id、scrypt、bcrypt、PBKDF2 以及计算成本设计。 OWASP:Password Storage Cheat Sheet

  11. RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
    Argon2 的规范资料,介绍其密码哈希设计、内存硬化特性以及 Argon2d、Argon2i、Argon2id 等模式。 RFC Editor:RFC 9106 — Argon2

  12. RFC 4648 — The Base16, Base32, and Base64 Data Encodings
    Base64 的标准规范,详细说明 8 bit 数据如何重新组织为 6 bit 单元、64 字符编码表以及 = Padding 的含义,可用于理解为什么 Base64 是编码而不是加密。 RFC Editor:RFC 4648 — Base16, Base32 and Base64

  13. NIST SP 800-57 Part 1 Rev. 5 — Recommendation for Key Management: Part 1 — General
    介绍密码密钥的生命周期、密钥类型、密钥保护、生成、存储与管理,是理解“算法公开、密钥保密”以及密钥管理重要性的参考资料。 NIST:SP 800-57 Part 1 Rev. 5 — Key Management

  14. NIST SP 800-90A Rev. 1 — Recommendation for Random Number Generation Using Deterministic Random Bit Generators
    介绍密码学安全随机数生成器与 DRBG,包括基于哈希、HMAC 和分组密码构造的随机比特生成机制,是理解 CSPRNG 和密码学随机数的重要资料。 NIST:SP 800-90A Rev. 1 — Random Number Generation

  15. OWASP Cheat Sheet Series — Cryptographic Storage Cheat Sheet
    从工程实践角度介绍数据加密、密钥管理、算法选择以及密码不应使用可逆加密保存等问题,可作为本文“算法安全不等于系统安全”部分的补充资料。 OWASP:Cryptographic Storage Cheat Sheet

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

密码学:从“加密”与“解密”说开去
https://inn.nightbugclub.cn/posts/study/260921-encryption-and-decryption/
作者
Eternity_Riguru
发布于
2026-09-21
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Eternity_Riguru
Wish you have a nice day!
What am I doing?
在线

正在电脑前

公告
因平日较忙,更新频率低,敬请谅解。
分类
标签
最新动态
站点统计
文章
20
分类
4
标签
16
总字数
324,954
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.9
文章许可
CC BY-NC-SA 4.0
文章目录