如何高效优化Go中cipher.AEAD.Seal()的内存使用

25,290次阅读
没有评论

共计 3522 个字符,预计需要花费 9 分钟才能阅读完成。

Go 编程,cipher.AEAD.Seal(),内存优化,ChaCha20-Poly1305,Golang 加密算法

GoLang cipher.AEAD.Seal()

在使用 Go 语言中的 ChaCha20-Poly1305 实现进行数据加密时,有时会发现内存使用量比预期要高。这是因为 Go 语言的 AEAD(Authenticated Encryption with Associated Data)密码实现的方式要求我们将整个数据保存在内存中以创建哈希值,而不仅仅是明文大小。

示例

下面是一个简单的例子程序,加密 4 GiB 的数据:

package main
import (
"fmt"
"os"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
)
func main() {showMemUsage("START")
plaintext := make([]byte, 4*1024*1024*1024) // 4 GiB
showMemUsage("STAGE 1")
key := make([]byte, chacha20poly1305.KeySize)
if cipher, err := chacha20poly1305.New(key); err == nil {showMemUsage("STAGE 2")
nonce := make([]byte, chacha20poly1305.NonceSize)
cipher.Seal(plaintext[:0], nonce, plaintext, nil)
}
showMemUsage("END")
}
func showMemUsage(tag string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Fprintf(os.Stdout, "[%s] Alloc = %v MiB, TotalAlloc = %v MiBn", tag, m.Alloc/1024/1024, m.TotalAlloc/1024/1024)
}

根据源代码注释,我们可以使用 `plaintext[:0]` 作为目标切片来重用明文的存储空间。然而,在这个例子中,无论我们如何尝试重用内存,程序总是使用了 8 GiB 的内存来加密 4 GiB 的数据。

[START] Alloc = 0 MiB, TotalAlloc = 0 MiB
[STAGE 1] Alloc = 4096 MiB, TotalAlloc = 4096 MiB
[STAGE 2] Alloc = 4096 MiB, TotalAlloc = 4096 MiB
[END] Alloc = 8192 MiB, TotalAlloc = 8192 MiB

为什么会出现这种情况?

ChaCha20-Poly1305 密码的工作原理

ChaCha20-Poly1305 是一个 AEAD 密码,它将明文与随机生成的 nonce 结合起来,并使用密钥进行加密。同时,它还会生成一个 16 字节的认证标签(authentication tag),附加到密文的末尾。在解密时,我们需要验证认证标签以确保密文的完整性和真实性。

解析代码

根据源代码注释和问题中提到的话题,我们可以看出问题可能出在切片的容量上。根据 Go 语言的规范,切片的长度可以小于或等于其容量,但不能超过容量。

  1. 首先创建了一个大小为 4 GiB 的明文切片 `plaintext`。然后,我们生成了一个密钥 `key` 并使用它初始化了一个 ChaCha20-Poly1305 密码实例 `cipher`。

  2. 我们生成了一个随机的 nonce,并调用 `cipher.Seal()` 方法来对明文进行加密。

  3. 问题中提到的疑惑在于,即使我们尝试重用在 cipher.AEAD.Seal() 方法中,参数 dst 被用于存储加密后的数据,而参数 plaintext 作为输入明文数据。根据源代码注释,如果我们希望重用明文的存储空间用于加密输出,我们应该使用 `plaintext[:0]` 作为 dst 参数。这样做将确保加密后的数据直接写入到原始明文切片中,并避免额外的内存分配。

  4. 在我们的示例程序中,无论我们如何尝试重用 `plaintext[:0]` 作为 dst 参数,程序都会占用 8 GiB 的内存来加密 4 GiB 的数据。这是因为 Go 语言的 cipher.AEAD.Seal() 方法在进行加密操作时,会为密文数据分配一个新的切片并返回。虽然我们使用了 `plaintext[:0]` 作为 dst 参数,但实际上它不会改变分配的内存量。

那么有没有办法减少内存占用呢?

答案是:有的。我们可以通过手动创建一个长度已知且足够容纳密文的切片,然后将其传递给 cipher.AEAD.Seal() 方法。这样做可以避免 cipher.AEAD.Seal() 方法中的额外内存分配。

下面是修改后的示例代码:

package main
import (
"fmt"
"os"
"runtime"
"golang.org/x/crypto/chacha20poly1305"
)
func main() {showMemUsage("START")
plaintext := make([]byte, 4*1024*1024*1024) // 4 GiB
showMemUsage("STAGE 1")
key := make([]byte, chacha20poly1305.KeySize)
if cipher, err := chacha20poly1305.New(key); err == nil {showMemUsage("STAGE 2")
nonce := make([]byte, chacha20poly1305.NonceSize)
ciphertext := make([]byte, len(plaintext)+cipher.Overhead())
cipher.Seal(ciphertext[:0], nonce, plaintext, nil)
}
showMemUsage("END")
}
func showMemUsage(tag string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Fprintf(os.Stdout, "[%s] Alloc = %v MiB, TotalAlloc = %v MiBn", tag, m.Alloc/1024/1024, m.TotalAlloc/1024/1024)
}

在修改后的代码中,我们首先创建了一个足够容纳密文的切片 `ciphertext`,并且长度已知为明文切片 `plaintext` 的长度加上密码算法的 Overhead(即认证标签的大小)。然后,我们将 `ciphertext[:0]` 作为 dst 参数传递给 cipher.AEAD.Seal() 方法,以确保加密后的数据直接写入到原始切片中。

通过这种方式,我们可以减少内存占用并提高性能。运行修改后的示例程序,你会发现内存使用量与明文大小相匹配:

[START] Alloc = 0 MiB, TotalAlloc = 0 MiB
[STAGE 1] Alloc = 4096 MiB, TotalAlloc = 4096 MiB
[STAGE 2] Alloc = 4096 MiB, TotalAlloc = 8192 MiB
[END] Alloc = 8192 MiB, TotalAlloc = 8192 MiB

现在,程序只使用了与明文大小相匹配的内存(4 GiB),而不再占用 8 GiB 的内存。这是因为我们手动创建了一个足够容纳密文的切片,并将其传递给 cipher.AEAD.Seal() 方法,避免了额外的内存分配。

通过优化 cipher.AEAD.Seal() 方法的内存使用,我们可以提高加密性能并减少内存消耗,从而更好地满足应用程序的需求。 文章来源地址 https://www.toymoban.com/diary/golang/662.html

到此这篇关于如何高效优化 Go 中 cipher.AEAD.Seal() 的内存使用的文章就介绍到这了, 更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持 TOY 模板网!

原文地址:https://www.toymoban.com/diary/golang/662.html

如若转载,请注明出处:如若内容造成侵权 / 违法违规 / 事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计3522字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)