如果blockSize的大小 小于 inputFile指定文件内容大小,解密失败

如果 blockSize 的大小小于 inputFile 指定文件的内容大小,确实可能会导致解密失败。这是因为 GCM 模式在加密和解密时需要处理完整的加密块,而分块处理时可能会破坏加密块的完整性。为了解决这个问题,我们需要确保在分块处理时,每个块的大小能够正确对齐 GCM 的加密块。

问题分析

  1. GCM 模式的特点:
    • GCM 模式在加密时会附加一个认证标签(tag),通常为 16 字节。
    • 解密时,GCM 会验证这个标签,确保数据的完整性和真实性。
    • 如果加密块的大小不正确(例如,最后一个块不完整),解密时会失败。
  2. 分块处理的挑战:
    • 如果 blockSize 不是加密块大小的整数倍,可能会导致最后一个块的大小不正确。
    • 解密时,最后一个块可能无法正确验证认证标签。

解决方案

为了确保分块处理的正确性,我们需要在加密和解密时处理完整的加密块。可以通过以下方式实现:
  1. 确保 blockSize 是加密块大小的整数倍。
  2. 在加密时记录每个块的大小,并在解密时正确处理每个块。

修复后的代码

以下是修复后的代码,确保分块处理时每个块的大小正确对齐 GCM 的加密块:
go
复制
package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
	"os"
)

// encryptFileWithBlocks 使用 AES-GCM 模式按块加密文件
func encryptFileWithBlocks(inputFile, outputFile string, key []byte, blockSize int) error {
	// 打开输入文件
	file, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("打开输入文件失败: %w", err)
	}
	defer file.Close()

	// 创建输出文件
	outFile, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("创建输出文件失败: %w", err)
	}
	defer outFile.Close()

	// 创建 AES 加密块
	block, err := aes.NewCipher(key)
	if err != nil {
		return fmt.Errorf("创建加密块失败: %w", err)
	}

	// 创建 GCM 模式
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return fmt.Errorf("创建 GCM 模式失败: %w", err)
	}

	// 生成随机的 nonce
	nonce := make([]byte, aesgcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return fmt.Errorf("生成 nonce 失败: %w", err)
	}

	// 将 nonce 写入输出文件
	if _, err := outFile.Write(nonce); err != nil {
		return fmt.Errorf("写入 nonce 失败: %w", err)
	}

	// 按块读取并加密文件内容
	buffer := make([]byte, blockSize)
	for {
		n, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			return fmt.Errorf("读取输入文件内容失败: %w", err)
		}
		if n == 0 {
			break
		}

		// 加密当前块
		ciphertext := aesgcm.Seal(nil, nonce, buffer[:n], nil)
		// 将加密内容写入输出文件
		if _, err := outFile.Write(ciphertext); err != nil {
			return fmt.Errorf("写入加密内容失败: %w", err)
		}
	}

	return nil
}

// decryptFileWithBlocks 使用 AES-GCM 模式按块解密文件
func decryptFileWithBlocks(inputFile, outputFile string, key []byte, blockSize int) error {
	// 打开输入文件
	file, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("打开输入文件失败: %w", err)
	}
	defer file.Close()

	// 创建输出文件
	outFile, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("创建输出文件失败: %w", err)
	}
	defer outFile.Close()

	// 读取 nonce
	nonce := make([]byte, 12) // GCM 的 nonce 长度通常是 12 字节
	if _, err := io.ReadFull(file, nonce); err != nil {
		return fmt.Errorf("读取 nonce 失败: %w", err)
	}

	// 创建 AES 加密块
	block, err := aes.NewCipher(key)
	if err != nil {
		return fmt.Errorf("创建加密块失败: %w", err)
	}

	// 创建 GCM 模式
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return fmt.Errorf("创建 GCM 模式失败: %w", err)
	}

	// 按块读取并解密文件内容
	buffer := make([]byte, blockSize + aesgcm.Overhead()) // 包括加密块的认证标签
	for {
		n, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			return fmt.Errorf("读取加密文件内容失败: %w", err)
		}
		if n == 0 {
			break
		}

		// 解密当前块
		plaintext, err := aesgcm.Open(nil, nonce, buffer[:n], nil)
		if err != nil {
			return fmt.Errorf("解密内容失败: %w", err)
		}

		// 将解密内容写入输出文件
		if _, err := outFile.Write(plaintext); err != nil {
			return fmt.Errorf("写入解密内容失败: %w", err)
		}
	}

	return nil
}

func main() {
	// 示例密钥(AES-256 需要 32 字节密钥)
	key := []byte("0123456789abcdef0123456789abcdef")

	// 输入文件路径
	inputFile := "example.txt"
	// 加密输出文件路径
	encryptedFile := "example.encrypted"
	// 解密输出文件路径
	decryptedFile := "example.decrypted.txt"

	// 块大小(必须是加密块大小的整数倍)
	blockSize := 4096 // 4KB

	// 加密文件
	err := encryptFileWithBlocks(inputFile, encryptedFile, key, blockSize)
	if err != nil {
		fmt.Println("加密失败:", err)
		return
	}
	fmt.Println("文件加密成功:", encryptedFile)

	// 解密文件
	err = decryptFileWithBlocks(encryptedFile, decryptedFile, key, blockSize)
	if err != nil {
		fmt.Println("解密失败:", err)
		return
	}
	fmt.Println("文件解密成功:", decryptedFile)
}
 

关键修复点

  1. 调整 blockSize
    • 确保 blockSize 是加密块大小的整数倍。GCM 模式的加密块大小通常为 16 字节(AES 的块大小)加上认证标签的大小(通常是 16 字节)。
    • 在解密时,blockSize 需要加上 aesgcm.Overhead(),以确保能够正确读取完整的加密块。
  2. 正确处理最后一个块:
    • 在加密和解密时,确保每个块的大小正确对齐 GCM 的加密块。

测试方法

  1. 确保 example.txt 文件存在。
  2. 运行程序:
    bash
    复制
    go run main.go
     
  3. 检查 example.encryptedexample.decrypted.txt 文件是否正确。
通过以上修复,可以确保分块处理时每个块的大小正确对齐 GCM 的加密块,从而避免解密失败的问题。
posted @ 2025-06-06 22:07  牧之丨  阅读(29)  评论(0)    收藏  举报