2025-05-31  2025-06-01    662 字  2 分钟

今天在在通过管道将上一个命令的输出传递给下一个命令时, 发现最终得到的数据怎么都不对, 经过一整天的debug, 终于发现问题出在了第二个命令的缓冲区上

一开始脑子抽了, 觉得是openwrt平台的zstd有问题, 甚至用go重新写了一个zstd工具

背景

  1. tar打包目录为 A.tar
  2. zstd压缩 A.tarA.tar.zst
  3. kscrypt加密 A.tar.zstA.tar.zst.enc (kscrypt是自己编写的加密程序)

完整命令如下

1# 打包压缩加密
2tar -cf - -C "$SYNC_PATH" . | zstd -c | kscrypt -key "$PASSWORD" -out "A.tar.zst.enc"
3# 测试
4kscrypt -d -key "$PASSWORD" -in "A.tar.zst.enc" | zstd -d -c | tar -tf - > /dev/null
5# 解密解压解包
6kscrypt -d -key "$PASSWORD" -in "A.tar.zst.enc" | zstd -d -c | tar -xvf -

经过多次测试, 发现在OpenWrt系统下 kscrypt 加密后的数据怎么都对不上, 解密总是失败

问题代码

虽然配置了缓冲区大小, 但是每次input.Read得到的数据大小并不固定, 导致每次加密的数据块大小不一致. 而在解密时没有读取到正确的数据块大小, 导致解密失败

 1buffer := make([]byte, bufferSize)
 2for {
 3  n, readErr := input.Read(buffer)
 4  if n > 0 {
 5    encrypted := aesGCM.Seal(nil, nonce, buffer[:n], nil) // 加密
 6    _, writeErr := output.Write(encrypted)                // 写入加密数据
 7    if writeErr != nil {
 8      return fmt.Errorf("写入加密数据失败: %v", writeErr)
 9    }
10  }
11  if errors.Is(readErr, io.EOF) {
12    break
13  }
14  if readErr != nil {
15    return fmt.Errorf("读取输入数据失败: %v", readErr)
16  }
17}

改正代码

增加了缓冲区, 当缓冲区数据达到指定的块大小后, 再执行加密或解密, 保证数据块大小一致

 1tmp := make([]byte, maxEncryptionBlockSize)
 2buffer := bytes.NewBuffer(nil)
 3for {
 4  n, readErr := input.Read(tmp)
 5  if n > 0 {
 6    _, err = buffer.Write(tmp[:n])
 7    if err != nil {
 8      return err
 9    }
10    for buffer.Len() >= maxEncryptionBlockSize {
11      incrementNonce(nonce)
12      data := buffer.Next(maxEncryptionBlockSize)
13      encrypted := aesGCM.Seal(nil, nonce, data, nil)
14      if _, err := output.Write(encrypted); err != nil {
15        return err
16      }
17    }
18  }
19  if readErr == io.EOF {
20    if buffer.Len() > 0 {
21      incrementNonce(nonce)
22      data := buffer.Bytes()
23      encrypted := aesGCM.Seal(nil, nonce, data, nil)
24      if _, err := output.Write(encrypted); err != nil {
25        return err
26      }
27    }
28    break
29  }
30  if readErr != nil {
31    return readErr
32  }
33}

除另有声明外本博客文章均采用 知识共享 (Creative Commons) 署名 4.0 国际许可协议 进行许可转载请注明原作者与文章出处