• goalng中md5算法的4种写法及其性能比较以及源码简单分析


    1. md5算法简介

    md5算法全称Message Digest Algorithm 5,中文名消息摘要算法第五版,主要用于文件或数据的完整性校验。我们在日常的研发中通常会见到32位的字符串。
    md5算法具有以下特点:

    1. md5算法可输入任意长度的数据,输出均为128bit的二进制数据。
    2. md5算法不可逆(或者说碰撞概率非常小)

    2. Golang md5包

    在golang中,我们可以通过crypto/md5 来直接调用md5算法。
    常见的调用方式有以下四种:

    func MD5_1(s string) string {
       hash := md5.New()
       _, err := hash.Write([]byte(s))
       if err != nil {
          panic(err)
       }
       sum := hash.Sum(nil)
       return fmt.Sprintf("%x\n", sum)
    }
    
    func MD5_2(s string) string {
       sum := md5.Sum([]byte(s))
       return fmt.Sprintf("%x\n", sum)
    }
    
    func MD5_3(s string) string {
       hash := md5.New()
       _, _ = io.WriteString(hash, s)
       return hex.EncodeToString(hash.Sum(nil))
    }
    
    func MD5_4(s string) string {
       sum := md5.Sum([]byte(s))
       return hex.EncodeToString(sum[:])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    我们通过基准测试(benchmark)来比较四种写法的性能。

    ➜ go test -bench .
    goos: darwin
    goarch: arm64
    BenchmarkMD5_1-10        5461878               217.1 ns/op
    BenchmarkMD5_2-10        4637865               258.8 ns/op
    BenchmarkMD5_3-10        5880980               205.1 ns/op
    BenchmarkMD5_4-10        8062526               147.0 ns/op
    PASS
    ok      _/Users/code/project/md5_test/source  7.441s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们可以看出方法4的性能最优,因此我们更推荐这种写法。

    3. Golang 分块计算大文件md5

    我们有时候还需要面对下载大文件的情况,而此时我们可以考虑分块计算,从而降低时间消耗。

    func Md5check2(fileName, md5FileName string) (error, bool) {
       file, err := os.Open(fileName)
       if err != nil {
          return err, false
       }
       defer file.Close()
       md5String, err := ioutil.ReadFile(md5FileName)
       if err != nil {
          return err, false
       }
       md5Bytes, _ := hex.DecodeString(string(md5String))
       return md5Check_2(fileName, md5Bytes)
    }
    
    func md5Check_2(fileName string, md5sum []byte) (error, bool) {
       fileMd5, err := calcMd5_2(fileName)
       if err != nil {
          return err, false
       }
    
       if !bytes.Equal(fileMd5, md5sum) {
          return errors.New("the md5 check failed, fileMD5 not equal to md5sum"), false
       }
       return nil, true
    }
    
    func calcMd5_2(fileName string) ([]byte, error) {
       f, err := os.Open(fileName)
       if err != nil {
          return nil, err
       }
       defer f.Close()
    
       const bufferSize = 65536
    
       hash := md5.New()
       for buf, reader := make([]byte, bufferSize), bufio.NewReader(f); ; {
          n, err := reader.Read(buf)
          if err != nil {
             if err == io.EOF {
                break
             }
             return nil, err
          }
    
          hash.Write(buf[:n])
       }
    
       return hash.Sum(nil), nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    4. crypto/md5源码分析

    md5包中定义了如下两个常量:

    // The size of an MD5 checksum in bytes. md5校验和字节数
    const Size = 16
    
    // The blocksize of MD5 in bytes. md5字节块大小
    const BlockSize = 64
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们调用md5.New() 方法时,会返回一个名为digest的结构体。

    func New() hash.Hash {
     d := new(digest)
     d.Reset()
     return d
    }
    
    type digest struct {
     s   [4]uint32
     x   [BlockSize]byte
     nx  int
     len uint64
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们调用md5.Sum()方法,可以直接返回md5值。

    func Sum(data []byte) [Size]byte {
     var d digest
     d.Reset()
     d.Write(data)
     return d.checkSum()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们可以看到,md5.Sum()内部主要是通过write方法来实现。

    func (d *digest) Write(p []byte) (nn int, err error) {
     // Note that we currently call block or blockGeneric
     // directly (guarded using haveAsm) because this allows
     // escape analysis to see that p and d don't escape.
     nn = len(p)
     d.len += uint64(nn)
     if d.nx > 0 {
      n := copy(d.x[d.nx:], p)
      d.nx += n
      if d.nx == BlockSize {
       if haveAsm {
        block(d, d.x[:])
       } else {
        blockGeneric(d, d.x[:])
       }
       d.nx = 0
      }
      p = p[n:]
     }
     if len(p) >= BlockSize {
      n := len(p) &^ (BlockSize - 1)
      if haveAsm {
       block(d, p[:n])
      } else {
       blockGeneric(d, p[:n])
      }
      p = p[n:]
     }
     if len(p) > 0 {
      d.nx = copy(d.x[:], p)
     }
     return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    缓存穿透、缓存击穿、缓存雪崩
    react-router v6
    2020长三角区块链应用创新大赛复赛第三场于能链科技成功举办!
    Unity之NetCode多人网络游戏联机对战教程(3)--NetworkObject组件讲解
    【图像识别-指纹识别】指纹特征提取附matlab代码
    最长公共子串问题
    Axure RP美容美妆医美行业上门服务交互原型图模板源文件
    【CSS】不定区间进度条
    神经网络控制与matlab仿真,matlab神经网络拟合预测
    【WSN布局】基于LICHTENBERG算的多目标传感器选择和放置优化问题研究附matlab代码
  • 原文地址:https://blog.csdn.net/weixin_43915303/article/details/126235799