// internal/age/primitives.go
func aeadEncrypt(key, plaintext []byte) ([]byte, error) {
aead, err := chacha20poly1305.New(key)
if err != nil {
return nil, err
}
// The nonce is fixed because this function is only used in places where the
// spec guarantees each key is only used once (by deriving it from values
// that include fresh randomness), allowing us to save the overhead.
// For the code that encrypts the actual payload, look at the
// filippo.io/age/internal/stream package.
nonce := make([]byte, chacha20poly1305.NonceSize)
return aead.Seal(nil, nonce, plaintext, nil), nil
}
age:一个现代化的加密工具
age 是 golang crypto 库的维护者之一的 FiloSottile 写的一个现代化加密工具,目前正处在 Beta 阶段。
age 的是 Actual Good Encryption 的缩写,有点 PGP(Pretty Good Privacy) 的意思,说起来作者本身的想法就是想替代GnuPG。
age 的现代化体现在密码学算法的选择上,age 使用 x25519 作为非对称加密算法,x25519 是 Curve25519 被设计用于密钥交换的曲线,是目前公认的最快的椭圆密码曲线,它还有个用做签名的 ed25519 的兄弟,二者的公私钥可以互相转换。
对称加密算法选择上,age 使用 chacha20poly1305 ,现在已经是 TLS1.3 推荐对称加密算法,这个密码套件由两个算法构成:ChaCha20,一种流式密码,提供并行处理能力;以及用作认证加密(AEAD)的Poly1305。
age 的密钥也就是 x25519 的密钥,密钥的格式使用比特币bech32方式存储,bech32 相比 base58 编码提供更小的空间占用和更快的校验方式。
在命令行下只需要运行
age-keygen
即可生成一个新公私钥对:在 x25519 下为了加密通常需要使用 ECDH,使用己方私钥和对方公钥计算出共享密钥,然后使用共享密钥密码进行加密。不过如果己方长期使用的私钥泄露,那么所有的历史消息都是有可能被破解的。为了保证前向安全,加密密钥,在 age 中称之为 fileKey,需要是临时生成的,只用做一次性加密。为了共享这个 filekey,可以使用 ECDH 共享密钥加密 filekey,那么接收方也能计算出共享密钥来得到真正加密密钥。加密 filekey 的这个操作称之为 Wrap,所以加密 fileKey 的加密密钥也称之为 wrappingKey。
临时公钥会放入加密内容中,这样接收方也能使用自己的私钥计算出真正的 wrappingKey。为了保证 wrappingKey 的随机性,这里的共享密钥不是 wrappingKey,需要做一次 HKDF 后得到。
经过上述一系列的操作我们得到 wrappingKey,之后就可以对 fileKey 进行加密。aeadEncrypt 是加密 filekey 的方法,其中 nonce 选择固定的全零值,这里由于只是加密 fileKey,作者说为了不要过度设计,如果 nonce 是随机的,那么还需要另外途径放在加密内容内。加密后的 fileKey 我们称之为 wrappedKey。
为了保证消息完整性,还需要填充消息验证码。age 设计了一个类型 HTTP 的协议格式,先 header 后 body ,MAC 就放在 header 中。
header 第一行为版本信息,现在固定为
age-encryption.org/v1
;接下来是临时公钥信息,使用
->
开头字符串来标志,然后紧接着一个空格加上 Type 和 Args,对于 x25519 方式加密而言,Type 是X25519
,Args 是不带填充的 base64 编码的公钥信息,除了 x25519 ,age 还支持 RSA,scrypt,ed25519(间接转换为x25519)等加密方式。接下来是 wrappedKey ,也是进行 base64 进行编码,如果过长会进行换行。
header 的最后是 footer,由
---
开头,至此是所有计算 HMAC 的内容,计算 MAC 后放入后面。如下所示:计算 MAC 是通过 HMAC-With-SHA256 进行,不对 body 进行 MAC 是因为我们使用 AEAD 加密内容,不需要额外的操作。
body 是存放加密内容的地方。这里计算方式是 chacha20poly1305 的过程,为了得到最终的加密密钥,这里生成了同样 16 字节的 nonce,与 fileKey 进行 HKDF 混合后得到。
body 先写入 nonce 后,后续使用流式方式加密并写入。
这个就是 age x25519 的加密方式的所有内容,解密最重要的计算 filekey 的过程,这个上述有说过,这里不再赘述。(TODO:或许以后会写)
命令行工具进行加解密也十分简单:
age 加密也支持读取 stdin 数据:
也可以制定 -a 参数加密为 base64 格式数据:
如果要输出到 stdout 需要指定
-
总之 age 是个非常简单而且现代化的加密工具,但很遗憾的是,age只提供加解密,并不提供签名功能,加密流程也没有提供签名,只能可以在确认发送方身份时使用。
备注
Why HKDF
HKDF 遵循“先提取后扩展”的模式,其中KDF逻辑上由两个模块组成。第一阶段采用输入密钥材料并从中“提取”固定长度的伪随机密钥K。第二阶段将密钥K“扩展”为多个附加的伪随机密钥(KDF的输出)。在许多应用中,输入密钥材料不一定均匀分布,攻击者可能对其有部分了解(例如,由密钥交换协议计算的Diffie-Hellman值),甚至对其有部分控制(如在一些熵收集应用中)。因此,“提取”阶段的目标是将输入密钥材料的可能分散的熵“集中”成一个短的、但加密性强的伪随机密钥。在某些应用中,输入可能已经是一个很好的伪随机密钥;在这些情况下,不需要“提取”阶段,“扩展”部分可以单独使用。第二阶段将伪随机密钥“扩展”到所需的长度;输出密钥的数量和长度取决于需要密钥的特定加密算法。RFC
Why not RSA
RSA 仍被广泛使用,但是有个已知安全问题,PKCS#1.5签名密钥也是OAEP的加密密钥。另外RSA的安全强度也不如椭圆曲线,3072位RSA密钥的加密强度才等同于256位的ECC密钥的水平。