Skip to content

密码学应用:先决定要保护什么,再选算法 ​

保存用户密码、给数据库字段加密、校验 webhook、建立 HTTPS 连接,虽然都用到了密码学,目标却不同。把 SHA-256 换成 AES 不能自动让密码存储更安全,给数据做 Base64 也没有保密效果。

本文围绕四个问题展开:以后需不需要还原原文,谁能验证内容,谁能生成可信内容,以及密钥泄漏后如何恢复。代码使用标准密码学实现,演示密码校验、认证加密、MAC、签名和证书验证,不设计新算法或新密码协议。

编码、哈希、加密:区别在它们承诺什么 ​

操作是否需要秘密能否按设计还原输入提供的主要能力不能直接提供什么
Base64 编码不需要可以直接解码把字节表示成适合传输的文本保密、身份、抗篡改
SHA-256 等密码学哈希不需要没有解密步骤,但可猜输入再比较摘要、完整性比较的组件单独证明来源、抵抗低熵密码猜测
密码 KDF,例如 Argon2id盐公开,通常不依赖秘密不解密;用候选密码重新计算提高离线猜密码的时间和内存成本让弱密码不可猜、阻止在线撞库
AEAD 加密需要密钥持密钥方可解密保密,并验证密文与附加数据完整性自动判断业务授权或防止合法密文重放
HMAC需要共享秘密不用于还原共享密钥圈内的消息完整性与认证区分所有共享密钥持有者
数字签名私钥签名,公钥验证不用于还原验证者不持有签名能力加密消息、自动证明现实身份

Base64 的实际输出很直观:

go
base64.StdEncoding.EncodeToString([]byte("hello"))
text
base64: aGVsbG8=

知道编码规则的任何人都能还原它。哈希虽然没有对应的“解密”,攻击者仍能对常见密码逐个计算并比对,所以“不可逆”不等于“难猜”。哈希、KDF、AEAD 与签名是组件,只有放到合适的威胁模型里才有意义。RFC 4648、Go SHA-256、RFC 5116 AEAD

密码存储:让每次离线猜测都付出代价 ​

登录系统只需要判断输入密码是否匹配,不需要取回原密码,因此通常存密码 KDF 的结果,而不是加密后的原密码。数据库一旦泄漏,攻击者可以离线尝试,不受登录接口限流约束。MD5、SHA-1、SHA-256 直接算一次都太快;给快速哈希简单加盐,仍然没有获得现代密码 KDF 的工作因子和内存成本。

截至 2026-10-13 查证,OWASP 密码存储指南 将 Argon2id 的一组最低配置列为 19 MiB、2 次迭代、并行度 1;bcrypt 主要用于遗留系统,工作因子至少 10,并注意 72 字节输入限制。最低值不是服务器的最终调优结果,也不是未来永远不变的建议。

选择主要调节项实际好处部署时的代价与边界
Argon2id内存、迭代次数、并行度同时增加猜测的计算和内存负担登录并发会放大内存占用,必须按预算压测
bcryptcost,增长会增加计算工作生态成熟,便于维护历史哈希有输入长度边界,不具备同样的可调内存参数
scryptN、r、pArgon2id 不可用时的内存困难选择参数约束、峰值内存与库支持需要验证
PBKDF2-HMACPRF 与迭代次数某些合规环境中的既定方案需要当前推荐参数及合规实现,不能只看算法名称

Argon2 的 RFC 给出了更高资源预算的推荐配置,例如 64 MiB、3 次迭代、4 条 lane 的受限内存选项;它与 OWASP 的最低配置不是同一层次的承诺。应该先确定可承受的单次时间、并发量和内存,再在目标机器上测量,而不是从两张表里挑一个更好看的数字。RFC 9106 §4、§7.4

盐、参数、摘要一起保存,秘密另有用途 ​

盐是每条密码记录独立生成的随机值,可以公开保存。它使相同密码在不同记录上得到不同结果,也使预计算结果不能直接跨用户复用。记录应包含算法、版本、参数、盐和结果,便于日后升级。额外的 pepper 是服务端秘密,应与密码数据库分离管理;若它泄漏,可能需要重置密码,不能把它当作替代 KDF 的捷径。

正在绘制图示…

可切换适合窗口或原始尺寸
查看图源代码
flowchart LR
    P["用户输入密码"] --> K["KDF:固定且受限的参数"]
    S["记录中的随机盐"] --> K
    K --> C["常数时间比较"]
    H["保存的 KDF 结果"] --> C
    C --> A{"匹配?"}
    A -- "否" --> N["统一失败响应与限流"]
    A -- "是" --> U["必要时用新参数重算并更新"]
放大图示

原始尺寸下可横向或纵向滚动;按 Escape 关闭。

正在绘制图示…

查看图源代码
flowchart LR
    P["用户输入密码"] --> K["KDF:固定且受限的参数"]
    S["记录中的随机盐"] --> K
    K --> C["常数时间比较"]
    H["保存的 KDF 结果"] --> C
    C --> A{"匹配?"}
    A -- "否" --> N["统一失败响应与限流"]
    A -- "是" --> U["必要时用新参数重算并更新"]

本例固定 golang.org/x/crypto v0.58.0,用 argon2.IDKey 验证机制。参数的内存单位是 KiB,19*1024 才是 19 MiB;返回值长度这里设为 32 字节。它返回原始派生结果,不会自动替你生成盐或保存完整的密码记录格式。固定版本 argon2 实现

go
argon2.IDKey(password, salt, 2, 19*1024, 1, 32)

程序使用随机 16 字节盐,分别检验正确密码、错误密码、相同密码换盐,以及 bcrypt 校验。实际输出:

text
argon-good=true argon-wrong=false different-salts=true bcrypt-good=true
bcrypt-73-bytes-rejected: true

最后一行来自向 Go bcrypt 传入 73 个 ASCII 字节。这个库返回 ErrPasswordTooLong;不要在调用前悄悄截断密码,也不要把“72 字节”误写成“72 个汉字”。采用预哈希兼容长密码涉及额外协议与编码风险,不能随手拼接一层 SHA 当修复。固定版本 bcrypt 源码

生产验证器还要限制从密码记录解析出的参数范围,防止异常记录要求分配巨大内存;为登录并发做容量预算并设置限流,避免昂贵 KDF 变成拒绝服务放大器。不存在的账号也应避免明显泄漏与已有账号不同的验证路径。本文没有实现密码记录解析器、参数迁移、账号枚举防护或并发容量测试;没有测出适合你的生产参数。

加密数据:默认把完整性一起考虑 ​

对称加密由共享密钥完成,适合大量数据。非对称密码学把公钥和私钥分开,常用于签名、密钥协商或封装密钥;不要把“公钥算法”当作直接加密任意长文件的通用 API。常见系统用对称数据密钥加密内容,再用 KMS 管理的主密钥保护数据密钥,这叫信封加密。NIST SP 800-38D、AWS KMS 信封加密说明

仅加密而不验证完整性,可能让被修改的数据继续进入业务。AEAD 把加密和认证组合在一起:输入是密钥、nonce、明文和可选附加认证数据 AAD;输出通常包含密文和认证标签。AAD 会参与验证,但不会被加密,适合绑定租户 ID、记录 ID、版本等不需保密的上下文。RFC 5116 §2

nonce 重复,不是“小概率多解出一条”那么简单 ​

GCM 要求同一密钥下按其规则避免重复 nonce;重用可能泄漏明文关系并破坏认证能力。nonce 通常不需要保密,但它的唯一性或碰撞概率约束属于安全条件,不能把固定的全零 nonce 放在循环里。

Go 1.24 加入 cipher.NewGCMWithRandomNonce。它要求底层 cipher 来自 AES,内部生成随机 96 位 nonce,并把 nonce 放在密文前面。调用方传入的 nonce 参数是 nil;NonceSize() 返回 0,Overhead() 返回 28。官方限制同一密钥不能加密超过 2^32 条消息,用以约束随机碰撞风险;实际系统还可能需要更低的使用上限和其他数据量限制。Go 1.27.2 API

go
sealed := a.Seal(nil, nil, msg, aad)
plain, err := a.Open(nil, nil, sealed, aad)

本例生成一次性随机 AES-256 密钥,加密后分别篡改密文和改动 AAD 中的租户,得到:

text
decrypt=true tamper-rejected=true aad-rejected=true nonce-size=0 overhead=28

标签验证失败时必须丢弃结果,不能把失败后的缓冲区内容当明文继续处理。AAD 绑定能阻止把一条密文直接挪到不同上下文,但不会自动阻止在同一个上下文重放旧版本;需要单调版本、数据库状态或业务幂等规则进一步控制。

选择 AES-GCM 还是 ChaCha20-Poly1305,应看平台实现、硬件能力、依赖与合规要求,不凭算法名字推断吞吐。无论哪一个,随机源、密钥轮换、nonce 规则和错误处理都必须匹配具体 API。本文没有做性能基准,也没有测任何 nonce 碰撞概率。

HMAC 和签名:区别在验证者是否也能伪造 ​

HMAC 把共享秘密与消息放进标准构造,产生认证码。发送方与接收方都知道同一个秘密,所以双方都能生成有效认证码。数字签名由私钥生成,公钥验证;把公钥发给十个验证服务,不会因此让它们都获得签名能力。RFC 2104、RFC 8032

决策维度HMAC-SHA-256Ed25519 等数字签名
验证方拿到什么共享秘密公钥
验证方是否能产生有效消息能仅有公钥时不能
常见用途双方共享密钥的 webhook 或请求认证多方验证制品、令牌或声明
密钥扩散后的影响任意一处泄漏都可能允许伪造公钥泄漏不等于私钥泄漏
仍然需要的协议设计内容规范化、版本、受众、重放窗口同样需要,并要可信分发公钥

不要用 SHA256(secret || message) 自创 MAC;使用 crypto/hmac。比较认证码使用 hmac.Equal,避免把普通字节比较引入的时序差异作为校验路径。签名则应使用成熟库,验证完整消息和正确的上下文,不能只验证一个由输入随意替换的摘要。Go hmac、Go ed25519

本机同时对原消息和修改后的消息验证,实际输出:

text
hmac-good=true hmac-changed=false signature-good=true signature-changed=false

这些结果不提供保密,原消息仍然可以公开。签名也不会阻止原样重放;交易 ID、时间戳、用途和一次性状态必须进入协议。所谓“不可否认”还依赖私钥保管、身份绑定、审计和法律规则,不能由一个 Verify=true 单独推出。

TLS:验证证书只是建立安全通道的一部分 ​

TLS 在传输中提供加密和完整性,并通过证书等机制认证通信方。TLS 1.3 的普通证书握手包含协商、密钥协商、证书与签名验证、Finished 验证,再用派生出的对称密钥保护应用数据。长期证书私钥与每次连接的流量密钥不是同一件东西。RFC 8446 §2

正在绘制图示…

可切换适合窗口或原始尺寸
查看图源代码
sequenceDiagram
    participant C as 客户端
    participant S as 服务端
    C->>S: ClientHello 和密钥协商参数
    S-->>C: ServerHello、可选 CertificateRequest、证书、握手签名、Finished
    C->>C: 验证信任链、服务身份与握手完整性
    opt mTLS 请求客户端证书
        C->>S: 客户端证书与握手签名
        S->>S: 验证客户端链与身份映射
    end
    C->>S: Finished
    C<<->>S: 受保护的应用数据
放大图示

原始尺寸下可横向或纵向滚动;按 Escape 关闭。

正在绘制图示…

查看图源代码
sequenceDiagram
    participant C as 客户端
    participant S as 服务端
    C->>S: ClientHello 和密钥协商参数
    S-->>C: ServerHello、可选 CertificateRequest、证书、握手签名、Finished
    C->>C: 验证信任链、服务身份与握手完整性
    opt mTLS 请求客户端证书
        C->>S: 客户端证书与握手签名
        S->>S: 验证客户端链与身份映射
    end
    C->>S: Finished
    C<<->>S: 受保护的应用数据

这是省略了部分消息与恢复流程的概念图,不是抓包实测。TLS 保护的是两个 TLS 端点之间的链路;若流量在网关终止,网关到后端是否再次加密与认证要单独设计。它也不能修复服务端 SQL 注入或阻止持有合法会话的人越权。

证书链可信,主机名仍然可能不对 ​

证书包含公钥、主体名称、有效期、用途等信息,并由发行者签名。通常服务端发送叶子证书和必要中间证书,客户端从这些材料构建到本地信任根的链。根证书的信任来自预先配置的信任库,而不是因为它“自己签了自己”。RFC 5280

检查解决什么问题不能替代什么
签名链到可信根公钥和证书由受信任体系确认目标主机名是否匹配
SAN 中的服务身份证书是否适用于所访问服务用户有没有业务权限
有效期是否在接受时间内密钥是否已经泄漏
Key Usage / EKU是否允许相应密码学或服务用途完整的应用身份映射
吊销或短期凭据策略如何停止接受不应继续使用的凭据自动恢复已泄漏的数据

现代 TLS 服务身份验证应按 SAN 中的 DNS/IP 等标识处理,不依赖 Common Name 回退。RFC 9525 Go 的 ServerName 参与服务端身份验证及适用的 SNI;若只验证证书签名而不检查服务名,另一台同 CA 签发证书的服务也可能被误认。

实验生成一次性的测试 CA 与只允许 service.example、ServerAuth 的叶子证书,然后用 x509.Certificate.Verify 改变验证条件:

text
chain-host=true wrong-host=false wrong-usage=false missing-root=false

四次使用同一张叶子证书。链和主机名都正确才通过;换服务名、要求客户端用途或不给可信根都失败。代码显式给空根池测试缺根,不依赖机器上安装了哪些系统根。这里验证的是证书链与选项,未执行 TLS 握手;Go 的 Certificate.Verify 文档还明确说明它不做吊销检查。固定版本 Verify

mTLS:客户端也用证书证明身份 ​

普通 HTTPS 通常是客户端验证服务端,客户端用户身份再由会话或令牌处理。mTLS 额外让服务端请求并验证客户端证书。Go 服务端通常需要配置 ClientAuth: tls.RequireAndVerifyClientCert 和正确的 ClientCAs;仅设置 RequireAnyClientCert 只要求提供证书,不代表建立了可信链验证。客户端则仍需 RootCAs 和正确的服务身份验证。Go tls.Config

验证客户端证书后,还要把受信证书里的身份映射为工作负载或用户,并检查它能调用哪些动作。一个来自可信 CA 的客户端证书不应自动获得全部管理员权限。证书签发、短期更新、根轮换、吊销、会话恢复与连接池中的旧连接,都影响权限何时真正失效。

不要为了绕过证书错误把 InsecureSkipVerify 留在生产配置中。该选项会跳过标准的证书链与主机名验证,除非你另行正确实现验证,否则会失去防中间人的重要边界。优先选择当前维护的 TLS 默认实现,按兼容需求明确最低协议版本;在所有客户端支持时使用 TLS 1.3,保留 TLS 1.2 应有经过验证的兼容理由。TLS 推荐 BCP 195 / RFC 9325

最后回到密钥:算法正确,生命周期仍然可能出错 ​

密码学系统需要知道密钥怎么生成、谁能使用、如何标识版本、旧数据如何解密,以及失窃后怎么停止使用。随机密钥用 crypto/rand,不要用 math/rand、时间戳或人类可记密码直接充当 AES/HMAC 密钥。测试中的临时随机密钥只存在进程内,没有真实账户和生产密钥。

密钥轮换应区分“新数据改用新密钥”和“旧密钥立即销毁”。旧密文若尚未重加密或重封装,删除旧密钥会造成不可恢复的数据丢失;反过来,只更新配置但无限期接受旧签名,也不算完成撤销。生产系统可用 KMS 或受控密钥服务降低密钥分发范围,但仍要限定调用权限并审计解密行为。

本文能复现算法接口的几个边界,不能替你完成密码系统设计。上线前最值得补的验证是目标硬件的 KDF 并发成本、密钥轮换故障恢复、TLS 双向握手与身份映射,以及真实数据备份能否在授权条件下恢复。

依据与验证边界 ​

  • 密码存储:OWASP Password Storage,查证日 2026-10-13;RFC 9106;golang.org/x/crypto v0.58.0 的 argon2 与 bcrypt 固定源码。参数建议与版本均有时效,本文没有宣称配置永远足够。
  • 加密与认证:RFC 5116、NIST SP 800-38D、RFC 2104、RFC 8032;Go 1.27.2 的 cipher、hmac、ed25519、rand 文档。
  • 传输和证书:RFC 8446、5280、9525、9325;Go 1.27.2 的 tls.Config 与 x509.Certificate.Verify。
  • 本次 2026-10-13 在官方 Go 1.27.2、Linux/amd64 运行 go run .、go test -race -count=1 ./...、go vet ./...,全部通过。实际输出只报告布尔边界,不以单次时间当性能结论。
  • 未运行真实 TLS/mTLS 握手、证书签发平台、吊销服务、KMS、生产密码迁移或抗拒绝服务压测。测试 CA 不加入操作系统信任库,密钥不写入磁盘。

完整复现代码 ​

程序固定测试时间生成证书,每次生成新的临时密钥和盐。字符串 public demonstration password 是公开测试夹具,不是账号密码。固定参数用于复现;生产还需安全的记录格式、受限参数解析、限流与密钥生命周期管理。

go.mod ​

go.mod
module example.com/security04

go 1.27.2

require golang.org/x/crypto v0.58.0

require golang.org/x/sys v0.49.0 // indirect

go.sum ​

text
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=

main.go ​

代码块 · 139 行 · 点击收起 / 展开
go
package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/ed25519"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"fmt"
	"golang.org/x/crypto/argon2"
	"golang.org/x/crypto/bcrypt"
	"math/big"
	"time"
)

func random(n int) []byte {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return b
}
func derive(password, salt []byte) []byte { return argon2.IDKey(password, salt, 2, 19*1024, 1, 32) }
func passwordChecks() (bool, bool, bool, bool, error) {
	password := []byte("public demonstration password")
	salt := random(16)
	stored := derive(password, salt)
	good := subtle.ConstantTimeCompare(stored, derive(password, salt)) == 1
	bad := subtle.ConstantTimeCompare(stored, derive([]byte("wrong"), salt)) == 1
	different := !bytes.Equal(stored, derive(password, random(16)))
	hash, err := bcrypt.GenerateFromPassword(password, 10)
	if err != nil {
		return false, false, false, false, err
	}
	bgood := bcrypt.CompareHashAndPassword(hash, password) == nil
	return good, bad, different, bgood, nil
}
func aeadChecks() (bool, bool, bool, int, int, error) {
	block, err := aes.NewCipher(random(32))
	if err != nil {
		return false, false, false, 0, 0, err
	}
	a, err := cipher.NewGCMWithRandomNonce(block)
	if err != nil {
		return false, false, false, 0, 0, err
	}
	msg := []byte("public fixture")
	aad := []byte("tenant=a;record=7;version=1")
	sealed := a.Seal(nil, nil, msg, aad)
	plain, err := a.Open(nil, nil, sealed, aad)
	good := err == nil && bytes.Equal(msg, plain)
	tampered := bytes.Clone(sealed)
	tampered[len(tampered)-1] ^= 1
	_, bad := a.Open(nil, nil, tampered, aad)
	_, wrongAAD := a.Open(nil, nil, sealed, []byte("tenant=b;record=7;version=1"))
	return good, bad != nil, wrongAAD != nil, a.NonceSize(), a.Overhead(), nil
}
func macAndSignature() (bool, bool, bool, bool) {
	key := random(32)
	msg := []byte("public invoice fixture")
	mac := func(b []byte) []byte { h := hmac.New(sha256.New, key); h.Write(b); return h.Sum(nil) }
	tag := mac(msg)
	mgood := hmac.Equal(tag, mac(msg))
	mbad := hmac.Equal(tag, mac([]byte("changed")))
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	must(err)
	sig := ed25519.Sign(priv, msg)
	return mgood, mbad, ed25519.Verify(pub, msg, sig), ed25519.Verify(pub, []byte("changed"), sig)
}

type certificates struct {
	ca, leaf *x509.Certificate
	roots    *x509.CertPool
}

func makeCertificates() (certificates, error) {
	var out certificates
	pub, caKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return out, err
	}
	now := time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)
	ca := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "PUBLIC TEST CA"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign}
	der, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, caKey)
	if err != nil {
		return out, err
	}
	ca, err = x509.ParseCertificate(der)
	if err != nil {
		return out, err
	}
	leafPub, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return out, err
	}
	leaf := &x509.Certificate{SerialNumber: big.NewInt(2), DNSNames: []string{"service.example"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}
	der, err = x509.CreateCertificate(rand.Reader, leaf, ca, leafPub, caKey)
	if err != nil {
		return out, err
	}
	leaf, err = x509.ParseCertificate(der)
	if err != nil {
		return out, err
	}
	roots := x509.NewCertPool()
	roots.AddCert(ca)
	return certificates{ca, leaf, roots}, nil
}
func verifyCert(c certificates, name string, usage x509.ExtKeyUsage, roots *x509.CertPool) bool {
	_, err := c.leaf.Verify(x509.VerifyOptions{Roots: roots, DNSName: name, CurrentTime: time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC), KeyUsages: []x509.ExtKeyUsage{usage}})
	return err == nil
}
func main() {
	fmt.Println("base64:", base64.StdEncoding.EncodeToString([]byte("hello")))
	good, bad, salts, bgood, err := passwordChecks()
	must(err)
	fmt.Printf("argon-good=%v argon-wrong=%v different-salts=%v bcrypt-good=%v\n", good, bad, salts, bgood)
	_, err = bcrypt.GenerateFromPassword(bytes.Repeat([]byte("x"), 73), 10)
	fmt.Println("bcrypt-73-bytes-rejected:", err == bcrypt.ErrPasswordTooLong)
	g, t, a, n, o, err := aeadChecks()
	must(err)
	fmt.Printf("decrypt=%v tamper-rejected=%v aad-rejected=%v nonce-size=%d overhead=%d\n", g, t, a, n, o)
	mg, mb, sg, sb := macAndSignature()
	fmt.Printf("hmac-good=%v hmac-changed=%v signature-good=%v signature-changed=%v\n", mg, mb, sg, sb)
	c, err := makeCertificates()
	must(err)
	fmt.Printf("chain-host=%v wrong-host=%v wrong-usage=%v missing-root=%v\n", verifyCert(c, "service.example", x509.ExtKeyUsageServerAuth, c.roots), verifyCert(c, "other.example", x509.ExtKeyUsageServerAuth, c.roots), verifyCert(c, "service.example", x509.ExtKeyUsageClientAuth, c.roots), verifyCert(c, "service.example", x509.ExtKeyUsageServerAuth, x509.NewCertPool()))
}
func must(err error) {
	if err != nil {
		panic(err)
	}
}

main_test.go ​

代码块 · 39 行 · 点击收起 / 展开
go
package main

import (
	"bytes"
	"crypto/x509"
	"golang.org/x/crypto/bcrypt"
	"testing"
)

func TestPassword(t *testing.T) {
	good, bad, salts, bgood, err := passwordChecks()
	if err != nil || !good || bad || !salts || !bgood {
		t.Fatal(good, bad, salts, bgood, err)
	}
	if _, err := bcrypt.GenerateFromPassword(bytes.Repeat([]byte("x"), 73), 10); err != bcrypt.ErrPasswordTooLong {
		t.Fatal(err)
	}
}
func TestAEAD(t *testing.T) {
	good, tamper, aad, n, o, err := aeadChecks()
	if err != nil || !good || !tamper || !aad || n != 0 || o != 28 {
		t.Fatal(good, tamper, aad, n, o, err)
	}
}
func TestAuthenticity(t *testing.T) {
	mg, mb, sg, sb := macAndSignature()
	if !mg || mb || !sg || sb {
		t.Fatal(mg, mb, sg, sb)
	}
}
func TestCertificate(t *testing.T) {
	c, err := makeCertificates()
	if err != nil {
		t.Fatal(err)
	}
	if !verifyCert(c, "service.example", x509.ExtKeyUsageServerAuth, c.roots) || verifyCert(c, "wrong.example", x509.ExtKeyUsageServerAuth, c.roots) || verifyCert(c, "service.example", x509.ExtKeyUsageClientAuth, c.roots) || verifyCert(c, "service.example", x509.ExtKeyUsageServerAuth, x509.NewCertPool()) {
		t.Fatal("certificate boundary")
	}
}

运行命令 ​

sh
go mod download
go run .
go test -race -count=1 ./...
go vet ./...

保留各书来源与许可 · 示例运行条件以正文为准