關於 web service, unity, blogger 等軟體工程筆記

Calculate file's chunk MD5 checksum using Golang

Edit icon 沒有留言
Golang

由於工作需求,需要將檔案先分塊 (chunk),計算每塊的 MD5 checksum,而大部分的 tools 都是算整個檔案的 MD5 checksum,想說這樣的功能應該不會很困難,因此使用 Golang 寫一小段 CLI (Command line interface) 程式來完成該功能,順便練習一下 Golang。

程式碼發布在 Gist。以下紀錄演算法概念以及說明。

什麼是 MD5 Checksum

Checksum 是用來檢查檔案是否完整正確的校正和,應用上伺服器會提供一組 checksum,待用戶檔案下載完成後,計算該檔案的 checksum 是否與伺服器提供那組相同,以檢查下載檔案是否正確。

MD5 (Message-Digest Algorithm) 是一種雜湊函數 (Hash function),計算產生 128 bits (16 bytes) 的雜湊值,該值來做為 checksum 使用。其他常用來計算 checksum 的函數,例如有 SHA-1, SHA-256, SHA-512 等等。

關於演算法

大部分的工具計算 MD5 checksum 都是這樣:

整個檔案計算 MD5 checksum

整個檔案計算 MD5 checksum

但需求要將檔案切塊 (chunk),每塊固定大小 1MB,分不完的資料自成一塊,每塊計算其 MD5 checksum:

將檔案切塊,計算每塊的 MD5 checksum

將檔案切塊,計算每塊的 MD5 checksum

完成實作請參考 Gist 或是以下的程式碼。

MD5 checksum 程式碼

package main

import (
   "crypto/md5"
   "flag"
   "fmt"
   "io"
   "os"
)

const (
   bufferSize = int64(8 * 1024)
)

func main() {
   // 從 Args 讀取參數,檔案位置以及 chunk 大小
   var filePath string
   var chunkSize int64

   flag.StringVar(&filePath, "filepath", "", "file path")
   flag.Int64Var(&chunkSize, "chunksize", -1, "chunk size in bytes")

   flag.Parse()

   if len(filePath) <= 0 {
      println("No filepath")
      os.Exit(1)
   }

   // 讀取檔案資訊
   fileInfo, err := os.Stat(filePath)
   if err != nil {
      println("Cannot find file,", filePath, ", error:", err.Error())
      os.Exit(1)
   }

   // 計算要分成幾個 chunks,或是單一 chunk (e.g. whole file)
   chunkCount := int64(1)
   if chunkSize > 0 {
      chunkCount = (fileInfo.Size() / chunkSize) + 1
   } else {
      chunkSize = fileInfo.Size()
   }

   // 嘗試開啟檔案,加入 defer 關閉檔案
   file, err := os.Open(filePath)
   if err != nil {
      println("Cannot open file,", filePath, ", error:", err.Error())
      os.Exit(1)
   }
   defer file.Close()

   // 計算 chunk's md5 checksum 演算法,只用到 bufferSize 這麼大的緩存區來處理
   hash := md5.New()
   buff := make([]byte, bufferSize)
   for i := int64(0); i < chunkCount; i++ {
      hash.Reset()
      for chunkReaded := int64(0); chunkReaded < chunkSize; {

         // 考慮是否已經讀完一個 chunk,計算這次要讀多少資料
         m := bufferSize
         if chunkReaded+m > chunkSize {
            m = chunkSize - chunkReaded
         }

         // 讀取 [0-m] 這麼多的資料
         n, err := file.Read(buff[0:m])
         if err != nil {
            // 如果錯誤為 EOF,表示已經讀完檔案
            if err == io.EOF {
               break
            }
            println("Cannot read file,", filePath, ", error:", err.Error())
            os.Exit(1)
         }

         // 只寫入成功讀取的部分 [0-n]
         hash.Write(buff[0:n])
         chunkReaded += int64(n)
      }

      if chunkCount > 1 {
         fmt.Printf("chunk[%d] md5 = %x\r\n", i, hash.Sum(nil))
      } else {
         fmt.Printf("md5 = %x\r\n", hash.Sum(nil))
      }
   }
}

編譯成執行檔 md5sum 後的使用範例,filepath 指定檔案位置,chunksize 切塊大小(省略表示計算整個檔案的 MD5 checksum):

md5sum --filepath="C:\example.exe" --chunksize=10485760

沒有留言: