密码学:从“加密”与“解密”说开去
我们每天都在使用密码学,只是大多数时候并不会意识到它的存在。
打开一个 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 → DB → EC → F那么:
HELLO
会变成:
KHOOR
只要知道“移动三位”这个规则,就可以反向恢复原文。
这种方法现在当然谈不上安全,因为英语字母只有 26 个,攻击者完全可以把所有偏移量都尝试一遍。
这种把所有可能情况逐一尝试的方法,就叫做:暴力破解(Brute Force)。
早期密码系统中,很多方案的安全性都高度依赖“别人不知道规则”。但随着密码学的发展,人们逐渐认识到:
如果一个系统必须依靠算法本身保密才能安全,那么它通常并不是真正可靠的密码系统。
密钥:现代加密真正的核心
现代加密可以简单写成:
Ciphertext = Encrypt(Key, Plaintext)解密则是:
Plaintext = Decrypt(Key, Ciphertext)其中真正需要严格保护的是:
Key
也就是密钥。
可以把算法理解为一把公开设计的锁,而密钥才是真正决定谁能开锁的东西。
这就是所谓的 Kerckhoffs 原则,即:一个密码系统即使除了密钥之外的所有设计都被攻击者知道,也应该仍然保持安全。
“最好的加密就是公开?”:为什么算法不怕被看见
这个问题确实有点反直觉:
人们千方百计想要加密一段数据,可这样的加密方法反而是公开的吗?
准确来说,现代密码学追求的并不是“算法越公开越安全”,而是:
一个真正可靠的密码系统,不应该把“攻击者不知道算法”当成安全前提。
这和日常生活中的直觉有些不同。
如果我们自己发明了一套加密规则,例如:
把每个字母先向后移动 7 位,再把字符串倒过来,然后每两个字符交换位置。
只要暂时没人知道规则,密文看起来可能确实很神秘。
但这种安全性依赖的是:
别人还没有猜到我们在做什么。
一旦规则泄露,系统可能立刻失去保护能力。
这种思路通常被称为依赖隐蔽性或“把算法藏起来”。它并不是说隐藏实现细节毫无价值,而是说:隐藏算法本身不能成为密码系统最后一道、甚至唯一一道防线。
现代密码学采用的是几乎相反的思路。
AES 的规范是公开的,SHA-256 的每一步运算是公开的,RSA 的数学原理也是公开的。攻击者完全可以知道:
- 数据被怎样分块;
- 每一轮进行了什么运算;
- 公钥中包含哪些参数;
- 哈希函数怎样处理输入;
- 整个协议按照什么顺序执行。
真正需要保密的,是其中的密钥,或者更具体一点,是私钥。
可以把它类比成现实中的门锁。
一把可靠的锁,不应该要求:
“小偷绝对不能知道这把锁的内部结构。”
更合理的目标是:
“即使小偷已经拆过同型号的锁、拥有设计图、知道每个零件怎样运动,只要没有正确钥匙,仍然很难把这扇门打开。”
这正是“算法公开、密钥保密”的意义。
公开还有另一个非常重要的好处:任何人都可以尝试攻击它。
一种新的密码算法刚被提出时,即使设计者认为它非常牢固,也不能因此证明它真的安全。密码算法里可能隐藏着:
- 某种设计者没有发现的数学规律;
- 某些输入下异常薄弱的结构;
- 比暴力破解更高效的攻击方法;
- 参数选择上的缺陷;
- 与实际实现结合后才会暴露的问题。
如果算法长期公开,世界各地的密码学家、安全研究人员,甚至攻击者都可以研究它,寻找这些弱点。
有些算法经不起这样的检验,很快就会退出历史舞台;另一些算法在多年甚至几十年的公开分析中,始终没有出现足以破坏其实际安全性的攻击,于是才逐渐获得信任。
因此,“公开”本身并不会自动让一个算法变安全,但它让算法能够经历真正严格的检验。
这也是为什么:
“没人知道怎么破解”与“因为没人知道算法,所以没人破解”是两件完全不同的事情。
前者意味着:攻击者知道规则,仍然找不到现实可行的办法。
这也是为什么现代密码学普遍不鼓励个人随意设计所谓的“私有加密算法”。一个自己设计的算法也许看起来足够复杂,但“我暂时看不出漏洞”并不能证明它安全。成熟算法最宝贵的地方之一,恰恰是它们已经接受了大量公开分析。
所以,标题中的问题也可以得出答案了:
优秀的密码算法不需要害怕公开。真正需要保密的是密钥,而不是算法本身。
当然,这并不代表把密钥、随机数种子、服务器私钥或实现中的敏感信息公开也没有关系。算法公开与秘密材料公开完全是两回事。
“算法公开”说的是设计与规则可以公开审查,并不是要求把运行中的秘密材料一起公开。像私钥、会话密钥、密码、随机种子等,依然必须受到严格保护。
对称加密:一把钥匙负责加密和解密
现代加密首先可以分成两大类,即:对称加密与非对称加密。
**对称加密(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,因为“直接拿数学公式处理原文”本身并不安全。
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”更像是一整类数学工具,而不是某一个单独用途的算法。
这里最容易混淆的是: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 = HTTPSHTTPS 实际上是多种密码学机制共同工作的结果,而并不是某一种“加密算法”的简单应用。
因此,“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,那么单纯的哈希校验同样会失效。
因此,更严格的软件分发体系通常还会加入数字签名。
如果下载文件和对应的 SHA-256 都来自同一个已经被篡改的页面,那么“哈希一致”也无法证明文件来自真正的发布者。哈希负责检查内容是否一致,可信来源和数字签名负责补上“这个摘要是谁提供的”这一层。
数字签名:证明“是谁发的”和“有没有被篡改”
数字签名并不是“把整个文件用私钥加密”。
这种“私钥加密、公钥解密”的说法在一些入门材料里很常见,但对于现代数字签名来说并不准确。
更准确的理解是:签名算法利用私钥生成一段只有对应公钥才能验证的签名数据。它和“把整份消息反向加密一遍”不是一回事。
真实系统通常会先对消息计算哈希摘要,再使用私钥对这个摘要相关的数据执行签名算法。可以粗略理解为:
原始消息 ↓计算哈希 ↓消息摘要 ↓私钥 + 签名算法 ↓数字签名验证方收到消息和签名后,会自己重新计算消息摘要,再使用公钥按照对应算法验证签名是否成立。
这样做有两个好处:一是无需对一份可能很大的文件直接执行昂贵的公钥运算;二是只要消息发生细微变化,摘要就会改变,原来的签名也就无法继续通过验证。
它真正解决的是:
如何证明一份数据确实与某个私钥持有者有关,并且签名之后没有被修改?
假设 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 更进一步,把时间成本、内存成本和并行度都设计成可调参数。
对正常用户来说,登录时多花几十毫秒或几百毫秒通常没有明显感觉;但对于一个想每秒尝试数百万、数十亿个密码的攻击者来说,这种额外成本会被成倍放大。
所以密码哈希的目标并不是“越快越好”,而是:
对正常登录来说足够快,对大规模猜密码来说尽可能昂贵。
这也是为什么密码数据库泄露后,攻击者通常不是“解密哈希”,而是不断尝试候选密码,再比较计算结果。专门的密码哈希算法要做的,就是让每一次这样的尝试都更昂贵。
Salt:为什么同一个密码不应该得到同一个结果
密码哈希中还有一个非常重要的概念:
Salt(盐)。
假设两个用户使用完全相同的密码:
password123
如果系统只是简单计算:
Hash(password123)
那么两个人数据库中的结果也会完全一样。
攻击者一眼就能发现:
这两个账户使用了相同密码。
为了避免这种情况,系统会为不同用户生成随机 Salt:
用户 A:password123 + randomA
用户 B:password123 + randomB然后再计算密码哈希。
这样,即使两个人使用同一个密码,最终保存的结果也不同。
Salt 本身通常不需要保密。
它主要用于抵御预计算攻击,并避免相同密码产生完全相同的数据库记录。
注意:Base64 看起来像密文,但它不是加密
另一个非常常见的误区是:
一串看不懂的字符,就是加密后的结果。
Base64 就特别容易让人产生这种错觉。
例如:
hello
经过 Base64 编码后会变成:
aGVsbG8=原本熟悉的单词突然变成了一串看起来毫无规律的字符,甚至还带有 =,确实很像某种“密文”。
但 Base64 并不是加密,而是一种编码方式(Encoding)。
它最大的区别在于:
Base64 不需要密钥,也没有任何秘密规则。
任何知道“这是一段 Base64”的人,都可以按照公开规则直接把它还原。
例如:
aGVsbG8=↓ Base64 Decodehello这里不存在“破解”过程,也不存在“猜密钥”。
做的仅仅是:
按照 Base64 的规则把数据重新解释回来。
Base64 为什么会变成这些奇怪的字符
要理解这一点,首先要知道:计算机中的数据本质上都是二进制。
例如字符 hello 在计算机中,会先对应成一串字节。
以 ASCII / UTF-8 表示时:
h → 01101000e → 01100101l → 01101100l → 01101100o → 01101111把它们连接起来,就是一长串 0 和 1。
Base64 接下来并不会对这些数据进行什么“保密运算”,而只是改变它们的分组方式。
普通字节以 8 bit 为一组,而 Base64 会把数据重新切成 6 bit 一组。
原因是:
2^6 = 646 bit 一共正好可以表示 64 种不同的值。
Base64 因此准备了一张包含 64 个字符的公开字符表:
A-Za-z0-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;
- 压缩后的数据;
- 二进制数据的文本表示。
真正判断一段数据是否经过加密,关键不在于它“看起来有多乱”,而在于:
恢复原始信息时,是否需要某个只有特定通信方掌握的秘密。
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所以在真正的攻击中,攻击者往往不会选择“把所有密钥试一遍”这种最笨的方法。
相比之下,他们更愿意尝试:
- 弱密码;
- 密钥泄露;
- 配置错误;
- 程序漏洞;
- 随机数缺陷;
- 社会工程;
- 协议实现漏洞。
这也引出了密码学中一个非常重要的现实:
一个安全的算法,不等于一个安全的系统。
看到“使用 AES-256”时,最多只能说明某个环节采用了 AES-256。密钥怎么生成、怎么保存、Nonce 是否正确使用、协议有没有认证、程序有没有漏洞,这些同样会决定整个系统最终是否安全。
算法没有被破解,系统仍然可能被攻破
假设一个程序使用 AES-256。
算法本身没有问题。
但开发者却直接把密钥写在源码中:
KEY = "MySecretKey..."攻击者根本不需要破解 AES。
只需要把程序反编译,找到密钥即可。
又比如:
- 私钥文件权限配置错误;
- Token 被写进日志;
- 密钥通过不安全的渠道发送;
- 随机数发生器可以被预测;
- Nonce 被错误重复使用。
这些问题都可能让一个“使用顶级密码算法”的系统彻底失效。
因此,现实中的密码安全不仅关心数学算法,还必须同时关心:
算法安全吗?密钥长度足够吗?密钥如何生成?密钥保存在哪里?密钥如何交换?随机数可靠吗?实现有没有漏洞?参数有没有重复?密码学是整个安全系统的一部分,而不是安全系统的全部。
为什么密码学如此依赖随机数
现代密码系统中经常会出现:
KeyIVNonceSaltChallengeToken其中很多参数都需要依赖随机数生成。
但密码学中的随机数不能随便使用普通的伪随机函数。
例如一些编程环境中的普通:
Math.random()
通常并不是为密码学设计的。
密码系统需要的是:
CSPRNG(Cryptographically Secure Pseudorandom Number Generator)
也就是:
密码学安全伪随机数生成器。
它的重要要求之一是:
即使攻击者观察到之前的一部分输出,也不应该能够轻易预测后续结果。
Linux 中常见的安全随机来源包括:
/dev/urandom
而现代编程语言和操作系统通常也会提供专门的密码学安全随机 API。
如果随机数可以被预测,那么很多看起来“密钥长度很长”的安全系统仍然可能迅速崩溃。
IV 和 Nonce 到底是什么
这是实际接触实际密码算法时,经常会遇到的概念。
它们通常不是密码,也不是秘密密钥。
它们是加密过程中额外使用的参数。
例如使用:
AES-GCM
时,通常需要提供一个 Nonce。
一个非常重要的原则是:
对同一个密钥来说,某些算法要求 Nonce 绝对不能重复。
如果错误地重复使用 Nonce,可能会严重破坏加密系统的安全性。
“Nonce 不能重复”与“Nonce 必须随机”不是完全相同的要求。 不同算法对 Nonce 的生成方式有不同规定;有些方案最关键的是在同一密钥下保持唯一,而不是单纯追求“看起来随机”。实际使用时应遵循具体算法或协议的规范。
因此,实际的现代加密远不是:
AES(password, file)
这么简单。
更加完整的结构可能是:
KeyNoncePlaintextAAD ↓AES-GCM ↓CiphertextAuthentication Tag其中 Authentication Tag 用来帮助检测数据是否被修改。
对 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 |
| 同时保证保密和防篡改 | AEAD | AES-GCM、ChaCha20-Poly1305 |
| 安全保存用户密码 | 密码哈希 / KDF | Argon2、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 是否正确使用?
- 协议和程序实现有没有漏洞?
这正是现代密码学从“一个加密算法”发展为“一整套安全系统”的地方。
如果以后再遇到一个陌生的密码学名词,可以先问一句:它到底是在解决“保密、完整性、身份、密钥交换,还是密码存储”中的哪一个问题? 往往比先记住算法名字更容易建立整体理解。
结语
如果只是刚刚进入密码学领域,最值得先记住的其实不是某个算法的数学细节,而是以下几组区别:
加密 ≠ 哈希加密 ≠ 编码公钥 ≠ 私钥密码 ≠ 密钥哈希 ≠ 密码哈希加密 ≠ 数字签名这些概念一旦分清,之后再去理解 HTTPS、SSH、软件签名和密码存储,就会容易很多。
同时也应该记住现代密码学非常重要的一条原则:
真正可靠的安全系统,并不是依靠“别人不知道算法”,而是依靠公开、经过长期检验的算法,以及正确生成、交换、保存和使用的密钥。
所以,“最好的加密就是公开?”并不是说“公开以后自然就会安全”,而是说:足够成熟的密码算法,应当能够在所有规则都公开、所有攻击者都可以研究它的情况下,仍然把安全性牢牢建立在密钥和经过验证的数学难题之上。
从这个角度看,“加密”并不是把信息变成乱码这么简单。
它更像是一场围绕机密性、完整性、身份与信任展开的系统工程。
而我们每天使用的 HTTPS、SSH、数字证书和软件签名,正是这套系统在现实世界中的具体体现。
参考资料
本文主要参考 NIST、IETF、RFC Editor、OWASP 等机构发布的密码学标准、协议规范与安全实践资料:
-
NIST — FIPS 197: Advanced Encryption Standard (AES)
AES 的官方标准,介绍 AES-128、AES-192、AES-256,以及 128 bit 分组、密钥扩展和各轮变换等基本结构。 NIST:FIPS 197 — Advanced Encryption Standard (AES) -
NIST — FIPS 180-4: Secure Hash Standard (SHS)
介绍 SHA-224、SHA-256、SHA-384、SHA-512 等安全哈希算法,以及消息摘要在完整性检测中的用途。 NIST:FIPS 180-4 — Secure Hash Standard -
NIST — FIPS 186-5: Digital Signature Standard (DSS)
NIST 数字签名标准,介绍数字签名在数据完整性、签名者身份认证等方面的作用,并规定相关签名算法。 NIST:FIPS 186-5 — Digital Signature Standard -
RFC 8017 — PKCS #1: RSA Cryptography Specifications Version 2.2
RSA 密码体系的重要规范,介绍 RSA 的基础运算、密钥表示、RSA-OAEP 加密以及 RSA-PSS 数字签名等机制。 RFC Editor:RFC 8017 — RSA Cryptography Specifications -
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 -
RFC 8032 — Edwards-Curve Digital Signature Algorithm (EdDSA)
介绍 EdDSA 数字签名体系,以及 Ed25519、Ed448 等具体方案的密钥生成、签名和验证过程。 RFC Editor:RFC 8032 — EdDSA / Ed25519 -
RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3
TLS 1.3 协议规范,介绍现代 HTTPS 中的握手、证书认证、密钥协商、会话密钥以及认证加密等机制。 RFC Editor:RFC 8446 — TLS 1.3 -
NIST SP 800-38D — Galois/Counter Mode (GCM) and GMAC
介绍 GCM 认证加密模式,是理解 AES-GCM 如何同时提供数据机密性和完整性认证的重要资料。 NIST:SP 800-38D — GCM and GMAC -
RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols
介绍 ChaCha20 流密码、Poly1305 消息认证器以及两者组合形成的 ChaCha20-Poly1305 AEAD 方案。 RFC Editor:RFC 8439 — ChaCha20 and Poly1305 -
OWASP Cheat Sheet Series — Password Storage Cheat Sheet
介绍为什么密码应当使用单向密码哈希而非可逆加密保存,并讨论 Salt、Argon2id、scrypt、bcrypt、PBKDF2 以及计算成本设计。 OWASP:Password Storage Cheat Sheet -
RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
Argon2 的规范资料,介绍其密码哈希设计、内存硬化特性以及 Argon2d、Argon2i、Argon2id 等模式。 RFC Editor:RFC 9106 — Argon2 -
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 -
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 -
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 -
OWASP Cheat Sheet Series — Cryptographic Storage Cheat Sheet
从工程实践角度介绍数据加密、密钥管理、算法选择以及密码不应使用可逆加密保存等问题,可作为本文“算法安全不等于系统安全”部分的补充资料。 OWASP:Cryptographic Storage Cheat Sheet
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!








