使用go-msgauth/dkim库进行DKIM验证时持续报“body hash did not verify”错误的排查求助
使用go-msgauth/dkim库进行DKIM验证时持续报“body hash did not verify”错误的排查求助
大家好,我最近在使用go-msgauth/dkim库做DKIM验证时碰到了个头疼的问题:直接读取本地.eml文件转成[]bytes传入Verify方法时验证完全正常,但通过业务代码里的io.Pipe+WriteOriginalMail流程写入邮件内容后再验证,就一直报「body hash did not verify」错误。
问题场景还原
我的业务代码逻辑是这样的:先在goroutine里把邮件的原始头和体写入到io.Writer,然后把对应的io.Reader传给dkim.VerifyWithOptions做验证。核心代码片段如下:
go func() { defer func() { _ = w.Close() r := recover() if r != nil { mc.Error(LogPanicWritingMail, slog.Any("recovered", r), ) } }() _ = message.WriteOriginalMail(mc.Context, mc.Logger, info, w) _, _ = w.Write([]byte("\r\n")) // 这里是我怀疑的关键点 }() verifications, err := dkim.VerifyWithOptions( r, &dkim.VerifyOptions{ MaxVerifications: 5, LookupTXT: j.lookupTXT, }, )
可复现的测试代码
我写了一个最小复现示例,能稳定复现这个问题:
package main import ( "bufio" "bytes" "context" "fmt" "io" "log" "strings" "github.com/emersion/go-msgauth/dkim" ) type BodyInfo struct { headers string body string } // 模拟业务中写入原始邮件头和体的方法 func WriteOriginalMail(ctx context.Context, info BodyInfo, w io.Writer) error { _, _ = w.Write([]byte(info.headers)) _, _ = w.Write([]byte("\r\n")) // 头和体的分隔符 _, _ = w.Write([]byte(info.body)) return nil } func main() { ctx := context.Background() // 模拟DKIM签名过的邮件:空body对应的bh值是47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= rawHeaders := strings.Join([]string{ "DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=test;", " bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=;", " h=from:to:subject:date;", " b=abc123==", "From: test@example.com", "To: you@example.com", "Subject: DKIM test", "Date: Tue, 1 Apr 2025 12:00:00 +0000", }, "\r\n") rawBody := "\r\n" // 空body,匹配上面的bh值 info := BodyInfo{ headers: rawHeaders, body: rawBody, } r, w := io.Pipe() go func() { defer w.Close() _ = WriteOriginalMail(ctx, info, w) _, _ = w.Write([]byte("\r\n")) // 问题根源:额外写入的换行!注释这行验证就通过 }() verifications, err := dkim.VerifyWithOptions( r, &dkim.VerifyOptions{ MaxVerifications: 5, LookupTXT: func(ctx context.Context, domain string) ([]string, error) { // 模拟TXT记录查询,这里不实际验证签名仅测试hash return []string{}, nil }, }, ) if err != nil { log.Fatalf("DKIM verify failed: %v", err) } for _, v := range verifications { fmt.Printf("Result: err=%v, domain=%s\n", v.Err, v.Domain) } }
我排查到的线索
在复现代码里,我把goroutine里那行额外的w.Write([]byte("\r\n"))注释掉之后,DKIM验证立刻就通过了!这说明额外写入的换行符修改了原始邮件的body内容,导致计算出的body hash和DKIM签名里的bh字段不匹配。
另外我也看了业务中实际的WriteOriginalMail实现,它本身已经处理了头和体的分隔(写入了\r\n),并且通过io.Copy把原始body完整写入:
func WriteOriginalMail( ctx context.Context, logger *slog.Logger, info BodyInfo, w io.Writer, ) error { headers := info.ReadHeaders(logger) body := info.ReadBody(logger) _ = headers.Write(w) _, _ = w.Write([]byte("\r\n")) // 头和体的分隔 bodyR := body.ReadPart(logger).OriginalReader(ctx) _, err := io.Copy(w, bodyR) return err }
想请教的问题
- 是不是这个额外的
\r\n确实是导致body hash验证失败的根源? - 在通过io.Pipe传递邮件内容给DKIM验证时,有没有更稳妥的方式确保写入的内容和原始签名的邮件完全一致,避免这类意外修改?
内容来源于stack exchange
相关产品推荐
相关产品推荐

