共计 3522 个字符,预计需要花费 9 分钟才能阅读完成。
Go 编程,cipher.AEAD.Seal(),内存优化,ChaCha20-Poly1305,Golang 加密算法
在使用 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 语言的规范,切片的长度可以小于或等于其容量,但不能超过容量。
-
首先创建了一个大小为 4 GiB 的明文切片 `plaintext`。然后,我们生成了一个密钥 `key` 并使用它初始化了一个 ChaCha20-Poly1305 密码实例 `cipher`。
-
我们生成了一个随机的 nonce,并调用 `cipher.Seal()` 方法来对明文进行加密。
-
问题中提到的疑惑在于,即使我们尝试重用在 cipher.AEAD.Seal() 方法中,参数 dst 被用于存储加密后的数据,而参数 plaintext 作为输入明文数据。根据源代码注释,如果我们希望重用明文的存储空间用于加密输出,我们应该使用 `plaintext[:0]` 作为 dst 参数。这样做将确保加密后的数据直接写入到原始明文切片中,并避免额外的内存分配。
-
在我们的示例程序中,无论我们如何尝试重用 `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() 方法,避免了额外的内存分配。 文章来源:https://www.toymoban.com/diary/golang/662.html
通过优化 cipher.AEAD.Seal() 方法的内存使用,我们可以提高加密性能并减少内存消耗,从而更好地满足应用程序的需求。 文章来源地址 https://www.toymoban.com/diary/golang/662.html
到此这篇关于如何高效优化 Go 中 cipher.AEAD.Seal() 的内存使用的文章就介绍到这了, 更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持 TOY 模板网!
原文地址:https://www.toymoban.com/diary/golang/662.html
如若转载,请注明出处:如若内容造成侵权 / 违法违规 / 事实不符,请联系站长进行投诉反馈,一经查实,立即删除!