完善不同加密方法的使用

This commit is contained in:
yun
2026-09-30 15:49:12 +08:00
parent 034315ddbd
commit 7db722f8b9
69 changed files with 21362 additions and 2563 deletions
+78 -19
View File
@@ -1,40 +1,99 @@
// Package md5x 提供 MD5 摘要计算,以及十六进制/Base64 编码与流式、文件接口。
//
// # 安全警告
//
// MD5 已被证明无法抵抗碰撞攻击,攻击者可以构造出摘要完全相同的两份不同数据。
// 本包仅用于兼容旧系统或非安全用途(如文件校验和、缓存键、内容指纹),
// 不可用于口令存储、口令派生、消息认证或数字签名。
// 新代码请使用 sha256x、sha512x、sha3x 或 blake2x。
package md5x
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"hash"
"io"
"os"
)
// 字符串的md5
func Md5String(value string) string {
return Md5Byte([]byte(value))
const (
// Size 是 MD5 摘要的字节数(16)。
Size = md5.Size
// BlockSize 是 MD5 分组的字节数(64)。
BlockSize = md5.BlockSize
)
// New 返回一个计算 MD5 摘要的 hash.Hash,可反复 Write 以流式处理数据。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func New() hash.Hash {
return md5.New()
}
func Md5Byte(b []byte) string {
m := md5.New()
m.Write(b)
return hex.EncodeToString(m.Sum(nil))
// Sum 返回 data 的 MD5 摘要(Size 字节原始数据)。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func Sum(data []byte) []byte {
h := md5.New()
_, _ = h.Write(data)
return h.Sum(nil)
}
// 文件的md5
func Md5File(path string) (string, error) {
file, err := os.Open(path)
// Hex 返回 data 的 MD5 摘要的小写十六进制字符串。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func Hex(data []byte) string {
return hex.EncodeToString(Sum(data))
}
// Base64 返回 data 的 MD5 摘要的标准 Base64 字符串。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func Base64(data []byte) string {
return base64.StdEncoding.EncodeToString(Sum(data))
}
// SumReader 读取 r 中的全部数据并返回其 MD5 摘要;读取失败时返回包装后的 error。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func SumReader(r io.Reader) ([]byte, error) {
if r == nil {
return nil, fmt.Errorf("md5x: 读取器为 nil")
}
h := md5.New()
if _, err := io.Copy(h, r); err != nil {
return nil, fmt.Errorf("md5x: 读取数据失败: %w", err)
}
return h.Sum(nil), nil
}
// HexReader 读取 r 中的全部数据并返回其 MD5 摘要的小写十六进制字符串。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func HexReader(r io.Reader) (string, error) {
sum, err := SumReader(r)
if err != nil {
return "", err
}
defer file.Close()
return Md5Stream(file)
return hex.EncodeToString(sum), nil
}
// 流式的md5
func Md5Stream(reader io.Reader) (string, error) {
hash := md5.New()
_, err := io.Copy(hash, reader)
// HexFile 计算 path 所指向文件的 MD5 摘要并返回小写十六进制字符串;
// 文件不存在或读取失败时返回包装后的 error,不 panic。
//
// 安全警告:MD5 不安全,仅用于兼容旧系统或非安全用途(如校验和),不可用于口令或签名。
func HexFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
return "", fmt.Errorf("md5x: 打开文件 %q 失败: %w", path, err)
}
return hex.EncodeToString(hash.Sum(nil)), nil
defer f.Close()
sum, err := SumReader(f)
if err != nil {
return "", fmt.Errorf("md5x: 计算文件 %q 摘要失败: %w", path, err)
}
return hex.EncodeToString(sum), nil
}
+178
View File
@@ -0,0 +1,178 @@
package md5x_test
import (
"bytes"
"encoding/base64"
"encoding/hex"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"code.yun.ink/pkg/encryptx/md5x"
)
// 测试向量来源:RFC 1321 附录 A.5,并经 Python 3 hashlib 独立复算确认。
var vectors = []struct {
name string
input string
want string
}{
{"空串", "", "d41d8cd98f00b204e9800998ecf8427e"},
{"abc", "abc", "900150983cd24fb0d6963f7d28e17f72"},
{"message digest", "message digest", "f96b697d7cb7938d525a2f31aaf161d0"},
{"The quick brown fox jumps over the lazy dog", "The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"},
}
type failingReader struct{}
func (failingReader) Read([]byte) (int, error) { return 0, errors.New("模拟读取失败") }
func TestConstants(t *testing.T) {
if md5x.Size != 16 {
t.Fatalf("Size = %d, 期望 16", md5x.Size)
}
if md5x.BlockSize != 64 {
t.Fatalf("BlockSize = %d, 期望 64", md5x.BlockSize)
}
}
func TestVectors(t *testing.T) {
for _, v := range vectors {
t.Run(v.name, func(t *testing.T) {
data := []byte(v.input)
sum := md5x.Sum(data)
if len(sum) != md5x.Size {
t.Fatalf("Sum 长度 = %d, 期望 %d", len(sum), md5x.Size)
}
if got := hex.EncodeToString(sum); got != v.want {
t.Errorf("Sum = %s, 期望 %s", got, v.want)
}
if got := md5x.Hex(data); got != v.want {
t.Errorf("Hex = %s, 期望 %s", got, v.want)
}
if want := base64.StdEncoding.EncodeToString(sum); md5x.Base64(data) != want {
t.Errorf("Base64 = %s, 期望 %s", md5x.Base64(data), want)
}
h := md5x.New()
if _, err := h.Write(data); err != nil {
t.Fatalf("Write 失败: %v", err)
}
if got := hex.EncodeToString(h.Sum(nil)); got != v.want {
t.Errorf("New+Write = %s, 期望 %s", got, v.want)
}
})
}
}
func TestStreamingMatchesSum(t *testing.T) {
data := []byte(strings.Repeat("encryptx-md5-", 100))
h := md5x.New()
for i := 0; i < len(data); i += 7 {
end := i + 7
if end > len(data) {
end = len(data)
}
if _, err := h.Write(data[i:end]); err != nil {
t.Fatalf("Write 失败: %v", err)
}
}
if got, want := h.Sum(nil), md5x.Sum(data); !bytes.Equal(got, want) {
t.Errorf("分块写入 = %x, 期望 %x", got, want)
}
}
func TestReaderConsistency(t *testing.T) {
const s = "encryptx 一致性检查"
sum, err := md5x.SumReader(strings.NewReader(s))
if err != nil {
t.Fatalf("SumReader 失败: %v", err)
}
if want := md5x.Sum([]byte(s)); !bytes.Equal(sum, want) {
t.Errorf("SumReader = %x, 期望 %x", sum, want)
}
hexStr, err := md5x.HexReader(strings.NewReader(s))
if err != nil {
t.Fatalf("HexReader 失败: %v", err)
}
if want := md5x.Hex([]byte(s)); hexStr != want {
t.Errorf("HexReader = %s, 期望 %s", hexStr, want)
}
}
func TestReaderErrors(t *testing.T) {
if _, err := md5x.SumReader(nil); err == nil {
t.Fatal("nil 读取器应返回 error")
}
if _, err := md5x.HexReader(nil); err == nil {
t.Fatal("nil 读取器应返回 error")
}
if _, err := md5x.SumReader(failingReader{}); err == nil {
t.Fatal("读取失败时应返回 error")
}
}
func TestHexFile(t *testing.T) {
dir := t.TempDir()
content := []byte("文件内容 file content\n")
path := filepath.Join(dir, "sample.bin")
if err := os.WriteFile(path, content, 0o600); err != nil {
t.Fatalf("写入测试文件失败: %v", err)
}
got, err := md5x.HexFile(path)
if err != nil {
t.Fatalf("HexFile 失败: %v", err)
}
if want := md5x.Hex(content); got != want {
t.Errorf("HexFile = %s, 期望 %s", got, want)
}
}
func TestHexFileErrors(t *testing.T) {
if _, err := md5x.HexFile(filepath.Join(t.TempDir(), "不存在.bin")); err == nil {
t.Fatal("不存在的文件应返回 error")
}
if _, err := md5x.HexFile(""); err == nil {
t.Fatal("空路径应返回 error")
}
if _, err := md5x.HexFile(t.TempDir()); err == nil {
t.Fatal("目录路径应返回 error")
}
}
func TestLargeInput(t *testing.T) {
data := largeData(1 << 20)
want := md5x.Hex(data)
if got := md5x.Hex(data); got != want {
t.Errorf("重复计算结果不一致: %s vs %s", got, want)
}
got, err := md5x.HexReader(bytes.NewReader(data))
if err != nil {
t.Fatalf("HexReader 失败: %v", err)
}
if got != want {
t.Errorf("HexReader = %s, 期望 %s", got, want)
}
h := md5x.New()
if _, err := h.Write(data[:1<<19]); err != nil {
t.Fatalf("Write 失败: %v", err)
}
if _, err := h.Write(data[1<<19:]); err != nil {
t.Fatalf("Write 失败: %v", err)
}
if got := hex.EncodeToString(h.Sum(nil)); got != want {
t.Errorf("1MiB 分块 = %s, 期望 %s", got, want)
}
}
// largeData 生成确定性的 n 字节数据。
func largeData(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(i*31 + i/7)
}
return b
}