Go 中的 AES-CBC-Ciphertext Stealing(密文窃取)加解密:aescts 包原理与在 Kerberos 加密链中的实战应用
网络安全【免费下载链接】sliverAdversary Emulation Framework项目地址https://gitcode.com/gh_mirrors/sl/sliver点击查看免费下载本篇文章以 Sliver 仓库中 vendor 化的gopkg.in/jcmturner/aescts.v1包README.md为核心系统讲解在 Go 中如何使用 AES CBC Ciphertext StealingCTS密文窃取模式对数据进行加密与解密包括包的安装与导入、Encrypt/Decrypt两个核心函数的完整语义、三种明文长度场景下的算法分支以及该包在 Kerberos 5RFC 3962 / RFC 8009AES 加密类型中的真实调用链。读完本文你将理解 CTS 模式为何能免填充、其交换末两块并截断的具体实现并能在自己的 Go 项目中正确使用该包。一、CBC 密文窃取Ciphertext Stealing是什么传统 CBC 模式要求明文长度必须是 16 字节AES 块大小的整数倍否则需要使用 PKCS#7 等填充方案补齐最后一个块。而Ciphertext StealingCTS是一种不用填充即可处理任意长度明文的 CBC 变体它让最后两个块互相借用密文——倒数第二个密文块中截取所需字节补全最后一个块加密完成后交换末两块的位置最后再按原始明文长度截断输出。该包的源码注释明确引用了这一设计见 aescts.go为保持一致性密文窃取总是作用于待加密数据的最后两个块。如果数据长度恰好是块大小的整数倍则等价于普通 CBC 模式再将最后两个密文块交换一次加密输出中倒数第二块即最后一个明文块的加密结果被用作下一次加密的初始向量。这意味着 CTS 模式具有两个关键特性输出密文与明文等长无填充膨胀以及可以自然地传递 next IV 用于连续分片的加密——这两点对 Kerberos 这类需要把加密数据嵌入协议消息的场景至关重要。二、获取与导入 aescts 包原文档给出了最直接的获取方式go get gopkg.in/jcmturner/aescts.v1在 Go Modules 模式下更稳妥的做法是指定版本当前仓库 vendor 的版本为 v1.0.1见 implant/go-mod 第 42 行go get gopkg.in/jcmturner/aescts.v1v1.0.1导入方式同样继承自原文档import gopkg.in/jcmturner/aescts.v1该包只暴露两个公开函数aescts.Encrypt与aescts.Decrypt依赖 Go 标准库crypto/aes与crypto/cipher无任何第三方依赖。三、核心 APIEncrypt 与 DecryptEncrypt加密并返回 next IVfunc Encrypt(key, iv, plaintext []byte) ([]byte, []byte, error)参数与返回值对应 aescts.go 的Encrypt实现项说明keyAES 密钥必须是 16 / 24 / 32 字节对应 AES-128/192/256否则aes.NewCipher返回错误iv初始向量必须是 16 字节函数内部不会修改传入的 ivplaintext明文任意长度含非块整数倍允许为空调用则由内部zeroPad报错返回值 1next IV取自加密输出倒数第二块供后续分片加密接力使用返回值 2密文长度与明文严格一致CTS 无填充膨胀返回值 3错误密钥非法、明文为空等场景返回非 nil从源码看Encrypt内部首先用aes.NewCipher(key)创建分组密码再用cipher.NewCBCEncrypter(block, iv)创建 CBC 加密器随后按明文长度分三种分支处理详见下一节。值得注意的是函数内部对plaintext做了拷贝因此不会破坏调用方的切片。Decrypt逆向恢复明文func Decrypt(key, iv, ciphertext []byte) ([]byte, error)Decrypt的实现aescts.go有几点值得注意先拷贝密文源码注释明确指出即使以值传递切片其底层数组仍可能被修改因此内部先copy一份避免污染调用方数据长度下限校验密文长度小于一个块16 字节直接返回错误Ciphertext is not large enough...整数倍场景只需把末两块交换回来再用标准 CBC 解密单个块恰好 16 字节时无法交换直接按 CBC 解密非整数倍场景需要从倒数第二块密文尾部借出npb BlockSize - len(ct) % BlockSize个字节拼接到最后一块上再依次解密最后按len(ct)截断输出。返回值只有明文与错误长度与原始密文等长。四、三种明文长度场景的算法分支Encrypt依据l : len(plaintext)与aes.BlockSize16的关系走三条路径理解这三条路径是掌握该包行为的关键场景 1明文 ≤ 16 字节单块m, _ zeroPad(m, aes.BlockSize) mode.CryptBlocks(m, m) return m, m, nil先用零填充到 16 字节做一次 CBC 加密返回的密文是 16 字节注意此时输出比明文长这是唯一填充的情形next IV 即该密文本身。场景 2明文长度是块大小的整数倍mode.CryptBlocks(m, m) iv m[len(m)-aes.BlockSize:] rb, _ : swapLastTwoBlocks(m, aes.BlockSize) return iv, rb, nil整段按普通 CBC 加密然后交换最后两个密文块返回的 next IV 是交换前的最后一块密文。场景 3明文长度不是块大小的整数倍真正的 CTS先zeroPad到块大小整数倍再调用tailBlocks拆出前半段 rb / 倒数第二块 pb / 最后一块 lb三部分前半段按 CBC 连续加密并滚动更新 iv随后分别加密 pb 与 lb最后将密文按lb pb顺序拼接交换末两块并按原始明文长度ct[:l]截断。这正是维基百科密文窃取条目所描述的 CBC-CTS 标准做法源码注释中直接引用了该参考见 aescts.go。五、辅助函数tailBlocks、swapLastTwoBlocks、zeroPad包内还有三个未导出的辅助函数共同支撑上述流程aescts.gotailBlocks(b []byte, c int) ([]byte, []byte, []byte, error)把切片拆成剩余部分 / 倒数第二块 / 最后一块三块。最后一块的长度按len(b) % BlockSize计算余数为 0 时整块为 16 字节当数据不超过两个块时返回的rb为 nil。数据不大于一个块时返回错误bytes slice is not larger than one block so cannot tail。swapLastTwoBlocks(b []byte, c int) ([]byte, error)在tailBlocks基础上重排为rb lb pb实现末两块交换。加密整数倍场景与解密交换回来两侧都在使用它。zeroPad(b []byte, m int) ([]byte, error)用零字节把数据补到m的整数倍。块大小参数非法m 0或数据为空时返回错误。注意它与 PKCS#7 填充不同——只是补零因为 CTS 最终会按原始长度截断补零部分不进入最终输出。六、仓库中的实战应用Kerberos AES 加密链这个看似只有两个函数的包在仓库中的实际价值体现在它是Kerberos 5 AES 加密类型的内核。aescts 在 implant/go-mod 中被列为间接依赖indirect由gopkg.in/jcmturner/gokrb5.v7 v7.5.0第 45 行引入并被打包进 Sliver 的 implant 端 vendor 目录。RFC 3962AES-CTS-HMAC-SHA1-96rfc3962/encryption.go 中的EncryptData与DecryptData直接转调 aesctsivz : make([]byte, e.GetCypherBlockBitLength()/8) return aescts.Encrypt(key, ivz, data)这里 IV 是全零的 16 字节ivz。对应的加密类型在 aes128-cts-hmac-sha1-96.go 的文件头注释中列出aes128-cts-hmac-sha1-96etype 17128 位密钥与aes256-cts-hmac-sha1-96etype 18256 位密钥加密/解密函数均为 AES in CBC-CTS mode校验和类型为hmac-sha1-96-aes128/25696 位。EncryptMessage在加密前还会拼接随机 confounder 并通过DeriveKey按 usage 派生会话密钥。RFC 8009AES-CTS-HMAC-SHA2 系列rfc8009/encryption.go 的结构类似区别在于AES256_CTS_HMAC_SHA384_192类型需要 32 字节密钥且完整性哈希按HMAC(Ki, IV | C)方式计算先验密再解密见第 108-118 行注释。两条路径最终都汇聚到同一个aescts.Encrypt/aescts.Decrypt。上层链路implant 的 SSH Kerberos 认证在 Sliver 的 implant 端implant/sliver/shell/ssh/ssh.go 的getClient函数在同时提供krb5conf、keytab、realm时走 Kerberos 认证分支调用github.com/yiya1989/sshkrb5/krb5forssh.NewKrb5InitiatorClient该库导入gokrb5.v7的client、spnego、keytab、messages等包见 sshkrb5 client.go。由此构成的依赖链是implant SSH 命令Kerberos 认证 └─ github.com/yiya1989/sshkrb5SPNEGO/KRB5 客户端 └─ gopkg.in/jcmturner/gokrb5.v7RFC 3962 / RFC 8009 加密类型 └─ gopkg.in/jcmturner/aescts.v1AES-CBC-CTS 加解密内核也就是说implant 通过 SSH 使用 Kerberos 票据认证时票据与认证器消息中所有 AES 加密数据的机密性保障最终都落在这个约 186 行的 aescts 包上。七、使用注意事项结合源码行为使用该包时建议注意以下几点密钥与 IV 长度必须正确key必须是 16/24/32 字节iv必须是 16 字节否则要么aes.NewCipher返回错误要么在加密过程中产生错误结果Kerberos 场景中 IV 通常取全零向量。务必保存并使用返回的 next IVEncrypt第一个返回值是供后续加密使用的 next IV加密输出倒数第二块。如果要分片加密长消息并保持 CBC 链式依赖必须在下一片加密时传入它而不是沿用原始 iv。密文长度与明文等长除单块场景输出被补齐到 16 字节外CTS 输出长度恒等于明文长度解密方必须知晓这个语义避免把补零字节误当数据。Decrypt 会做长度校验密文不足一个块会直接返回错误整数倍与非整数倍两条解密路径不能互换必须与加密侧保持一致。在 Sliver 中它是间接依赖日常开发一般无需直接调用 aescts它通过 gokrb5 服务于 implant 的 Kerberos/SPNEGO 认证链路如需独立使用参考第二节的go get与import方式即可。结语aescts.v1虽然是一个只有两个公开函数的小型加密包但它以标准库crypto/aes与crypto/cipher为基础完整实现了 CBC 密文窃取模式的免填充加密并在本仓库中承担着 Kerberos 5 AES 加密类型RFC 3962 / RFC 8009的底层加解密职责。理解其单块 / 整数倍 / 非整数倍三分支逻辑与 next IV 语义既能帮助你在自己的 Go 项目中正确引入 CTS 加密也能更深入地读懂 Sliver implant 中 SSH Kerberos 认证这条完整依赖链。赞分享网络安全【免费下载链接】sliverAdversary Emulation Framework项目地址https://gitcode.com/gh_mirrors/sl/sliver点击查看免费下载相关推荐SeaTunnel FieldEncrypt 加密 Transform 插件实战指南AES-GCM/AES-CBC 字段加解密原理与配置SeaTunnel FieldEncrypt 加密 Transform 插件实战指南AES GCM/AES CBC 字段加解密原理与配置 SeaTunnel数据集成ETL大数据批处理流处理变更数据捕获AhabAssistantLimbusCompanyLimbus Company自动化助手终极指南AhabAssistantLimbusCompanyLimbus Company自动化助手终极指南 AhabAssistantLimbusCompany简称桌面应用RPA计算机视觉MicroPython cryptolib 模块实战AES 加密解密ECB / CBC / CTR完整指南MicroPython cryptolib 模块实战AES 加密解密ECB / CBC / CTR完整指南 本指南以 MicroPython 仓库中 do嵌入式语言运行时编程语言解释器编译器物联网系统编程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考