
Go 的 crypto/cipher.NewOFB 不会自动将 IV 写入加密文件,开发者需手动将其前置到密文开头;解密时再读取并剥离 IV,否则无法正确还原明文。
go 的 `crypto/cipher.newofb` 不会自动将 iv 写入加密文件,开发者需手动将其前置到密文开头;解密时再读取并剥离 iv,否则无法正确还原明文。
在 Go 中使用 cipher.NewOFB(或其他流式加密模式如 CFB、CTR)时,一个常见误区是认为加密器会自动将 IV 附加到输出中。实际上,cipher.Stream 接口仅负责加/解密字节流,不涉及 IV 的序列化或传输逻辑——IV 的生成、存储与传递完全由应用层自行管理。
✅ 正确做法:手动前置 IV
以下是一个完整、安全的 OFB 加密示例,包含 IV 生成、写入、读取与验证:
func encryptFile(filename, keystring string) error {
// 1. 读取原始文件
inFile, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open input file: %w", err)
}
defer inFile.Close()
// 2. 生成随机 IV(AES 块大小 = 16 字节)
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return fmt.Errorf("failed to generate IV: %w", err)
}
// 3. 创建输出文件(含 .enc 后缀)
outFile, err := os.Create(filename + ".enc")
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer outFile.Close()
// 4. 写入 IV(固定长度,无需额外长度字段)
if _, err := outFile.Write(iv); err != nil {
return fmt.Errorf("failed to write IV: %w", err)
}
// 5. 初始化 AES 块和 OFB 流
key := []byte(keystring)
block, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("invalid key: %w", err)
}
// 6. 构建 StreamWriter:加密流写入已含 IV 的文件
stream := cipher.NewOFB(block, iv)
writer := &cipher.StreamWriter{S: stream, W: outFile}
// 7. 加密并写入剩余内容
if _, err := io.Copy(writer, inFile); err != nil {
return fmt.Errorf("encryption failed: %w", err)
}
return nil
}
? 解密时必须先读取 IV
解密函数需严格按相同顺序反向操作:先读取前 aes.BlockSize 字节作为 IV,再用该 IV 初始化 OFB 流:
func decryptFile(encryptedFilename, keystring string) error {
inFile, err := os.Open(encryptedFilename)
if err != nil {
return err
}
defer inFile.Close()
// 1. 读取前 16 字节作为 IV
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(inFile, iv); err != nil {
return fmt.Errorf("failed to read IV: %w", err)
}
// 2. 初始化解密流
key := []byte(keystring)
block, err := aes.NewCipher(key)
if err != nil {
return err
}
stream := cipher.NewOFB(block, iv)
// 3. 输出到原始文件名(去掉 .enc)
outFilename := strings.TrimSuffix(encryptedFilename, ".enc")
outFile, err := os.Create(outFilename)
if err != nil {
return err
}
defer outFile.Close()
// 4. 解密剩余数据
reader := &cipher.StreamReader{S: stream, R: inFile}
if _, err := io.Copy(outFile, reader); err != nil {
return err
}
return nil
}
⚠️ 关键注意事项
- IV 必须唯一且不可预测:每次加密都应使用 crypto/rand.Read 生成新 IV(如示例),绝不可复用或硬编码。
- IV 长度固定:OFB 模式要求 IV 长度等于底层分组密码块大小(AES 为 16 字节),无需额外编码长度。
- 不要混淆 IV 和 salt:IV 用于加密随机性,salt 用于密钥派生(如 scrypt 或 bcrypt),二者用途不同,不可混用。
- 密钥管理独立于 IV:本例直接使用 []byte(keystring),生产环境应通过 crypto/rand 生成密钥,并配合密钥派生函数(如 argon2)从密码派生密钥。
遵循这一模式,即可确保加密文件可被可靠解密,同时满足 OFB 模式的安全性前提:唯一 IV + 保密密钥 = 语义安全。











