You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中cipher.AEAD.Seal()加密大文件内存占用翻倍问题咨询

Go中ChaCha20-Poly1305加密大文件内存翻倍的问题解析

我使用Go语言的ChaCha20-Poly1305实现加密数据,但在加密大文件时,内存占用超出预期。我理解Go的AEAD密码实现需要将全部数据载入内存以生成哈希,但内存占用达到了明文的两倍。

以下是复现问题的代码(实际场景中key和nonce不应为空):

package main

import (
  "os"
  "fmt"
  "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 MiB\n", tag, m.Alloc / 1024 / 1024, m.TotalAlloc / 1024 / 1024)
}

根据crypto/cipher/gcm.go的源码注释(同样适用于ChaCha20-Poly1305):

// To reuse plaintext's storage for the encrypted output, use plaintext[:0]
// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
Seal(dst, nonce, plaintext, additionalData []byte) []byte

我尝试复用内存,但调用Seal()后,加密4GiB数据始终占用8GiB内存,运行结果如下:

[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

问题根源

问题出在明文slice的容量不足以容纳加密后的数据:
ChaCha20-Poly1305加密后的数据会在明文基础上追加16字节的Poly1305校验标签。你的代码中plaintext是用make([]byte, 4*1024*1024*1024)创建的,这意味着它的长度和容量都是4GiB,没有多余空间存储标签。

当调用Seal(plaintext[:0], nonce, plaintext, nil)时,内部的grow函数会检查目标slice的容量是否足够容纳加密后的数据(明文长度+16字节标签):

  • len(dst)是0,需要的总长度是4GiB + 16
  • 而dst的容量是4GiB,不足以容纳额外的16字节标签
  • 因此grow函数会分配一块新的内存来存储加密后的数据,导致内存占用翻倍(原4GiB明文 + 新分配的4GiB+16字节密文)

解决方案

创建明文slice时,预留出足够容纳Poly1305标签的容量,修改plaintext的创建代码:

plaintext := make([]byte, 4*1024*1024*1024, 4*1024*1024*1024 + chacha20poly1305.TagSize)

这样plaintext[:0]的容量是4GiB + 16,满足加密后数据的存储需求,grow函数会直接在原有内存上扩展slice长度,不会分配新内存。修改后的内存占用会保持在4GiB左右(仅额外占用标签的16字节)。

补充说明

即使你不需要保留明文,也需要注意:如果明文slice的容量刚好等于明文长度,加密时必然会触发新内存分配。只有当目标slice的容量大于等于明文长度+标签长度时,才能实现真正的原地内存复用。

内容的提问来源于stack exchange,提问作者chrixm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 06:18:12