上传文件
  1. base64hub
  2. 学习 Base64
  3. 编码与加密
安全

Base64 不是加密:编码与加密的区别

Base64 只是改变了字节的书写方式。你用 Base64 编码的任何内容,看到它的人都能用一条命令解码,不需要任何密钥。

三种不同的操作

Base64 经常出现在人们以为受到保护的地方,所以有必要弄清楚它到底做了什么。它是一种编码:从字节到文本的固定、公开的映射(见 Base64 的工作原理)。这个映射没有密钥,没有秘密,相同的输入总是产生相同的输出。

编码(Base64、hex)加密(AES-GCM)哈希(SHA-256、Argon2)
用途把字节表示为文本保证数据机密生成数据指纹或校验数据
可还原是,任何人都能是,需要密钥否
需要密钥否是否(HMAC 需要)
相同输入得到相同输出总是否,使用随机 nonce 时是,除非加盐
输出大小约为输入的 4/3输入加上 nonce 和认证标签固定长度

解码不费吹灰之力:

echo ZGVwbG95Omh1bnRlcjI= | base64 -d
# deploy:hunter2

这个字符串在人眼看来似乎难以理解。但对脚本、日志扫描器或攻击者来说,它只是多了一个步骤的明文。

一个实用的判断方法:如果某个值出现在截图里会出问题,那么经过 Base64 编码后它照样会出问题。

Base64 数据在实践中如何泄露

模式总是一样的:某个值为了传输被 Base64 编码,有人误以为它受到了保护,然后它就出现在了能被人读到的地方。下面是这类错误的常见形态。

HTTP Basic 认证请求头

Basic 认证(RFC 7617)在每个请求中以 Base64(user:password) 的形式发送用户名和密码:

Authorization: Basic ZGVwbG95Omh1bnRlcjI=

唯一的保护是连接上的 TLS。请求头一旦离开这条隧道,密码实际上就是明文。常见的情况有:记录请求头的反向代理或应用框架、从浏览器开发者工具导出并附在工单里的 HAR 文件、粘贴到聊天里的 curl -v 输出,以及会捕获完整请求的错误追踪工具。Basic 认证解码器可以演示读取它有多容易。

Kubernetes Secret

Kubernetes Secret 以 Base64 编码的形式存储它的值:

apiVersion: v1
kind: Secret
metadata:
  name: db
data:
  password: czNjcjN0

czNjcjN0 就是 s3cr3t。这种编码是为了让值能在 YAML 中容纳二进制数据。Kubernetes 文档明确指出,Secret 默认以未加密的形式存储在 etcd 中,任何有权通过 API get 该 Secret 的人都能读取它。常见的泄露途径有:带有真实 data: 值的清单被提交到 Git 仓库、kubectl get secret -o yaml 的输出被粘贴到工单中,以及 etcd 或集群状态的备份未经加密存储。Kubernetes Secret 解码器能一次性解码 Secret 的所有值,也可以用它快速检查一份清单实际暴露了什么。

容器镜像仓库的凭据也是同样的模式。Docker config.json 或 .dockerconfigjson Secret 中的 auth 字段就是 Base64(user:password),dockerconfigjson 解码器可以读取它。

JWT 载荷

签名后的 JWT 看起来像一个密封的令牌,但它的前两部分只是 Base64URL 编码的 JSON:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImVtYWlsIjoiYW5hQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIn0.<signature>

载荷解码后是 {"sub":"42","email":"ana@example.com","role":"admin"}。签名保护令牌不被篡改,但对机密性毫无帮助。每个能看到这个令牌的浏览器扩展、代理、日志文件和统计脚本都能读到里面的内容。个人数据、不希望暴露的内部 ID,以及任何机密信息,都不应该放在 JWT 载荷中。如果一定要通过令牌传递,请使用加密的 JWT(JWE)。更多细节见 JWT 中的 Base64。

代码和配置中“混淆”过的密钥

把 API 密钥以 Base64 形式存放在前端 JavaScript、移动应用安装包或配置文件中,是一个经典错误。通常的理由是这样密钥就不是“明文”了。它确实能让密钥躲过对字面值的简单搜索,但仅此而已。任何发给客户端的东西,客户端都能读到。

在攻击中,同样的思路也会反过来用。PowerShell 的 -EncodedCommand 接受 UTF-16LE 文本的 Base64,常被用来让恶意命令一眼看上去难以读懂。防御方几秒钟就能解开,例如用 PowerShell 编码命令解码器。

应该改用什么

根据任务选择工具。在下面每种场景中,Base64 仍然有用武之地:作为包裹字节的文本外壳,而这些字节由别的东西来保护。

保证数据机密:认证加密

通过维护良好的库使用 AES-256-GCM 或 ChaCha20-Poly1305。两者都是认证加密,解密时能发现篡改。有两条规则比算法本身更重要:同一个密钥绝不能重复使用 nonce;密钥不要和数据放在一起(放在 KMS、密钥管理服务,或者攻击者无法同时控制的环境中)。

import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";

const key = randomBytes(32);   // in real code, load this from a KMS or secret store
const iv = randomBytes(12);    // 96-bit nonce, unique per message

const cipher = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([cipher.update("deploy:hunter2", "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();

// Base64 carries the encrypted bytes as text: nonce + tag + ciphertext
const token = Buffer.concat([iv, tag, ct]).toString("base64");

const raw = Buffer.from(token, "base64");
const d = createDecipheriv("aes-256-gcm", key, raw.subarray(0, 12));
d.setAuthTag(raw.subarray(12, 28));
console.log(Buffer.concat([d.update(raw.subarray(28)), d.final()]).toString("utf8"));
// deploy:hunter2

输出是一个 56 个字符的 Base64 字符串,由于 nonce 是随机的,每次运行结果都不同。没有密钥,它毫无用处。如果不想写代码就想试试,AES 加密和 AES 解密工具可以直接在浏览器中运行。

存储密码:慢速、加盐的哈希

密码不应该被加密,也不应该被编码。应当存储由专为密码设计的函数生成的哈希:Argon2id、scrypt 或 bcrypt,并为每个密码使用唯一的随机盐。对密码直接做 SHA-256 计算太快,扛不住暴力破解。Python 标准库自带 scrypt:

import hashlib, hmac, os

def hash_password(pw: str) -> bytes:
    salt = os.urandom(16)
    return salt + hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)

def check_password(pw: str, stored: bytes) -> bool:
    salt, digest = stored[:16], stored[16:]
    candidate = hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)
    return hmac.compare_digest(candidate, digest)

如果需要把结果放进文本列或 JSON 中,就用 Base64 编码它。

证明完整性:签名与 HMAC

当目标是“没人改过它”而不是“没人能读它”时,请使用带共享密钥的 HMAC 或数字签名。结果是字节,通常以 Base64 或 hex 形式传输。HMAC 生成器会同时显示这两种形式的输出。

传输中和存储中的凭据

所有承载 Basic 认证或令牌的连接都要使用 TLS;优先使用短期令牌而不是静态密码;从日志中清除 Authorization 请求头;为 Kubernetes Secret 启用静态加密;不要把真实的密钥值放进 Git。

常见问题

Base64 加密安全吗?

Base64 没有任何安全属性。它没有密钥,解码只需要字符串本身。对于已经受到保护的数据(例如密文或签名),用它作为传输格式是安全的。

用 Base64 隐藏配置文件里的密码够不够?

不够。任何能读取该文件的人都能用 base64 -d 解出密码。请限制文件权限、使用密钥管理服务,或者用存放在别处的密钥加密这个值。

既然不是为了安全,Kubernetes Secret 为什么要用 Base64 编码?

这样 Secret 的值就能在 YAML 和 JSON 中容纳任意二进制数据,例如 TLS 密钥或密钥库。真正的保护来自 RBAC、etcd 的静态加密,以及谨慎处理清单文件。

没有密钥,别人能看到 JWT 载荷吗?

能。头部和载荷都是 Base64URL 编码的 JSON,任何人都能读。密钥只在验证或生成签名时才需要。可以用 JWT 解码器试试。

编码、加密和哈希有什么区别?

编码任何人都能还原;加密只有持有密钥才能还原;哈希是单向的:你可以用哈希校验一个值,但无法把哈希还原成原来的值。

继续阅读