Google Tink 04:对称加密的安全实现

还记得 Tink 的第一个演示么?回头可以再看一下,在代码中有一行用来生成新的 KeysetHandle,如下:

kh, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())

其中,我们提到了 aead.AES256GCMKeyTemplate() 创建了一个密钥模板,该模板是定义如何生成加解密数据所使用的密钥,这类模板中包含加解密算法、密钥长度、IV 长度等值。

这篇文章主要就用来分析还有哪一些常用的 KeyTemplate。

KeyTemplate

目前的 Tink 中,用于生成 KeyTemplate 的方法,按照加密算法和加密模式的不同,可以分为以下的三大类,它们被定义在 go/aead/aead_key_templates.go 文件中:

  1. 使用 AES 算法,并采用 GCM 模式的 KeyTemplate 有:
  • AES128GCMKeyTemplate():使用 128 位密钥长度的基于 GCM 的 AES 算法;
  • AES256GCMKeyTemplate():使用 256 位密钥长度的基于 GCM 的 AES 算法;
  1. 使用 AES 算法,并采用 CTR 模式的 KeyTemplate 有:
  • AES128CTRHMACSHA256KeyTemplate():
  • AES256CTRHMACSHA256KeyTemplate():

注意采用 CTR 模式需要对密文进行认证,所以需要使用 HMACSHA256 算法,而 GCM 模式自带 GMAC 功能对消息进行认证。

  1. 使用 ChaCha20 算法,并采用 Poly1305 消息认证码算法有:
  • ChaCha20Poly1305KeyTemplate():
  • XChaCha20Poly1305KeyTemplate():

上面所有的方法,返回的都是 *tinkpb.KeyTemplate 类型,它是一个通过 ProtoBuf 协议定义的结构体类型。

KeyTemplate 结构体

go/proto/tink_go_proto/tink.pd.go 文件中,定义了 KeyTemplate 结构体,熟悉 Go 的同学都知道,这种 .pd.go 后缀的文件,都是通过 .proto 文件自动生成的。

下面是 tink.pd.go 中的 KeyTemplate 结构体和它的定义文件 tink.proto 中定义的 message KeyTemplate类型。

type KeyTemplate struct {
    // Required.
    TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
    // Optional.
    // If missing, it means the key type doesn't require a *KeyFormat proto.
    Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
    // Optional.
    // If missing, uses OutputPrefixType.TINK.
    OutputPrefixType     OutputPrefixType `protobuf:"varint,3,opt,name=output_prefix_type,json=outputPrefixType,proto3,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
    ......
}

提示:OutputPrefixType 是 int32 的别名,它定义的是密文的输出前缀。

定义 KeyTemplate 结构体类型的文件 proto/tink.proto 中,有这个结构体的定义:

message KeyTemplate {
  // Required.
  string type_url = 1;  // in format type.googleapis.com/packagename.messagename
  // Optional.
  // If missing, it means the key type doesn't require a *KeyFormat proto.
  bytes value = 2;  // contains specific serialized *KeyFormat proto
  // Optional.
  // If missing, uses OutputPrefixType.TINK.
  OutputPrefixType output_prefix_type = 3;
}

根据生成的 tink.pd.go 文件中 KeyTemplate 的内容,该结构体内的主要字段用途为:

  • TypeUrl: 代表使用的密码算法的类型,它以 URL 的形式显示;
  • Value:不同类型的密码算法结构体序列化后的字节数组,最主要就参数就是密钥长度;
  • OutputPrefixType:输出的前缀;
  1. TypeUrl

按照使用密码算法模板的不同,TypeUrl 为:

  • AES GCM:"type.googleapis.com/google.crypto.tink.AesGcmKey"
  • AES CTR:"type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey"
  • ChaCha20Poly1305:"type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key"
  • xChaCha20Poly1305:"type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key"
  1. OutputPrefixType

而 OutputPrefixType 一般则固定为 OutputPrefixType = 1,一种 int32 的数据类型。这个前缀会在每一次输出密文的时候输出到最前面,所以我们可以发现,所有的密文都会以 01 开头的原因了。

  1. Vault

Value 中才会保存着生成与 Key 相关的信息,对于 AES GCM 来说,Vault中保存的是 AesGcmKeyFormat 序列化之后的 []byte 数组,它的定义如下:

type AesGcmKeyFormat struct {
    KeySize              uint32   `protobuf:"varint,2,opt,name=key_size,json=keySize,proto3" json:"key_size,omitempty"`
    Version              uint32   `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"`
    ......
}

其中,最重要的那个参数就是 KeySize,用来描述密钥的长度。

AES GMC 实现

上面我们简单介绍了,AES GCM 按照密钥的长度分为两种,一种是 AES 128,另一种是 AES 256。而采用 AES GCM 模式自带有密文签名的功能,所以只需要实现加解密功能就可以了,签名功能就不用另外写代码了。

上篇文章中我们介绍过,Tink 的使用上 是不用我们管理密钥的,它会随机的为我们生成一个用来加密的密钥,同时也会为这个密钥分配一个 ID 值,并将其记录到 Keyset 中。解密的时候就通过这个 ID 值去 Keyset 中查找对就在的密钥去解密。

我们调用 AES GMC 实现的 AEAD 功能,仅仅需要传入明文和可选的额外数据就可以了。

密钥的准备流程

在 Tink 中,要实现 AEAD 的加解密功能,大致流程如下:

  1. 创建模板

密钥的创建是要通过预先定义好的密钥模板(KeyTemplate)的,假如想使用 AES GCM 加密算法,则是通过 AES128GCMKeyTemplate()AES256GCMKeyTemplate() 来生成密钥模板的。

  1. 创建密钥

创建密钥的过程是通过 KeyManager 来完成的,它主要的功能就是生成随机的 KeyID 和 Key,这里的 Key 就是用来加解密数据使用的。

  1. 创建 PrimitiveSet

创建的密钥会被加入到 PrimitiveSet 中,为什么有个 Set 呢?这是因为如果你有多个 Key 的话,加密是通过设置为 Primary 的 Key 来完成的,解密则会查找指定的 KeyID Key 来完成。

  1. 使用 Primitive

完成了上面的步骤之后,就可以通过调用 Primitive 的 Encrypt 或 Decrypt 来执行加解密任务了。

上面的步骤,只需要通过两行代码就可以完成,也就是下面的这两行:

kh, _ := keyset.NewHandle(aead.AES256CTRHMACSHA256KeyTemplate())
a, _ := aead.New(kh)

是不是相当的简单,现在代码中的 a 就是 Primitive,可以通过它实现加解密的操作。

密文的构成

通过对上面知识的介绍,现在我们能了解到 Tink 中一些关键的实现方法,大致就能推导出密文的组成方式。

比如,前面的介绍中使用的加密示例,用来加密字符串 "this data needs to be encrypted":

ct, err := a.Encrypt([]byte("this data needs to be encrypted"), []byte("associated data"))

运行后,输出的密文结果就是,它由 128 个十六进制数组成:

0187574a607e5a6e321cd16ea125546ef145d0f9e250a7ae4ce5ef259a715a7025cacdeb8e004c1bb98b2db96de439ed9023e8c5636b05f462dd298c597789c7

那它的构成是通过下面的内容组合而成的:

  • prefix: 1 byte,使用 Tink 就默认是 1;
  • key id: 4 byte,随机生成的 32 位数字;
  • IV: 12 byte,随机生成的 iv,GCM 中应该叫 nonce;
  • GCM tag: 16 byte,GCM 生成的认证数据,将和明文混合;
  • ciphertext: 密文的长度,它和明文拥有一样的长度,如何用 byte 统计,上面的 "this data needs to be encrypted" 长度为 31 byte。

发现最终的结果就是 64 byte(1 + 4 + 12 + 16 + 31),打印成十六进制数就是 128 个,和上面的结果一致。

一般情况下,我们不需要去刻意的观察它的长度,只需要知道使用 Tink AEAD 加密的内容,一般都会是以 01 开头,而它使用密钥 ID 就是 01 后面的 8 个十六进制数(4 字节),也就是上面密文中的 87574a60 这串数字来表示的。

AES CTR 实现

了解了 AES GCM 的实现后,其它的加密模式就比较好理解了。

虽然 AES CTR 模式的实现比较复杂一点,那是因为 CTR 模式不带有对密文进行签名的功能,所以就需要使用 HMAC 来对密文进行签名。举个 AES 256 CTR 模板的例子:

// AES256CTRHMACSHA256KeyTemplate is a KeyTemplate that generates an AES-CTR-HMAC-AEAD key with the following parameters:
//  - AES key size: 32 bytes
//  - AES CTR IV size: 16 bytes
//  - HMAC key size: 32 bytes
//  - HMAC tag size: 32 bytes
//  - HMAC hash function: SHA256
func AES256CTRHMACSHA256KeyTemplate() *tinkpb.KeyTemplate {
    return createAESCTRHMACAEADKeyTemplate(32, 16, 32, 32, commonpb.HashType_SHA256)
}

我们就可以看到 AES、CTR、HMAC256 分别代表着加密算法、加密模式、签名算法,它们一起使用,就会带来一大堆的参数了。通过简单的推理也可以知道,采用 AES256CTRHMACSHA256KeyTemplate 这个 KeyTemplate 会比其它的两种 KeyTemplate 产生更长的密文。

虽然看起来很复杂,但通过 Tink 的封装,大量底层的逻辑操作都是通过 Primitive 提供的 非常简单的 Encrypt 和 Decrypt 接口来实现的,大大简化心智负担。这就相当于现在和计算机沟通,只用通过使用高级编程语言来完成,不用来编写二进制来控制计算机是一样的道理。

ChaCha20 和 XChaCha20 Poly1305 实现

ChaCha20 和 XChaCha20 它们使用的密钥长度都是 32 byte 长,而它们唯一的区别就是使用的 NonceSize 的长度不一样,Chacha20 使用的 NonceSize 是 12 byte,而 XChaCha20 合适的 NonceSize 是 24 byte

最关键的就是它们的加密实现中,也同时采用 Poly1305 签名算法实现了签名,所以就不用像 AES CTR 那样,在加密完成之后再对密文签名,大大的简化了它的处理逻辑。

它密文的构成方式和 AES GCM 是差不多的,是 1 字节的 prefix,4 字节的 KeyID,然后剩下的就是密文。这里的密文是 nonce (nonce 你可以理解为随机字符)和密文拼接在一起形成的。

这两个算法,一般选择用在没有对 AES 做过优化的处理器平台上,比如嵌入式处理器,而 armv8 之后,就做了对 AES 的优化处理。

总结

由于 Tink 的封装的特别好,所以作为使用者来说,这些底层的内容是可以不用了解的。但对于加解密这样关键的操作来说,知道一些框架底层的原理还是大有好处的。总比搞错了技术细节导致无法对密文解密花的时间和金钱上的损失少得多。

而且通过对 Tink 的学习,学可以掌握 Google 对代码进行封装的能力,可以将非常复杂的操作都封装到低层来实现,上层只需要调用简单的几个 API 就可以了。这样的写代码的抽象水平,可以好好的领悟下。

现在,我们知道了一些非常安全的实现对称加密算法的方式,当然也可以将其用到其它的地方,不至于犯一些低级的错误,也不需要再考虑什么 CBC、ECB 等这样的加密模式,也不会忘记对密文进行签名。

这样,会对以后开发加解密相关的功能更有信心

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343