👻

GoでCRC-32Cハッシュでダウンロードファイルの整合性をチェックする

2020/10/28に公開

APIのレスポンスからファイルを保存して、CRC32C整合性をハッシュチェックするサンプル実装

func downloadFile(res *http.Response, path string) error {
	var buf bytes.Buffer
	tr := io.TeeReader(res.Body, &buf)
	writeFile(tr, path)
	if err := writeFile(tr, path); err != nil {
		return err
	}

	if err := checkCRC32c(res, path); err != nil {
		return err
	}
}

func writeFile(r io.Reader, path string) error {
	dir := filepath.Dir(path)
	err := os.MkdirAll(dir, os.ModePerm)
	if err != nil {
		return errors.Wrapf(err, "failed to create directory: %s", dir)
	}

	out, err := os.Create(path)
	if err != nil {
		return errors.Wrapf(err, "failed to create file: %s", path)
	}
	defer out.Close()

	if _, err := io.Copy(out, r); err != nil {
		return errors.Wrapf(err, "failed to write raw file: %s", path)
	}
	if err := out.Sync(); err != nil {
		return errors.Wrapf(err, "failed to sync raw file: %s", path)
	}
	return nil
}

func checkCRC32c(res *http.Response, path string) error {
	hash, checked := parseCRC32c(res)
	if !checked {
		return nil
	}
	bytes, err := ioutil.ReadFile(path)
	if err != nil {
		return errors.Wrapf(err, "failed to read file: %s", path)
	}
	if hash != crc32.Checksum(bytes, crc32cTable) {
		return errors.Errorf("CRC32C hash is incorrect and the file may be missing: %s", path)
	}
	return nil
}

func parseCRC32c(res *http.Response) (uint32, bool) {
	const prefix = "crc32c="
	for _, spec := range res.Header["X-Goog-Hash"] {
		if strings.HasPrefix(spec, prefix) {
			c, err := decodeUint32(spec[len(prefix):])
			if err != nil {
				log.L().Warn("failed to parse CRC32c hash", zap.Error(err))
				break
			}
			return c, true
		}
	}
	return 0, false
}

func decodeUint32(b64 string) (uint32, error) {
	d, err := base64.StdEncoding.DecodeString(b64)
	if err != nil {
		return 0, errors.Wrapf(err, "failed to base64 decode string: %s", b64)
	}
	if len(d) != 4 {
		return 0, errors.Errorf("%q does not encode a 32-bit value", d)
	}
	return uint32(d[0])<<24 + uint32(d[1])<<16 + uint32(d[2])<<8 + uint32(d[3]), nil
}

ハッシュと ETag: ベスト プラクティス

Discussion