Repository: gohugoio/hugo (language: go)

Issue:
Update to github.com/mitchellh/hashstructure v2

Fingerprint appended twice causing file name too long
<!--
Please do not use the issue queue for questions or troubleshooting. Unless you are certain that your issue is a software defect, use the forum:

https://discourse.gohugo.io
-->

<!-- Please answer these questions before submitting your issue. Thanks! -->

### What version of Hugo are you using (`hugo version`)?

I normally use the extended edition, currently using the standard one.

<pre>
$ hugo version
hugo v0.128.2-de36c1a95d28595d8243fd8b891665b069ed0850 darwin/arm64 BuildDate=2024-07-04T08:13:25Z VendorInfo=gohugoio
</pre>

### Does this issue reproduce with the latest release?

Yes

---

Every now and then Hugo seems to append the fingerprint of a hashed image to it twice, this causes the server to fail because the resulting filename ends up being to long:

```
Change detected, rebuilding site (#11).
2024-07-13 23:00:45.163 +1000
Template changed /_default/list.html
ERROR open /Users/james/code/github/jloh/blog/public/posts/create-a-edgerouter-dmz/new_firewall_group.3ef0e068ae956523362daaab41a5c19413fc600293421b569d084e0ad28cae151f3c37908775cf8bb68b40a35ea70ab26f992eaed2bf19ad8898900b170e4444.3ef0e068ae956523362daaab41a5c19413fc600293421b569d084e0ad28cae151f3c37908775cf8bb68b40a35ea70ab26f992eaed2bf19ad8898900b170e4444.png: file name too long
ERROR open /Users/james/code/github/jloh/blog/public/posts/create-a-edgerouter-dmz/firewall_group_add_ranges.2135673415dc56dec230a31f866b42b22f535e25eddc4ab42d265509690184b1be03afc3020dc17c25ffd1e88c4346b29fd35dbdeb4ddbdf3b6e7809f48ac7f4.2135673415dc56dec230a31f866b42b22f535e25eddc4ab42d265509690184b1be03afc3020dc17c25ffd1e88c4346b29fd35dbdeb4ddbdf3b6e7809f48ac7f4.png: file name too long
ERROR open /Users/james/code/github/jloh/blog/public/posts/create-a-edgerouter-dmz/add_new_firewall_set.7465bafac167f2e5d84094403a821a040e8ebeaa484b296ef382d299c229f84196a01ee220ca7f4c3c4a78de2d10dccf66ec395b1f3ecfcac4127bb70a6fc478.7465bafac167f2e5d84094403a821a040e8ebeaa484b296ef382d299c229f84196a01ee220ca7f4c3c4a78de2d10dccf66ec395b1f3ecfcac4127bb70a6fc478.png: file name too long
ERROR open /Users/james/code/github/jloh/blog/public/posts/create-a-edgerouter-dmz/add_established_related_rule.83ac29f37d94588bf486c3656023249dc24daa583dfbaa701f35cea94b0358c86f3fb300ebada0aaead9cd978616640bdce62f0f947ab7cd354fb1ec223bff5f.83ac29f37d94588bf486c3656023249dc24daa583dfbaa701f35cea94b0358c86f3fb300ebada0aaead9cd978616640bdce62f0f947ab7cd354fb1ec223bff5f.png: file name too long
ERROR Rebuild failed: open /Users/james/code/github/jloh/blog/public/posts/installing-newrelic-monitor-gitlab/newrelic_install_menu.8c9fc81716df711510656edd5b972e5938bc81ef59a7cc84c6cdd2a9876a7eb09d47c02f6edd82b70b9d633e4e29f02d2bdb0feeeed9a8d315a104daaa770004.8c9fc81716df711510656edd5b972e5938bc81ef59a7cc84c6cdd2a9876a7eb09d47c02f6edd82b70b9d633e4e29f02d2bdb0feeeed9a8d315a104daaa770004.png: file name too long
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Total in 475 ms
```

Note that the images have the fingerprint once, a `.`, then the same fingerprint again.

I use a `render-image.html` hook that looks like this:

```golang
{{- with .Page.Resources.GetMatch .Destination -}}
	{{- $image := . -}}
	{{- $imageSHA := . | resources.Fingerprint "sha512" -}}
	{{- $src := $image.RelPermalink -}}

	{{- if $src -}}
		{{- $srcSets := slice -}}
		{{- range $imageSize := sort (site.Params.photoWidths) -}}
			{{- if ge $image.Width . }}
				{{- $srcSets = $srcSets | append (print (($image.Resize (print $imageSize "x")).RelPermalink | safeURL ) " " $imageSize "w") -}}
			{{- end -}}
		{{- end -}}
		{{- $srcSets = $srcSets | append (print (print ($imageSHA.RelPermalink) " " $image.Width "w")) -}}
		{{- $srcSets = delimit $srcSets ", " | safeHTMLAttr -}}
		<img
			class="mx-auto my-6"
			sizes="(min-width: 42rem) 42rem, 100vw"
			{{ printf "srcset=%q" $srcSets | safeHTMLAttr }}
			src="{{ $imageSHA.RelPermalink }}"
			alt="{{ $.Text }}"
			{{ with $.Title }}title="{{ . }}"{{ end }}
			width="{{ $image.Width }}"
			height="{{ $image.Height }}"
			loading="lazy"
		/>
	{{- else -}}
		{{- errorf "failed to find image" -}}
		could not find image
	{{- end -}}
{{- else -}}

	{{- if strings.HasPrefix .Destination "http" -}}
		<img
			loading="lazy"
			class="mx-auto my-6"
			src="{{ .Destination | safeURL }}"
			alt="{{ .Text }}"
			{{ with .Title }}title="{{ . }}"{{ end }}
		/>
	{{- else -}}
		<img
			loading="lazy"
			class="mx-auto my-6"
			src="{{ .Destination | safeURL }}"
			alt="{{ .Text }}"
			{{ with .Title }}title="{{ . }}"{{ end }}
			{{ with imageConfig ( printf "static/%s" .Destination ) }}width="{{ .Width }}" height="{{ .Height }}"{{ end }}
		/>
	{{- end -}}
{{- end -}}
```

I'm having trouble getting a reliable replication case, it seems to happen randomly. I wasn't editing the post that has those images either, it was a different post.

As per #12655 I am using the new `templates.Defer` functionality, I don't know whether thats involved. Apologies if this is a problem with my template but I have only been experiencing this recently!


Discussion / hints:

Fix patch:
diff --git a/common/hashing/hashing.go b/common/hashing/hashing.go
new file mode 100644
index 00000000000..70aa74ecd7a
--- /dev/null
+++ b/common/hashing/hashing.go
@@ -0,0 +1,166 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package hashing provides common hashing utilities.
+package hashing
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"io"
+	"strconv"
+	"sync"
+
+	"github.com/cespare/xxhash/v2"
+	"github.com/gohugoio/hashstructure"
+	"github.com/gohugoio/hugo/identity"
+)
+
+// XXHashFromReader calculates the xxHash for the given reader.
+func XXHashFromReader(r io.Reader) (uint64, int64, error) {
+	h := getXxHashReadFrom()
+	defer putXxHashReadFrom(h)
+
+	size, err := io.Copy(h, r)
+	if err != nil {
+		return 0, 0, err
+	}
+	return h.Sum64(), size, nil
+}
+
+// XXHashFromString calculates the xxHash for the given string.
+func XXHashFromString(s string) (uint64, error) {
+	h := xxhash.New()
+	h.WriteString(s)
+	return h.Sum64(), nil
+}
+
+// XxHashFromStringHexEncoded calculates the xxHash for the given string
+// and returns the hash as a hex encoded string.
+func XxHashFromStringHexEncoded(f string) string {
+	h := xxhash.New()
+	h.WriteString(f)
+	hash := h.Sum(nil)
+	return hex.EncodeToString(hash)
+}
+
+// MD5FromStringHexEncoded returns the MD5 hash of the given string.
+func MD5FromStringHexEncoded(f string) string {
+	h := md5.New()
+	h.Write([]byte(f))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+// HashString returns a hash from the given elements.
+// It will panic if the hash cannot be calculated.
+// Note that this hash should be used primarily for identity, not for change detection as
+// it in the more complex values (e.g. Page) will not hash the full content.
+func HashString(vs ...any) string {
+	hash := HashUint64(vs...)
+	return strconv.FormatUint(hash, 10)
+}
+
+var hashOptsPool = sync.Pool{
+	New: func() any {
+		return &hashstructure.HashOptions{
+			Hasher: xxhash.New(),
+		}
+	},
+}
+
+func getHashOpts() *hashstructure.HashOptions {
+	return hashOptsPool.Get().(*hashstructure.HashOptions)
+}
+
+func putHashOpts(opts *hashstructure.HashOptions) {
+	opts.Hasher.Reset()
+	hashOptsPool.Put(opts)
+}
+
+// HashUint64 returns a hash from the given elements.
+// It will panic if the hash cannot be calculated.
+// Note that this hash should be used primarily for identity, not for change detection as
+// it in the more complex values (e.g. Page) will not hash the full content.
+func HashUint64(vs ...any) uint64 {
+	var o any
+	if len(vs) == 1 {
+		o = toHashable(vs[0])
+	} else {
+		elements := make([]any, len(vs))
+		for i, e := range vs {
+			elements[i] = toHashable(e)
+		}
+		o = elements
+	}
+
+	hashOpts := getHashOpts()
+	defer putHashOpts(hashOpts)
+
+	hash, err := hashstructure.Hash(o, hashOpts)
+	if err != nil {
+		panic(err)
+	}
+	return hash
+}
+
+type keyer interface {
+	Key() string
+}
+
+// For structs, hashstructure.Hash only works on the exported fields,
+// so rewrite the input slice for known identity types.
+func toHashable(v any) any {
+	switch t := v.(type) {
+	case keyer:
+		return t.Key()
+	case identity.IdentityProvider:
+		return t.GetIdentity()
+	default:
+		return v
+	}
+}
+
+type xxhashReadFrom struct {
+	buff []byte
+	*xxhash.Digest
+}
+
+func (x *xxhashReadFrom) ReadFrom(r io.Reader) (int64, error) {
+	for {
+		n, err := r.Read(x.buff)
+		if n > 0 {
+			x.Digest.Write(x.buff[:n])
+		}
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			}
+			return int64(n), err
+		}
+	}
+}
+
+var xXhashReadFromPool = sync.Pool{
+	New: func() any {
+		return &xxhashReadFrom{Digest: xxhash.New(), buff: make([]byte, 48*1024)}
+	},
+}
+
+func getXxHashReadFrom() *xxhashReadFrom {
+	return xXhashReadFromPool.Get().(*xxhashReadFrom)
+}
+
+func putXxHashReadFrom(h *xxhashReadFrom) {
+	h.Reset()
+	xXhashReadFromPool.Put(h)
+}
diff --git a/common/loggers/handlersmisc.go b/common/loggers/handlersmisc.go
index 55bf8b940d5..2ae6300f74b 100644
--- a/common/loggers/handlersmisc.go
+++ b/common/loggers/handlersmisc.go
@@ -21,7 +21,7 @@ import (
 	"sync"
 
 	"github.com/bep/logg"
-	"github.com/gohugoio/hugo/identity"
+	"github.com/gohugoio/hugo/common/hashing"
 )
 
 // PanicOnWarningHook panics on warnings.
@@ -85,7 +85,7 @@ func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
 	}
 	h.mu.Lock()
 	defer h.mu.Unlock()
-	hash := identity.HashUint64(e.Level, e.Message, e.Fields)
+	hash := hashing.HashUint64(e.Level, e.Message, e.Fields)
 	if h.seen[hash] {
 		return errStop
 	}
diff --git a/config/namespace.go b/config/namespace.go
index b518c6c0100..46b5014c34c 100644
--- a/config/namespace.go
+++ b/config/namespace.go
@@ -16,13 +16,13 @@ package config
 import (
 	"encoding/json"
 
-	"github.com/gohugoio/hugo/identity"
+	"github.com/gohugoio/hugo/common/hashing"
 )
 
 func DecodeNamespace[S, C any](configSource any, buildConfig func(any) (C, any, error)) (*ConfigNamespace[S, C], error) {
 	// Calculate the hash of the input (not including any defaults applied later).
 	// This allows us to introduce new config options without breaking the hash.
-	h := identity.HashString(configSource)
+	h := hashing.HashString(configSource)
 
 	// Build the config
 	c, ext, err := buildConfig(configSource)
diff --git a/go.mod b/go.mod
index c77f4278345..b2733ed27be 100644
--- a/go.mod
+++ b/go.mod
@@ -37,6 +37,7 @@ require (
 	github.com/gobuffalo/flect v1.0.2
 	github.com/gobwas/glob v0.2.3
 	github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
+	github.com/gohugoio/hashstructure v0.1.0
 	github.com/gohugoio/httpcache v0.7.0
 	github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0
 	github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0
@@ -53,7 +54,6 @@ require (
 	github.com/makeworld-the-better-one/dither/v2 v2.4.0
 	github.com/marekm4/color-extractor v1.2.1
 	github.com/mattn/go-isatty v0.0.20
-	github.com/mitchellh/hashstructure v1.1.0
 	github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
 	github.com/muesli/smartcrop v0.3.0
 	github.com/niklasfasching/go-org v1.7.0
diff --git a/go.sum b/go.sum
index f535465cf50..3c5ae666818 100644
--- a/go.sum
+++ b/go.sum
@@ -226,6 +226,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
+github.com/gohugoio/hashstructure v0.1.0 h1:kBSTMLMyTXbrJVAxaKI+wv30MMJJxn9Q8kfQtJaZ400=
+github.com/gohugoio/hashstructure v0.1.0/go.mod h1:8ohPTAfQLTs2WdzB6k9etmQYclDUeNsIHGPAFejbsEA=
 github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs=
 github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
 github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 h1:MNdY6hYCTQEekY0oAfsxWZU1CDt6iH+tMLgyMJQh/sg=
@@ -377,8 +379,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
-github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
diff --git a/helpers/general.go b/helpers/general.go
index fa65265b836..11cc185a855 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -15,8 +15,6 @@ package helpers
 
 import (
 	"bytes"
-	"crypto/md5"
-	"encoding/hex"
 	"fmt"
 	"io"
 	"net"
@@ -27,12 +25,11 @@ import (
 	"unicode"
 	"unicode/utf8"
 
-	"github.com/cespare/xxhash/v2"
+	bp "github.com/gohugoio/hugo/bufferpool"
+
 	"github.com/spf13/afero"
 
 	"github.com/jdkato/prose/transform"
-
-	bp "github.com/gohugoio/hugo/bufferpool"
 )
 
 // FilePathSeparator as defined by os.Separator.
@@ -258,72 +255,6 @@ func SliceToLower(s []string) []string {
 	return l
 }
 
-// XxHashString takes a string and returns its xxHash hash.
-func XxHashString(f string) string {
-	h := xxhash.New()
-	h.WriteString(f)
-	hash := h.Sum(nil)
-	return hex.EncodeToString(hash)
-}
-
-// MD5String takes a string and returns its MD5 hash.
-func MD5String(f string) string {
-	h := md5.New()
-	h.Write([]byte(f))
-	return hex.EncodeToString(h.Sum([]byte{}))
-}
-
-// MD5FromReaderFast creates a MD5 hash from the given file. It only reads parts of
-// the file for speed, so don't use it if the files are very subtly different.
-// It will not close the file.
-// It will return the MD5 hash and the size of r in bytes.
-func MD5FromReaderFast(r io.ReadSeeker) (string, int64, error) {
-	const (
-		// Do not change once set in stone!
-		maxChunks = 8
-		peekSize  = 64
-		seek      = 2048
-	)
-
-	h := md5.New()
-	buff := make([]byte, peekSize)
-
-	for i := 0; i < maxChunks; i++ {
-		if i > 0 {
-			_, err := r.Seek(seek, 0)
-			if err != nil {
-				if err == io.EOF {
-					break
-				}
-				return "", 0, err
-			}
-		}
-
-		_, err := io.ReadAtLeast(r, buff, peekSize)
-		if err != nil {
-			if err == io.EOF || err == io.ErrUnexpectedEOF {
-				h.Write(buff)
-				break
-			}
-			return "", 0, err
-		}
-		h.Write(buff)
-	}
-
-	size, _ := r.Seek(0, io.SeekEnd)
-
-	return hex.EncodeToString(h.Sum(nil)), size, nil
-}
-
-// MD5FromReader creates a MD5 hash from the given reader.
-func MD5FromReader(r io.Reader) (string, error) {
-	h := md5.New()
-	if _, err := io.Copy(h, r); err != nil {
-		return "", nil
-	}
-	return hex.EncodeToString(h.Sum(nil)), nil
-}
-
 // IsWhitespace determines if the given rune is whitespace.
 func IsWhitespace(r rune) bool {
 	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index fbc1a8aa1d1..b23084a470a 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -22,7 +22,6 @@ import (
 	"time"
 
 	"github.com/gobuffalo/flect"
-	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/langs"
 	"github.com/gohugoio/hugo/markup/converter"
 	xmaps "golang.org/x/exp/maps"
@@ -32,6 +31,7 @@ import (
 	"github.com/gohugoio/hugo/source"
 
 	"github.com/gohugoio/hugo/common/constants"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/hugo"
 	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/common/maps"
@@ -328,7 +328,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
 	ps.m.setMetaPostCount++
 	var cascadeHashPre uint64
 	if ps.m.setMetaPostCount > 1 {
-		cascadeHashPre = identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
+		cascadeHashPre = hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
 		ps.m.pageConfig.CascadeCompiled = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
 
 	}
@@ -360,7 +360,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
 	}
 
 	if ps.m.setMetaPostCount > 1 {
-		ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
+		ps.m.setMetaPostCascadeChanged = cascadeHashPre != hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
 		if !ps.m.setMetaPostCascadeChanged {
 
 			// No changes, restore any value that may be changed by aggregation.
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl.go b/hugolib/pagesfromdata/pagesfromgotmpl.go
index 4107f9e33cf..fd7213bd93b 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl.go
@@ -19,6 +19,7 @@ import (
 	"io"
 	"path/filepath"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/common/paths"
 	"github.com/gohugoio/hugo/helpers"
@@ -219,7 +220,7 @@ type BuildState struct {
 }
 
 func (b *BuildState) hash(v any) uint64 {
-	return identity.HashUint64(v)
+	return hashing.HashUint64(v)
 }
 
 func (b *BuildState) checkHasChangedAndSetSourceInfo(changedPath string, v any) bool {
diff --git a/identity/identityhash.go b/identity/identityhash.go
deleted file mode 100644
index 8760ff64d2d..00000000000
--- a/identity/identityhash.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package identity
-
-import (
-	"strconv"
-
-	"github.com/mitchellh/hashstructure"
-)
-
-// HashString returns a hash from the given elements.
-// It will panic if the hash cannot be calculated.
-// Note that this hash should be used primarily for identity, not for change detection as
-// it in the more complex values (e.g. Page) will not hash the full content.
-func HashString(vs ...any) string {
-	hash := HashUint64(vs...)
-	return strconv.FormatUint(hash, 10)
-}
-
-// HashUint64 returns a hash from the given elements.
-// It will panic if the hash cannot be calculated.
-// Note that this hash should be used primarily for identity, not for change detection as
-// it in the more complex values (e.g. Page) will not hash the full content.
-func HashUint64(vs ...any) uint64 {
-	var o any
-	if len(vs) == 1 {
-		o = toHashable(vs[0])
-	} else {
-		elements := make([]any, len(vs))
-		for i, e := range vs {
-			elements[i] = toHashable(e)
-		}
-		o = elements
-	}
-
-	hash, err := hashstructure.Hash(o, nil)
-	if err != nil {
-		panic(err)
-	}
-	return hash
-}
-
-type keyer interface {
-	Key() string
-}
-
-// For structs, hashstructure.Hash only works on the exported fields,
-// so rewrite the input slice for known identity types.
-func toHashable(v any) any {
-	switch t := v.(type) {
-	case keyer:
-		return t.Key()
-	case IdentityProvider:
-		return t.GetIdentity()
-	default:
-		return v
-	}
-}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index f7b68db2566..08d9322ab16 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -25,9 +25,9 @@ import (
 	"sync"
 	"time"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/types"
 	"github.com/gohugoio/hugo/compare"
-	"github.com/gohugoio/hugo/identity"
 )
 
 // The Provider interface defines an interface for measuring metrics.
@@ -241,7 +241,7 @@ func howSimilar(a, b any) int {
 		return 90
 	}
 
-	h1, h2 := identity.HashString(a), identity.HashString(b)
+	h1, h2 := hashing.HashString(a), hashing.HashString(b)
 	if h1 == h2 {
 		return 100
 	}
diff --git a/resources/image.go b/resources/image.go
index 188f4962460..4595866d48c 100644
--- a/resources/image.go
+++ b/resources/image.go
@@ -29,9 +29,9 @@ import (
 	color_extractor "github.com/marekm4/color-extractor"
 
 	"github.com/gohugoio/hugo/cache/filecache"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/hstrings"
 	"github.com/gohugoio/hugo/common/paths"
-	"github.com/gohugoio/hugo/identity"
 
 	"github.com/disintegration/gift"
 
@@ -40,7 +40,6 @@ import (
 
 	"github.com/gohugoio/hugo/resources/resource"
 
-	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/resources/images"
 
 	// Blind import for image.Decode
@@ -274,7 +273,7 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
 	}
 
 	conf.Action = "filter"
-	conf.Key = identity.HashString(gfilters)
+	conf.Key = hashing.HashString(gfilters)
 	conf.TargetFormat = targetFormat
 	if conf.TargetFormat == 0 {
 		conf.TargetFormat = i.Format
@@ -481,7 +480,7 @@ func (i *imageResource) getImageMetaCacheTargetPath() string {
 	df := i.getResourcePaths()
 	p1, _ := paths.FileAndExt(df.File)
 	h := i.hash()
-	idStr := identity.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
+	idStr := hashing.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
 	df.File = fmt.Sprintf("%s_%s.json", p1, idStr)
 	return df.TargetPath()
 }
@@ -491,36 +490,16 @@ func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) interna
 	if conf.TargetFormat != i.Format {
 		p2 = conf.TargetFormat.DefaultExtension()
 	}
-
-	h := i.hash()
-	idStr := fmt.Sprintf("_hu%s_%d", h, i.size())
-
-	// Do not change for no good reason.
-	const md5Threshold = 100
-
-	key := conf.GetKey(i.Format)
-
-	// It is useful to have the key in clear text, but when nesting transforms, it
-	// can easily be too long to read, and maybe even too long
-	// for the different OSes to handle.
-	if len(p1)+len(idStr)+len(p2) > md5Threshold {
-		key = helpers.MD5String(p1 + key + p2)
-		huIdx := strings.Index(p1, "_hu")
-		if huIdx != -1 {
-			p1 = p1[:huIdx]
-		} else {
-			// This started out as a very long file name. Making it even longer
-			// could melt ice in the Arctic.
-			p1 = ""
-		}
-	} else if strings.Contains(p1, idStr) {
-		// On scaling an already scaled image, we get the file info from the original.
-		// Repeating the same info in the filename makes it stuttery for no good reason.
-		idStr = ""
+	const prefix = "_hu"
+	huIdx := strings.LastIndex(p1, prefix)
+	incomingID := "i"
+	if huIdx > -1 {
+		incomingID = p1[huIdx+len(prefix):]
+		p1 = p1[:huIdx]
 	}
-
+	hash := hashing.HashUint64(incomingID, i.hash(), conf.GetKey(i.Format))
 	rp := i.getResourcePaths()
-	rp.File = fmt.Sprintf("%s%s_%s%s", p1, idStr, key, p2)
+	rp.File = fmt.Sprintf("%s%s%d%s", p1, prefix, hash, p2)
 
 	return rp
 }
diff --git a/resources/internal/key.go b/resources/internal/key.go
index 2b95871aadc..b0ac9703f72 100644
--- a/resources/internal/key.go
+++ b/resources/internal/key.go
@@ -13,7 +13,7 @@
 
 package internal
 
-import "github.com/gohugoio/hugo/identity"
+import "github.com/gohugoio/hugo/common/hashing"
 
 // ResourceTransformationKey are provided by the different transformation implementations.
 // It identifies the transformation (name) and its configuration (elements).
@@ -38,5 +38,5 @@ func (k ResourceTransformationKey) Value() string {
 		return k.Name
 	}
 
-	return k.Name + "_" + identity.HashString(k.elements...)
+	return k.Name + "_" + hashing.HashString(k.elements...)
 }
diff --git a/resources/resource.go b/resources/resource.go
index 8fade941ae6..cc7008e5a88 100644
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -26,6 +26,7 @@ import (
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/resources/internal"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/herrors"
 	"github.com/gohugoio/hugo/common/paths"
 
@@ -307,7 +308,7 @@ type fileInfo interface {
 }
 
 type hashProvider interface {
-	hash() string
+	hash() uint64
 }
 
 var _ resource.StaleInfo = (*StaleValue[any])(nil)
@@ -403,7 +404,7 @@ func (l *genericResource) size() int64 {
 	return l.h.size
 }
 
-func (l *genericResource) hash() string {
+func (l *genericResource) hash() uint64 {
 	if err := l.h.init(l); err != nil {
 		panic(err)
 	}
@@ -628,7 +629,7 @@ type targetPather interface {
 }
 
 type resourceHash struct {
-	value    string
+	value    uint64
 	size     int64
 	initOnce sync.Once
 }
@@ -636,7 +637,7 @@ type resourceHash struct {
 func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
 	var initErr error
 	r.initOnce.Do(func() {
-		var hash string
+		var hash uint64
 		var size int64
 		f, err := l.ReadSeekCloser()
 		if err != nil {
@@ -644,7 +645,7 @@ func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
 			return
 		}
 		defer f.Close()
-		hash, size, err = helpers.MD5FromReaderFast(f)
+		hash, size, err = hashImage(f)
 		if err != nil {
 			initErr = fmt.Errorf("failed to calculate hash: %w", err)
 			return
@@ -655,3 +656,7 @@ func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
 
 	return initErr
 }
+
+func hashImage(r io.ReadSeeker) (uint64, int64, error) {
+	return hashing.XXHashFromReader(r)
+}
diff --git a/resources/resource_factories/create/create.go b/resources/resource_factories/create/create.go
index 35a1fb59df8..2d868bd1504 100644
--- a/resources/resource_factories/create/create.go
+++ b/resources/resource_factories/create/create.go
@@ -26,7 +26,6 @@ import (
 	"github.com/bep/logg"
 	"github.com/gohugoio/httpcache"
 	hhttpcache "github.com/gohugoio/hugo/cache/httpcache"
-	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/hugofs/glob"
 	"github.com/gohugoio/hugo/identity"
 
@@ -34,6 +33,7 @@ import (
 
 	"github.com/gohugoio/hugo/cache/dynacache"
 	"github.com/gohugoio/hugo/cache/filecache"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/hcontext"
 	"github.com/gohugoio/hugo/common/hugio"
 	"github.com/gohugoio/hugo/common/tasks"
@@ -226,7 +226,7 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
 // TODO(bep) see #10912; we currently emit a warning for this config scenario.
 func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
 	targetPath = path.Clean(targetPath)
-	key := dynacache.CleanKey(targetPath) + helpers.MD5String(content)
+	key := dynacache.CleanKey(targetPath) + hashing.MD5FromStringHexEncoded(content)
 	r, err := c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
 		return c.rs.NewResource(
 			resources.ResourceSourceDescriptor{
diff --git a/resources/resource_factories/create/remote.go b/resources/resource_factories/create/remote.go
index 5f4879dfe7a..606110d57fd 100644
--- a/resources/resource_factories/create/remote.go
+++ b/resources/resource_factories/create/remote.go
@@ -29,6 +29,7 @@ import (
 	gmaps "maps"
 
 	"github.com/gohugoio/httpcache"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/hugio"
 	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/common/maps"
@@ -310,10 +311,10 @@ func (c *Client) validateFromRemoteArgs(uri string, options fromRemoteOptions) e
 func remoteResourceKeys(uri string, optionsm map[string]any) (string, string) {
 	var userKey string
 	if key, k, found := maps.LookupEqualFold(optionsm, "key"); found {
-		userKey = identity.HashString(key)
+		userKey = hashing.HashString(key)
 		delete(optionsm, k)
 	}
-	optionsKey := identity.HashString(uri, optionsm)
+	optionsKey := hashing.HashString(uri, optionsm)
 	if userKey == "" {
 		userKey = optionsKey
 	}
diff --git a/resources/transform.go b/resources/transform.go
index b71c026b983..336495e6d07 100644
--- a/resources/transform.go
+++ b/resources/transform.go
@@ -24,6 +24,7 @@ import (
 	"sync"
 
 	"github.com/gohugoio/hugo/common/constants"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/paths"
 	"github.com/gohugoio/hugo/identity"
 
@@ -36,7 +37,6 @@ import (
 	"github.com/gohugoio/hugo/common/herrors"
 	"github.com/gohugoio/hugo/common/hugio"
 	"github.com/gohugoio/hugo/common/maps"
-	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/resources/internal"
 	"github.com/gohugoio/hugo/resources/resource"
 
@@ -397,7 +397,7 @@ func (r *resourceAdapter) TransformationKey() string {
 	for _, tr := range r.transformations {
 		key = key + "_" + tr.Key().Value()
 	}
-	return r.spec.ResourceCache.cleanKey(r.target.Key()) + "_" + helpers.MD5String(key)
+	return r.spec.ResourceCache.cleanKey(r.target.Key()) + "_" + hashing.MD5FromStringHexEncoded(key)
 }
 
 func (r *resourceAdapter) getOrTransform(publish, setContent bool) error {
diff --git a/source/fileInfo.go b/source/fileInfo.go
index 3a95a0593df..bece86fa9f8 100644
--- a/source/fileInfo.go
+++ b/source/fileInfo.go
@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/bep/gitmap"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/hugo"
 	"github.com/gohugoio/hugo/common/paths"
 	"github.com/gohugoio/hugo/media"
@@ -26,8 +27,6 @@ import (
 	"github.com/gohugoio/hugo/common/hugio"
 
 	"github.com/gohugoio/hugo/hugofs"
-
-	"github.com/gohugoio/hugo/helpers"
 )
 
 // File describes a source file.
@@ -125,7 +124,7 @@ func (fi *File) IsZero() bool {
 // in some cases that is slightly expensive to construct.
 func (fi *File) init() {
 	fi.lazyInit.Do(func() {
-		fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.Path()))
+		fi.uniqueID = hashing.MD5FromStringHexEncoded(filepath.ToSlash(fi.Path()))
 	})
 }
 
diff --git a/tpl/collections/reflect_helpers.go b/tpl/collections/reflect_helpers.go
index 4687acdde57..4b222be15ca 100644
--- a/tpl/collections/reflect_helpers.go
+++ b/tpl/collections/reflect_helpers.go
@@ -18,8 +18,8 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/types"
-	"github.com/mitchellh/hashstructure"
 )
 
 var (
@@ -47,14 +47,9 @@ func numberToFloat(v reflect.Value) (float64, error) {
 // to make them comparable
 func normalize(v reflect.Value) any {
 	k := v.Kind()
-
 	switch {
 	case !v.Type().Comparable():
-		h, err := hashstructure.Hash(v.Interface(), nil)
-		if err != nil {
-			panic(err)
-		}
-		return h
+		return hashing.HashUint64(v.Interface())
 	case isNumber(k):
 		f, err := numberToFloat(v)
 		if err == nil {
diff --git a/tpl/data/resources.go b/tpl/data/resources.go
index 3a3701d6087..9e06c0cce21 100644
--- a/tpl/data/resources.go
+++ b/tpl/data/resources.go
@@ -23,7 +23,7 @@ import (
 	"time"
 
 	"github.com/gohugoio/hugo/cache/filecache"
-	"github.com/gohugoio/hugo/helpers"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/spf13/afero"
 )
 
@@ -44,7 +44,7 @@ func (ns *Namespace) getRemote(cache *filecache.Cache, unmarshal func([]byte) (b
 
 	var headers bytes.Buffer
 	req.Header.Write(&headers)
-	id := helpers.MD5String(url + headers.String())
+	id := hashing.MD5FromStringHexEncoded(url + headers.String())
 	var handled bool
 	var retry bool
 
diff --git a/tpl/hash/hash.go b/tpl/hash/hash.go
index d4a80b342a8..00df4e3cdf0 100644
--- a/tpl/hash/hash.go
+++ b/tpl/hash/hash.go
@@ -16,10 +16,9 @@ package hash
 
 import (
 	"context"
-	"encoding/hex"
 	"hash/fnv"
 
-	"github.com/cespare/xxhash/v2"
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/tpl/internal"
 	"github.com/spf13/cast"
@@ -51,14 +50,7 @@ func (ns *Namespace) XxHash(v any) (string, error) {
 		return "", err
 	}
 
-	hasher := xxhash.New()
-
-	_, err = hasher.WriteString(conv)
-	if err != nil {
-		return "", err
-	}
-	hash := hasher.Sum(nil)
-	return hex.EncodeToString(hash), nil
+	return hashing.XxHashFromStringHexEncoded(conv), nil
 }
 
 const name = "hash"
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 8e36e21b92d..8de312fd8da 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/bep/lazycache"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/identity"
 
 	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
@@ -50,7 +51,7 @@ func (k partialCacheKey) Key() string {
 	if k.Variants == nil {
 		return k.Name
 	}
-	return identity.HashString(append([]any{k.Name}, k.Variants...)...)
+	return hashing.HashString(append([]any{k.Name}, k.Variants...)...)
 }
 
 func (k partialCacheKey) templateName() string {
diff --git a/tpl/templates/templates.go b/tpl/templates/templates.go
index 91e96ed8e60..98b4b4c3873 100644
--- a/tpl/templates/templates.go
+++ b/tpl/templates/templates.go
@@ -20,8 +20,8 @@ import (
 	"strconv"
 	"sync/atomic"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/deps"
-	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/tpl"
 	"github.com/mitchellh/mapstructure"
 )
@@ -83,7 +83,7 @@ func (ns *Namespace) DoDefer(ctx context.Context, id string, optsv any) string {
 	templateName := id
 	var key string
 	if opts.Key != "" {
-		key = helpers.MD5String(opts.Key)
+		key = hashing.MD5FromStringHexEncoded(opts.Key)
 	} else {
 		key = strconv.FormatUint(defferedIDCounter.Add(1), 10)
 	}
diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
index 9eee64235d4..f95335779f8 100644
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -18,12 +18,12 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/gohugoio/hugo/helpers"
 	htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
 	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
 
 	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/tpl"
 	"github.com/mitchellh/mapstructure"
@@ -254,7 +254,7 @@ func (c *templateContext) handleDefer(withNode *parse.WithNode) {
 		c.err = errors.New("resources.PostProcess cannot be used in a deferred template")
 		return
 	}
-	innerHash := helpers.XxHashString(s)
+	innerHash := hashing.XxHashFromStringHexEncoded(s)
 	deferredID := tpl.HugoDeferredTemplatePrefix + innerHash
 
 	c.deferNodes[deferredID] = inner
diff --git a/tpl/transform/unmarshal.go b/tpl/transform/unmarshal.go
index dc9029c8d67..23b99d91fa2 100644
--- a/tpl/transform/unmarshal.go
+++ b/tpl/transform/unmarshal.go
@@ -22,11 +22,11 @@ import (
 	"github.com/gohugoio/hugo/resources"
 	"github.com/gohugoio/hugo/resources/resource"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/types"
 
 	"github.com/mitchellh/mapstructure"
 
-	"github.com/gohugoio/hugo/helpers"
 	"github.com/gohugoio/hugo/parser/metadecoders"
 
 	"github.com/spf13/cast"
@@ -117,7 +117,7 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
 		return nil, errors.New("no data to transform")
 	}
 
-	key := helpers.MD5String(dataStr)
+	key := hashing.MD5FromStringHexEncoded(dataStr)
 
 	v, err := ns.cache.GetOrCreate(key, func(string) (*resources.StaleValue[any], error) {
 		f := decoder.FormatFromContentString(dataStr)


Test patch:
diff --git a/common/hashing/hashing_test.go b/common/hashing/hashing_test.go
new file mode 100644
index 00000000000..09286c0354d
--- /dev/null
+++ b/common/hashing/hashing_test.go
@@ -0,0 +1,144 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hashing
+
+import (
+	"fmt"
+	"math"
+	"strings"
+	"sync"
+	"testing"
+
+	qt "github.com/frankban/quicktest"
+)
+
+func TestXxHashFromReader(t *testing.T) {
+	c := qt.New(t)
+	s := "Hello World"
+	r := strings.NewReader(s)
+	got, size, err := XXHashFromReader(r)
+	c.Assert(err, qt.IsNil)
+	c.Assert(size, qt.Equals, int64(len(s)))
+	c.Assert(got, qt.Equals, uint64(7148569436472236994))
+}
+
+func TestXxHashFromReaderPara(t *testing.T) {
+	c := qt.New(t)
+
+	var wg sync.WaitGroup
+	for i := 0; i < 10; i++ {
+		i := i
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for j := 0; j < 100; j++ {
+				s := strings.Repeat("Hello ", i+j+1*42)
+				r := strings.NewReader(s)
+				got, size, err := XXHashFromReader(r)
+				c.Assert(size, qt.Equals, int64(len(s)))
+				c.Assert(err, qt.IsNil)
+				expect, _ := XXHashFromString(s)
+				c.Assert(got, qt.Equals, expect)
+			}
+		}()
+	}
+
+	wg.Wait()
+}
+
+func TestXxHashFromString(t *testing.T) {
+	c := qt.New(t)
+	s := "Hello World"
+	got, err := XXHashFromString(s)
+	c.Assert(err, qt.IsNil)
+	c.Assert(got, qt.Equals, uint64(7148569436472236994))
+}
+
+func TestXxHashFromStringHexEncoded(t *testing.T) {
+	c := qt.New(t)
+	s := "The quick brown fox jumps over the lazy dog"
+	got := XxHashFromStringHexEncoded(s)
+	// Facit: https://asecuritysite.com/encryption/xxhash?val=The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog
+	c.Assert(got, qt.Equals, "0b242d361fda71bc")
+}
+
+func BenchmarkXXHashFromReader(b *testing.B) {
+	r := strings.NewReader("Hello World")
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		XXHashFromReader(r)
+		r.Seek(0, 0)
+	}
+}
+
+func BenchmarkXXHashFromString(b *testing.B) {
+	s := "Hello World"
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		XXHashFromString(s)
+	}
+}
+
+func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
+	s := "The quick brown fox jumps over the lazy dog"
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		XxHashFromStringHexEncoded(s)
+	}
+}
+
+func TestHashString(t *testing.T) {
+	c := qt.New(t)
+
+	c.Assert(HashString("a", "b"), qt.Equals, "3176555414984061461")
+	c.Assert(HashString("ab"), qt.Equals, "7347350983217793633")
+
+	var vals []any = []any{"a", "b", tstKeyer{"c"}}
+
+	c.Assert(HashString(vals...), qt.Equals, "4438730547989914315")
+	c.Assert(vals[2], qt.Equals, tstKeyer{"c"})
+}
+
+type tstKeyer struct {
+	key string
+}
+
+func (t tstKeyer) Key() string {
+	return t.key
+}
+
+func (t tstKeyer) String() string {
+	return "key: " + t.key
+}
+
+func BenchmarkHashString(b *testing.B) {
+	word := " hello "
+
+	var tests []string
+
+	for i := 1; i <= 5; i++ {
+		sentence := strings.Repeat(word, int(math.Pow(4, float64(i))))
+		tests = append(tests, sentence)
+	}
+
+	b.ResetTimer()
+
+	for _, test := range tests {
+		b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				HashString(test)
+			}
+		})
+	}
+}
diff --git a/config/namespace_test.go b/config/namespace_test.go
index 9bd23e08e57..5eacdeac7dd 100644
--- a/config/namespace_test.go
+++ b/config/namespace_test.go
@@ -43,7 +43,7 @@ func TestNamespace(t *testing.T) {
 	c.Assert(err, qt.IsNil)
 	c.Assert(ns, qt.Not(qt.IsNil))
 	c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]interface{}{"foo": "bar"})
-	c.Assert(ns.SourceHash, qt.Equals, "14368731254619220105")
+	c.Assert(ns.SourceHash, qt.Equals, "1450430416588600409")
 	c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
 	c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
 }
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 7bef1b7768b..686e95ded74 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -14,7 +14,6 @@
 package helpers_test
 
 import (
-	"fmt"
 	"reflect"
 	"strings"
 	"testing"
@@ -22,7 +21,6 @@ import (
 	"github.com/gohugoio/hugo/helpers"
 
 	qt "github.com/frankban/quicktest"
-	"github.com/spf13/afero"
 )
 
 func TestResolveMarkup(t *testing.T) {
@@ -256,93 +254,6 @@ func TestUniqueStringsSorted(t *testing.T) {
 	c.Assert(helpers.UniqueStringsSorted(nil), qt.IsNil)
 }
 
-func TestFastMD5FromFile(t *testing.T) {
-	fs := afero.NewMemMapFs()
-
-	if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0o777); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0o777); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0o777); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0o777); err != nil {
-		t.Fatal(err)
-	}
-
-	c := qt.New(t)
-
-	sf1, err := fs.Open("small.txt")
-	c.Assert(err, qt.IsNil)
-	sf2, err := fs.Open("small2.txt")
-	c.Assert(err, qt.IsNil)
-
-	bf1, err := fs.Open("bigger.txt")
-	c.Assert(err, qt.IsNil)
-	bf2, err := fs.Open("bigger2.txt")
-	c.Assert(err, qt.IsNil)
-
-	defer sf1.Close()
-	defer sf2.Close()
-	defer bf1.Close()
-	defer bf2.Close()
-
-	m1, _, err := helpers.MD5FromReaderFast(sf1)
-	c.Assert(err, qt.IsNil)
-	c.Assert(m1, qt.Equals, "e9c8989b64b71a88b4efb66ad05eea96")
-
-	m2, _, err := helpers.MD5FromReaderFast(sf2)
-	c.Assert(err, qt.IsNil)
-	c.Assert(m2, qt.Not(qt.Equals), m1)
-
-	m3, _, err := helpers.MD5FromReaderFast(bf1)
-	c.Assert(err, qt.IsNil)
-	c.Assert(m3, qt.Not(qt.Equals), m2)
-
-	m4, _, err := helpers.MD5FromReaderFast(bf2)
-	c.Assert(err, qt.IsNil)
-	c.Assert(m4, qt.Not(qt.Equals), m3)
-
-	m5, err := helpers.MD5FromReader(bf2)
-	c.Assert(err, qt.IsNil)
-	c.Assert(m5, qt.Not(qt.Equals), m4)
-}
-
-func BenchmarkMD5FromFileFast(b *testing.B) {
-	fs := afero.NewMemMapFs()
-
-	for _, full := range []bool{false, true} {
-		b.Run(fmt.Sprintf("full=%t", full), func(b *testing.B) {
-			for i := 0; i < b.N; i++ {
-				b.StopTimer()
-				if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0o777); err != nil {
-					b.Fatal(err)
-				}
-				f, err := fs.Open("file.txt")
-				if err != nil {
-					b.Fatal(err)
-				}
-				b.StartTimer()
-				if full {
-					if _, err := helpers.MD5FromReader(f); err != nil {
-						b.Fatal(err)
-					}
-				} else {
-					if _, _, err := helpers.MD5FromReaderFast(f); err != nil {
-						b.Fatal(err)
-					}
-				}
-				f.Close()
-			}
-		})
-	}
-}
-
 func BenchmarkUniqueStrings(b *testing.B) {
 	input := []string{"a", "b", "d", "e", "d", "h", "a", "i"}
 
diff --git a/hugolib/hugo_sites_multihost_test.go b/hugolib/hugo_sites_multihost_test.go
index 004da351dbc..ce3944a3848 100644
--- a/hugolib/hugo_sites_multihost_test.go
+++ b/hugolib/hugo_sites_multihost_test.go
@@ -203,9 +203,9 @@ title: mybundle-en
 	b.AssertFileExists("public/de/mybundle/pixel.png", true)
 	b.AssertFileExists("public/en/mybundle/pixel.png", true)
 
-	b.AssertFileExists("public/de/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
+	b.AssertFileExists("public/de/mybundle/pixel_hu8581513846771248023.png", true)
 	// failing test below
-	b.AssertFileExists("public/en/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
+	b.AssertFileExists("public/en/mybundle/pixel_hu8581513846771248023.png", true)
 }
 
 func TestMultihostResourceOneBaseURLWithSuPath(t *testing.T) {
diff --git a/hugolib/image_test.go b/hugolib/image_test.go
index d5800851222..7dcd9fc26ab 100644
--- a/hugolib/image_test.go
+++ b/hugolib/image_test.go
@@ -72,22 +72,20 @@ SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Ex
 
 	b.Build(BuildCfg{})
 
-	b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
-	b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
-	b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
-	b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
+	b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu13235715490294913361.jpg/200/Lat: 36.59744166666667")
+	b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /bundle/sunset_hu13235715490294913361.jpg/200/Lat: 36.59744166666667")
+	b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu1573057890424052540.jpg/123/Lat: 36.59744166666667")
+	b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu1573057890424052540.jpg/123/Lat: 36.59744166666667")
 
-	b.AssertImage(200, 200, "public/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
+	b.AssertImage(200, 200, "public/bundle/sunset_hu13235715490294913361.jpg")
 
 	// Check the file cache
-	b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
+	b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu13235715490294913361.jpg")
 
-	b.AssertFileContent("resources/_gen/images/bundle/sunset_9750822043026343402.json",
+	b.AssertFileContent("resources/_gen/images/bundle/sunset_17710516992648092201.json",
 		"FocalLengthIn35mmFormat|uint16", "PENTAX")
 
-	b.AssertImage(123, 234, "resources/_gen/images/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
-
-	b.AssertFileContent("resources/_gen/images/images/sunset_9750822043026343402.json",
+	b.AssertFileContent("resources/_gen/images/images/sunset_17710516992648092201.json",
 		"FocalLengthIn35mmFormat|uint16", "PENTAX")
 
 	b.AssertNoDuplicateWrites()
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index 8919c246cf5..a1eb4343028 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -23,13 +23,13 @@ import (
 	"time"
 
 	"github.com/bep/clocks"
-	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/asciidocext"
 	"github.com/gohugoio/hugo/markup/rst"
 	"github.com/gohugoio/hugo/tpl"
 
 	"github.com/gohugoio/hugo/config"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/htime"
 	"github.com/gohugoio/hugo/common/loggers"
 
@@ -2040,8 +2040,8 @@ title: "p2"
 
 	b.Assert(p1, qt.Not(qt.Equals), p2)
 
-	b.Assert(identity.HashString(p1), qt.Not(qt.Equals), identity.HashString(p2))
-	b.Assert(identity.HashString(sites[0]), qt.Not(qt.Equals), identity.HashString(sites[1]))
+	b.Assert(hashing.HashString(p1), qt.Not(qt.Equals), hashing.HashString(p2))
+	b.Assert(hashing.HashString(sites[0]), qt.Not(qt.Equals), hashing.HashString(sites[1]))
 }
 
 // Issue #11243
diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go
index 302201a1f8e..e4da75a7240 100644
--- a/hugolib/pagebundler_test.go
+++ b/hugolib/pagebundler_test.go
@@ -19,12 +19,11 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/loggers"
 
 	"github.com/gohugoio/hugo/config"
 
-	"github.com/gohugoio/hugo/helpers"
-
 	"github.com/gohugoio/hugo/hugofs"
 
 	"github.com/gohugoio/hugo/resources/kinds"
@@ -701,13 +700,13 @@ bundle min min key: {{ $jsonMinMin.Key }}
 			b.AssertFileContent(index, fmt.Sprintf("data content unmarshaled: v%d", i))
 			b.AssertFileContent(index, fmt.Sprintf("data assets content unmarshaled: v%d", i))
 
-			md5Asset := helpers.MD5String(fmt.Sprintf(`vdata: v%d`, i))
+			md5Asset := hashing.MD5FromStringHexEncoded(fmt.Sprintf(`vdata: v%d`, i))
 			b.AssertFileContent(index, fmt.Sprintf("assets fingerprinted: /data%d/data.%s.yaml", i, md5Asset))
 
 			// The original is not used, make sure it's not published.
 			b.Assert(b.CheckExists(fmt.Sprintf("public/data%d/data.yaml", i)), qt.Equals, false)
 
-			md5Bundle := helpers.MD5String(fmt.Sprintf(`data: v%d`, i))
+			md5Bundle := hashing.MD5FromStringHexEncoded(fmt.Sprintf(`data: v%d`, i))
 			b.AssertFileContent(index, fmt.Sprintf("bundle fingerprinted: /bundle%d/data.%s.yaml", i, md5Bundle))
 
 			b.AssertFileContent(index,
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
index 709d6db7b83..a4cf4dcff24 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
@@ -119,7 +119,7 @@ docs/p1/sub/mymixcasetext2.txt
 		"RelPermalink: /docs/p1/sub/mymixcasetext2.txt|Name: sub/mymixcasetext2.txt|",
 		"RelPermalink: /mydata.yaml|Name: sub/data1.yaml|Title: Sub data|Params: map[]|",
 		"Featured Image: /a/pixel.png|featured.png|",
-		"Resized Featured Image: /a/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_10x10_resize_box_3.png|10|",
+		"Resized Featured Image: /a/pixel_hu16809842526914527184.png|10|",
 		// Resource from string
 		"RelPermalink: /docs/p1/mytext.txt|Name: textresource|Title: My Text Resource|Params: map[param1:param1v]|",
 		// Dates
diff --git a/hugolib/resource_chain_test.go b/hugolib/resource_chain_test.go
index 1365db72c67..7683bb5fd7c 100644
--- a/hugolib/resource_chain_test.go
+++ b/hugolib/resource_chain_test.go
@@ -27,8 +27,8 @@ import (
 
 	qt "github.com/frankban/quicktest"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/loggers"
-	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
 )
 
@@ -106,12 +106,12 @@ FAILED REMOTE ERROR DETAILS CONTENT: {{ with $failedImg.Err }}|{{ . }}|{{ with .
 		b.AssertFileContent("public/index.html",
 			fmt.Sprintf(`
 SUNSET: /images/sunset.jpg|/images/sunset.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587
-FIT: /images/sunset.jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200
+FIT: /images/sunset.jpg|/images/sunset_hu15210517121918042184.jpg|200
 CSS integrity Data first: sha256-od9YaHw8nMOL8mUy97Sy8sKwMV3N4hI3aVmZXATxH&#43;8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css
 CSS integrity Data last:  /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03&#43;ZmKY/1t2GCOjEEOXj2x2qow94vCc7o=
 
 SUNSET REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587
-FIT REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200
+FIT REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s_hu15210517121918042184.jpg|200
 REMOTE NOT FOUND: OK
 LOCAL NOT FOUND: OK
 PRINT PROTOCOL ERROR DETAILS: Err: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher"||
@@ -119,7 +119,7 @@ FAILED REMOTE ERROR DETAILS CONTENT: |failed to fetch remote resource: Not Imple
 |StatusCode: 501|ContentLength: 16|ContentType: text/plain; charset=utf-8|
 
 
-`, identity.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
+`, hashing.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
 
 		b.AssertFileContent("public/styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css", "body{background-color:#add8e6}")
 		b.AssertFileContent("public//styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css", "body{background-color:orange}")
diff --git a/resources/image_extended_test.go b/resources/image_extended_test.go
index 2f1f4f4a487..e570c4a3d24 100644
--- a/resources/image_extended_test.go
+++ b/resources/image_extended_test.go
@@ -42,6 +42,6 @@ func TestImageResizeWebP(t *testing.T) {
 	resized, err := image.Resize("123x")
 	c.Assert(err, qt.IsNil)
 	c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
-	c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunrise_hu6ad68bcbae1b79cbc2f6b451894deaf6_95652_123x0_resize_q68_h2_linear_2.webp")
+	c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunrise_hu544374262273649331.webp")
 	c.Assert(resized.Width(), qt.Equals, 123)
 }
diff --git a/resources/image_test.go b/resources/image_test.go
index 0a53b41177b..5cebba83797 100644
--- a/resources/image_test.go
+++ b/resources/image_test.go
@@ -33,14 +33,13 @@ import (
 	"github.com/gohugoio/hugo/htesting"
 	"github.com/gohugoio/hugo/resources/images/webp"
 
+	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/common/paths"
 
 	"github.com/spf13/afero"
 
 	"github.com/disintegration/gift"
 
-	"github.com/gohugoio/hugo/helpers"
-
 	"github.com/gohugoio/hugo/media"
 	"github.com/gohugoio/hugo/resources/images"
 	"github.com/google/go-cmp/cmp"
@@ -124,28 +123,28 @@ func TestImageTransformBasic(t *testing.T) {
 	assertWidthHeight(resizedAndRotated, 125, 200)
 
 	assertWidthHeight(resized, 300, 200)
-	c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_resize_q68_linear.jpg")
+	c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu2082030801149749592.jpg")
 
 	fitted, err := resized.Fit("50x50")
 	c.Assert(err, qt.IsNil)
-	c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_625708021e2bb281c9f1002f88e4753f.jpg")
+	c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu16263619592447877226.jpg")
 	assertWidthHeight(fitted, 50, 33)
 
 	// Check the MD5 key threshold
 	fittedAgain, _ := fitted.Fit("10x20")
 	fittedAgain, err = fittedAgain.Fit("10x20")
 	c.Assert(err, qt.IsNil)
-	c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f65ba24dc2b7fba0f56d7f104519157.jpg")
+	c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu847809310637164306.jpg")
 	assertWidthHeight(fittedAgain, 10, 7)
 
 	filled, err := image.Fill("200x100 bottomLeft")
 	c.Assert(err, qt.IsNil)
-	c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_bottomleft.jpg")
+	c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu18289448341423092707.jpg")
 	assertWidthHeight(filled, 200, 100)
 
 	smart, err := image.Fill("200x100 smart")
 	c.Assert(err, qt.IsNil)
-	c.Assert(smart.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_smart%d.jpg", 1))
+	c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu11649371610839769766.jpg")
 	assertWidthHeight(smart, 200, 100)
 
 	// Check cache
@@ -155,12 +154,12 @@ func TestImageTransformBasic(t *testing.T) {
 
 	cropped, err := image.Crop("300x300 topRight")
 	c.Assert(err, qt.IsNil)
-	c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x300_crop_q68_linear_topright.jpg")
+	c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu2242042514052853140.jpg")
 	assertWidthHeight(cropped, 300, 300)
 
 	smartcropped, err := image.Crop("200x200 smart")
 	c.Assert(err, qt.IsNil)
-	c.Assert(smartcropped.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_crop_q68_linear_smart%d.jpg", 1))
+	c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu12983255101170993571.jpg")
 	assertWidthHeight(smartcropped, 200, 200)
 
 	// Check cache
@@ -227,7 +226,7 @@ func TestImageTransformFormat(t *testing.T) {
 
 	imagePng, err := image.Resize("450x png")
 	c.Assert(err, qt.IsNil)
-	c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_450x0_resize_linear.png")
+	c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu11737890885216583918.png")
 	c.Assert(imagePng.ResourceType(), qt.Equals, "image")
 	assertExtWidthHeight(imagePng, ".png", 450, 281)
 	c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg")
@@ -235,7 +234,7 @@ func TestImageTransformFormat(t *testing.T) {
 
 	imageGif, err := image.Resize("225x gif")
 	c.Assert(err, qt.IsNil)
-	c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_225x0_resize_linear.gif")
+	c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu1431827106749674475.gif")
 	c.Assert(imageGif.ResourceType(), qt.Equals, "image")
 	assertExtWidthHeight(imageGif, ".gif", 225, 141)
 	c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg")
@@ -258,7 +257,7 @@ func TestImagePermalinkPublishOrder(t *testing.T) {
 			}()
 
 			check1 := func(img images.ImageResource) {
-				resizedLink := "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x50_resize_q75_box.jpg"
+				resizedLink := "/a/sunset_hu7919355342577096259.jpg"
 				c.Assert(img.RelPermalink(), qt.Equals, resizedLink)
 				assertImageFile(c, spec.PublishFs, resizedLink, 100, 50)
 			}
@@ -299,12 +298,12 @@ func TestImageBugs(t *testing.T) {
 		c.Assert(err, qt.IsNil)
 		c.Assert(resized, qt.Not(qt.IsNil))
 		c.Assert(resized.Width(), qt.Equals, 200)
-		c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_65b757a6e14debeae720fe8831f0a9bc.jpg")
+		c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu9514381480012510326.jpg")
 		resized, err = resized.Resize("100x")
 		c.Assert(err, qt.IsNil)
 		c.Assert(resized, qt.Not(qt.IsNil))
 		c.Assert(resized.Width(), qt.Equals, 100)
-		c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c876768085288f41211f768147ba2647.jpg")
+		c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu1776700126481066216.jpg")
 	})
 
 	// Issue #6137
@@ -402,7 +401,7 @@ func TestImageResize8BitPNG(t *testing.T) {
 	resized, err := image.Resize("800x")
 	c.Assert(err, qt.IsNil)
 	c.Assert(resized.MediaType().Type, qt.Equals, "image/png")
-	c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_3.png")
+	c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu8582372628235034388.png")
 	c.Assert(resized.Width(), qt.Equals, 800)
 }
 
@@ -815,10 +814,10 @@ func assetGoldenDirs(c *qt.C, dir1, dir2 string) {
 
 			if !goldenEqual(nrgba1, nrgba2) {
 				switch fi1.Name() {
-				case "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png",
-					"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png",
-					"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png",
-					"giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_200x0_resize_box_1.gif":
+				case "giphy_hu13007323561585908901.gif",
+					"gohugoio8_hu12690451569630232821.png",
+					"gohugoio8_hu1619987041333606118.png",
+					"gohugoio8_hu18164141965527013334.png":
 					c.Log("expectedly differs from golden due to dithering:", fi1.Name())
 				default:
 					c.Errorf("resulting image differs from golden: %s", fi1.Name())
@@ -835,9 +834,9 @@ func assetGoldenDirs(c *qt.C, dir1, dir2 string) {
 			_, err = f2.Seek(0, 0)
 			c.Assert(err, qt.IsNil)
 
-			hash1, err := helpers.MD5FromReader(f1)
+			hash1, _, err := hashing.XXHashFromReader(f1)
 			c.Assert(err, qt.IsNil)
-			hash2, err := helpers.MD5FromReader(f2)
+			hash2, _, err := hashing.XXHashFromReader(f2)
 			c.Assert(err, qt.IsNil)
 
 			c.Assert(hash1, qt.Equals, hash2)
diff --git a/resources/images/filters_test.go b/resources/images/filters_test.go
index a8c5601d607..ce8ae9f5a7a 100644
--- a/resources/images/filters_test.go
+++ b/resources/images/filters_test.go
@@ -17,7 +17,7 @@ import (
 	"testing"
 
 	qt "github.com/frankban/quicktest"
-	"github.com/gohugoio/hugo/identity"
+	"github.com/gohugoio/hugo/common/hashing"
 )
 
 func TestFilterHash(t *testing.T) {
@@ -25,8 +25,8 @@ func TestFilterHash(t *testing.T) {
 
 	f := &Filters{}
 
-	c.Assert(identity.HashString(f.Grayscale()), qt.Equals, identity.HashString(f.Grayscale()))
-	c.Assert(identity.HashString(f.Grayscale()), qt.Not(qt.Equals), identity.HashString(f.Invert()))
-	c.Assert(identity.HashString(f.Gamma(32)), qt.Not(qt.Equals), identity.HashString(f.Gamma(33)))
-	c.Assert(identity.HashString(f.Gamma(32)), qt.Equals, identity.HashString(f.Gamma(32)))
+	c.Assert(hashing.HashString(f.Grayscale()), qt.Equals, hashing.HashString(f.Grayscale()))
+	c.Assert(hashing.HashString(f.Grayscale()), qt.Not(qt.Equals), hashing.HashString(f.Invert()))
+	c.Assert(hashing.HashString(f.Gamma(32)), qt.Not(qt.Equals), hashing.HashString(f.Gamma(33)))
+	c.Assert(hashing.HashString(f.Gamma(32)), qt.Equals, hashing.HashString(f.Gamma(32)))
 }
diff --git a/resources/internal/key_test.go b/resources/internal/key_test.go
index 38286333d4a..fcad7d7546e 100644
--- a/resources/internal/key_test.go
+++ b/resources/internal/key_test.go
@@ -32,5 +32,5 @@ func TestResourceTransformationKey(t *testing.T) {
 	key := NewResourceTransformationKey("testing",
 		testStruct{Name: "test", V1: int64(10), V2: int32(20), V3: 30, V4: uint64(40)})
 	c := qt.New(t)
-	c.Assert(key.Value(), qt.Equals, "testing_518996646957295636")
+	c.Assert(key.Value(), qt.Equals, "testing_4231238781487357822")
 }
diff --git a/resources/resource_factories/create/remote_test.go b/resources/resource_factories/create/remote_test.go
index 49d0b1541ae..29384510716 100644
--- a/resources/resource_factories/create/remote_test.go
+++ b/resources/resource_factories/create/remote_test.go
@@ -121,15 +121,16 @@ func TestRemoteResourceKeys(t *testing.T) {
 	c := qt.New(t)
 
 	check := func(uri string, optionsm map[string]any, expect1, expect2 string) {
+		c.Helper()
 		got1, got2 := remoteResourceKeys(uri, optionsm)
 		c.Assert(got1, qt.Equals, expect1)
 		c.Assert(got2, qt.Equals, expect2)
 	}
 
-	check("foo", nil, "5917621528921068675", "5917621528921068675")
-	check("foo", map[string]any{"bar": "baz"}, "7294498335241413323", "7294498335241413323")
-	check("foo", map[string]any{"key": "1234", "bar": "baz"}, "14904296279238663669", "7294498335241413323")
-	check("foo", map[string]any{"key": "12345", "bar": "baz"}, "12191037851845371770", "7294498335241413323")
-	check("asdf", map[string]any{"key": "1234", "bar": "asdf"}, "14904296279238663669", "3787889110563790121")
-	check("asdf", map[string]any{"key": "12345", "bar": "asdf"}, "12191037851845371770", "3787889110563790121")
+	check("foo", nil, "7763396052142361238", "7763396052142361238")
+	check("foo", map[string]any{"bar": "baz"}, "5783339285578751849", "5783339285578751849")
+	check("foo", map[string]any{"key": "1234", "bar": "baz"}, "15578353952571222948", "5783339285578751849")
+	check("foo", map[string]any{"key": "12345", "bar": "baz"}, "14335752410685132726", "5783339285578751849")
+	check("asdf", map[string]any{"key": "1234", "bar": "asdf"}, "15578353952571222948", "15615023578599429261")
+	check("asdf", map[string]any{"key": "12345", "bar": "asdf"}, "14335752410685132726", "15615023578599429261")
 }
diff --git a/identity/identityhash_test.go b/resources/resource_test.go
similarity index 53%
rename from identity/identityhash_test.go
rename to resources/resource_test.go
index 1ecaf761245..ccce8eff719 100644
--- a/identity/identityhash_test.go
+++ b/resources/resource_test.go
@@ -11,34 +11,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package identity
+package resources
 
 import (
+	"os"
 	"testing"
-
-	qt "github.com/frankban/quicktest"
 )
 
-func TestHashString(t *testing.T) {
-	c := qt.New(t)
-
-	c.Assert(HashString("a", "b"), qt.Equals, "2712570657419664240")
-	c.Assert(HashString("ab"), qt.Equals, "590647783936702392")
-
-	var vals []any = []any{"a", "b", tstKeyer{"c"}}
-
-	c.Assert(HashString(vals...), qt.Equals, "12599484872364427450")
-	c.Assert(vals[2], qt.Equals, tstKeyer{"c"})
-}
-
-type tstKeyer struct {
-	key string
-}
-
-func (t tstKeyer) Key() string {
-	return t.key
-}
-
-func (t tstKeyer) String() string {
-	return "key: " + t.key
+func BenchmarkHashImage(b *testing.B) {
+	f, err := os.Open("testdata/sunset.jpg")
+	if err != nil {
+		b.Fatal(err)
+	}
+	defer f.Close()
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, err := hashImage(f)
+		if err != nil {
+			b.Fatal(err)
+		}
+		f.Seek(0, 0)
+	}
 }
diff --git a/resources/resource_transformers/js/options_test.go b/resources/resource_transformers/js/options_test.go
index de20cbd05de..53aa9b6bbff 100644
--- a/resources/resource_transformers/js/options_test.go
+++ b/resources/resource_transformers/js/options_test.go
@@ -44,7 +44,7 @@ func TestOptionKey(t *testing.T) {
 
 	key := (&buildTransformation{optsm: opts}).Key()
 
-	c.Assert(key.Value(), qt.Equals, "jsbuild_7891849149754191852")
+	c.Assert(key.Value(), qt.Equals, "jsbuild_1533819657654811600")
 }
 
 func TestToBuildOptions(t *testing.T) {
diff --git a/resources/resources_integration_test.go b/resources/resources_integration_test.go
index ba580b4dce1..c26eb8fffab 100644
--- a/resources/resources_integration_test.go
+++ b/resources/resources_integration_test.go
@@ -27,6 +27,7 @@ func TestImageCache(t *testing.T) {
 
 	files := `
 -- config.toml --
+disableLiveReload = true
 baseURL = "https://example.org"
 -- content/mybundle/index.md --
 ---
@@ -61,9 +62,9 @@ anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $
 
 	assertImages := func() {
 		b.AssertFileContent("public/index.html", `
-		gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x2_resize_box_3.gif|}|1|2|image/gif|
-		bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x3_resize_box_3.bmp|}|2|3|image/bmp|
-		anigif: /mybundle/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_4x5_resize_box_1.gif|4|5|image/gif|
+		gif: /mybundle/pixel_hu14657638653019978294.gif|}|1|2|image/gif|
+		bmp: /mybundle/pixel_hu14705577916774115224.bmp|}|2|3|image/bmp|
+		anigif: /mybundle/giphy_hu3665406585348417395.gif|4|5|image/gif|
 		`)
 	}
 
@@ -159,9 +160,9 @@ resize 2|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType
 	b := hugolib.Test(t, files)
 
 	b.AssertFileContent("public/index.html",
-		"jpg|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_17010532266664966692.jpg|MediaType: image/jpeg|Width: 1|Height: 1|",
-		"resize 1|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
-		"resize 2|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
+		"jpg|RelPermalink: /images/pixel_hu13683954895608450100.jpg|MediaType: image/jpeg|Width: 1|Height: 1|",
+		"resize 1|RelPermalink: /images/pixel_hu3453403302435331853.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
+		"resize 2|RelPermalink: /images/pixel_hu3453403302435331853.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
 	)
 }
 
diff --git a/resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_200x0_resize_box_1.gif b/resources/testdata/golden/giphy_hu13007323561585908901.gif
similarity index 100%
rename from resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_200x0_resize_box_1.gif
rename to resources/testdata/golden/giphy_hu13007323561585908901.gif
diff --git a/resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_512x0_resize_box_1.gif b/resources/testdata/golden/giphy_hu15867760255711130632.gif
similarity index 100%
rename from resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_512x0_resize_box_1.gif
rename to resources/testdata/golden/giphy_hu15867760255711130632.gif
diff --git a/resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_100x0_resize_q75_bgffffff_box_1.jpg b/resources/testdata/golden/giphy_hu6128044492074974243.jpg
similarity index 100%
rename from resources/testdata/golden/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_100x0_resize_q75_bgffffff_box_1.jpg
rename to resources/testdata/golden/giphy_hu6128044492074974243.jpg
diff --git a/resources/testdata/golden/gohugoio-card_hu4d09f75255d3942fd4680641110a1a73_10820_220x0_resize_box_1.gif b/resources/testdata/golden/gohugoio-card_hu2987156907072306909.gif
similarity index 100%
rename from resources/testdata/golden/gohugoio-card_hu4d09f75255d3942fd4680641110a1a73_10820_220x0_resize_box_1.gif
rename to resources/testdata/golden/gohugoio-card_hu2987156907072306909.gif
diff --git a/resources/testdata/golden/gohugoio-card_hu4d09f75255d3942fd4680641110a1a73_10820_100x0_resize_box_1.gif b/resources/testdata/golden/gohugoio-card_hu5424444801046891674.gif
similarity index 100%
rename from resources/testdata/golden/gohugoio-card_hu4d09f75255d3942fd4680641110a1a73_10820_100x0_resize_box_1.gif
rename to resources/testdata/golden/gohugoio-card_hu5424444801046891674.gif
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9a8d95423df65a9c230a4cc88056c13a.png b/resources/testdata/golden/gohugoio24_hu10797171466856050177.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9a8d95423df65a9c230a4cc88056c13a.png
rename to resources/testdata/golden/gohugoio24_hu10797171466856050177.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a487ef4bea3dba1e1a84be5358cfef39.png b/resources/testdata/golden/gohugoio24_hu11107750106104480197.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a487ef4bea3dba1e1a84be5358cfef39.png
rename to resources/testdata/golden/gohugoio24_hu11107750106104480197.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_aeaaf23afe6fb4702bd3992426d0cad3.png b/resources/testdata/golden/gohugoio24_hu11329286024190801059.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_aeaaf23afe6fb4702bd3992426d0cad3.png
rename to resources/testdata/golden/gohugoio24_hu11329286024190801059.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_ee57777f148caaa6993972d9709fdf2d.png b/resources/testdata/golden/gohugoio24_hu12210858383293148143.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_ee57777f148caaa6993972d9709fdf2d.png
rename to resources/testdata/golden/gohugoio24_hu12210858383293148143.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_3.png b/resources/testdata/golden/gohugoio24_hu13086833506724393086.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_bottomleft_3.png
rename to resources/testdata/golden/gohugoio24_hu13086833506724393086.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_abcdd770eaed9301cfff4bc2f96459ba.png b/resources/testdata/golden/gohugoio24_hu13525767338321336398.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_abcdd770eaed9301cfff4bc2f96459ba.png
rename to resources/testdata/golden/gohugoio24_hu13525767338321336398.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d87fd348ad697a9b16399709441d9d56.png b/resources/testdata/golden/gohugoio24_hu1369355309314118179.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d87fd348ad697a9b16399709441d9d56.png
rename to resources/testdata/golden/gohugoio24_hu1369355309314118179.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a751b6cd969d7feab12540a8bb0ca927.png b/resources/testdata/golden/gohugoio24_hu1400913850517810505.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a751b6cd969d7feab12540a8bb0ca927.png
rename to resources/testdata/golden/gohugoio24_hu1400913850517810505.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_3.png b/resources/testdata/golden/gohugoio24_hu14141684530559227507.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_600x0_resize_box_3.png
rename to resources/testdata/golden/gohugoio24_hu14141684530559227507.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a38a1924befb1721a09be7d432f5f70f.png b/resources/testdata/golden/gohugoio24_hu14186438102343930541.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_a38a1924befb1721a09be7d432f5f70f.png
rename to resources/testdata/golden/gohugoio24_hu14186438102343930541.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_43055c40cb4a15bd8491bfc502799f43.png b/resources/testdata/golden/gohugoio24_hu15334383941700802690.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_43055c40cb4a15bd8491bfc502799f43.png
rename to resources/testdata/golden/gohugoio24_hu15334383941700802690.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_3.png b/resources/testdata/golden/gohugoio24_hu16565102657258557500.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_3.png
rename to resources/testdata/golden/gohugoio24_hu16565102657258557500.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d111079da5d8d143b6cae10d6fedbc24.png b/resources/testdata/golden/gohugoio24_hu17044066173912896721.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d111079da5d8d143b6cae10d6fedbc24.png
rename to resources/testdata/golden/gohugoio24_hu17044066173912896721.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_1a923841aa34545db29f46a8fc4c5b0d.png b/resources/testdata/golden/gohugoio24_hu17704168266634609966.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_1a923841aa34545db29f46a8fc4c5b0d.png
rename to resources/testdata/golden/gohugoio24_hu17704168266634609966.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_e8ef2efdde4357a79694ea9c2be82f63.png b/resources/testdata/golden/gohugoio24_hu18417462639738980704.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_e8ef2efdde4357a79694ea9c2be82f63.png
rename to resources/testdata/golden/gohugoio24_hu18417462639738980704.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_3.png b/resources/testdata/golden/gohugoio24_hu2731125086148712886.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_400x200_fill_box_center_3.png
rename to resources/testdata/golden/gohugoio24_hu2731125086148712886.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_324b4d42c8746a684068d123fad8b744.png b/resources/testdata/golden/gohugoio24_hu281918710943920862.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_324b4d42c8746a684068d123fad8b744.png
rename to resources/testdata/golden/gohugoio24_hu281918710943920862.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_4ea8f246299cc5fba9744bdf162bd57d.png b/resources/testdata/golden/gohugoio24_hu3440533624707313030.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_4ea8f246299cc5fba9744bdf162bd57d.png
rename to resources/testdata/golden/gohugoio24_hu3440533624707313030.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_bdde5e36f15689c1451933f92fd357b3.png b/resources/testdata/golden/gohugoio24_hu4079163993667427623.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_bdde5e36f15689c1451933f92fd357b3.png
rename to resources/testdata/golden/gohugoio24_hu4079163993667427623.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_3.png b/resources/testdata/golden/gohugoio24_hu4425915891794988430.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x100_fill_nearestneighbor_topleft_3.png
rename to resources/testdata/golden/gohugoio24_hu4425915891794988430.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_3.png b/resources/testdata/golden/gohugoio24_hu4576019605973288331.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x0_resize_q50_r90_box_3.png
rename to resources/testdata/golden/gohugoio24_hu4576019605973288331.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_3.png b/resources/testdata/golden/gohugoio24_hu4640928585673549425.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_200x100_resize_box_3.png
rename to resources/testdata/golden/gohugoio24_hu4640928585673549425.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_3.png b/resources/testdata/golden/gohugoio24_hu5007208529579570920.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_100x100_fill_box_center_3.png
rename to resources/testdata/golden/gohugoio24_hu5007208529579570920.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_60c098f0ca6626668d9e3ad6bfb38b5b.png b/resources/testdata/golden/gohugoio24_hu5216349243587511129.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_60c098f0ca6626668d9e3ad6bfb38b5b.png
rename to resources/testdata/golden/gohugoio24_hu5216349243587511129.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_8166ccaf22bdabb94c9bb90bffe64133.png b/resources/testdata/golden/gohugoio24_hu578735004461910616.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_8166ccaf22bdabb94c9bb90bffe64133.png
rename to resources/testdata/golden/gohugoio24_hu578735004461910616.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9165e5559db8ba31a401327b5617c098.png b/resources/testdata/golden/gohugoio24_hu5858624748295381298.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9165e5559db8ba31a401327b5617c098.png
rename to resources/testdata/golden/gohugoio24_hu5858624748295381298.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_6575f3a3c39a30cba9d76a6045c36de6.png b/resources/testdata/golden/gohugoio24_hu7784426158237953794.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_6575f3a3c39a30cba9d76a6045c36de6.png
rename to resources/testdata/golden/gohugoio24_hu7784426158237953794.png
diff --git a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_3.png b/resources/testdata/golden/gohugoio24_hu7862113105739619058.png
similarity index 100%
rename from resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fit_linear_3.png
rename to resources/testdata/golden/gohugoio24_hu7862113105739619058.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_a0505112c99af88626ac9b9a16a27acb.png b/resources/testdata/golden/gohugoio8_hu11148399329171024835.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_a0505112c99af88626ac9b9a16a27acb.png
rename to resources/testdata/golden/gohugoio8_hu11148399329171024835.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_eff9583d9b94ac79c60cb099846ce8f3.png b/resources/testdata/golden/gohugoio8_hu11427867868933654094.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_eff9583d9b94ac79c60cb099846ce8f3.png
rename to resources/testdata/golden/gohugoio8_hu11427867868933654094.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_276650b97daa7ae98e79b929d7f87c19.png b/resources/testdata/golden/gohugoio8_hu1162986745985451229.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_276650b97daa7ae98e79b929d7f87c19.png
rename to resources/testdata/golden/gohugoio8_hu1162986745985451229.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_428e769d14483c2fcdd6f5c5138e2066.png b/resources/testdata/golden/gohugoio8_hu11638877035372353818.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_428e769d14483c2fcdd6f5c5138e2066.png
rename to resources/testdata/golden/gohugoio8_hu11638877035372353818.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_398ca764abfff83bb15318068105dcb9.png b/resources/testdata/golden/gohugoio8_hu1217888005047574594.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_398ca764abfff83bb15318068105dcb9.png
rename to resources/testdata/golden/gohugoio8_hu1217888005047574594.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_3ed273f49d1dc83891f5736e21fc5f44.png b/resources/testdata/golden/gohugoio8_hu12220589162864061188.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_3ed273f49d1dc83891f5736e21fc5f44.png
rename to resources/testdata/golden/gohugoio8_hu12220589162864061188.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png b/resources/testdata/golden/gohugoio8_hu12690451569630232821.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png
rename to resources/testdata/golden/gohugoio8_hu12690451569630232821.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_3.png b/resources/testdata/golden/gohugoio8_hu12777352499229939257.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_bottomleft_3.png
rename to resources/testdata/golden/gohugoio8_hu12777352499229939257.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_3.png b/resources/testdata/golden/gohugoio8_hu13982221718330949681.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_400x200_fill_box_center_3.png
rename to resources/testdata/golden/gohugoio8_hu13982221718330949681.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_b34412412a1cf1658e516a335b0a8dd4.png b/resources/testdata/golden/gohugoio8_hu14107602960808127665.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_b34412412a1cf1658e516a335b0a8dd4.png
rename to resources/testdata/golden/gohugoio8_hu14107602960808127665.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_e4d38674b70d9ef559c5df72c9262790.png b/resources/testdata/golden/gohugoio8_hu14156562190479686537.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_e4d38674b70d9ef559c5df72c9262790.png
rename to resources/testdata/golden/gohugoio8_hu14156562190479686537.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_00cd4ff18b53ecbd78e42aefe5fbf522.png b/resources/testdata/golden/gohugoio8_hu14592039178820708584.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_00cd4ff18b53ecbd78e42aefe5fbf522.png
rename to resources/testdata/golden/gohugoio8_hu14592039178820708584.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_f17bba59421e7a500387232295512fc0.png b/resources/testdata/golden/gohugoio8_hu14839804078094713214.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_f17bba59421e7a500387232295512fc0.png
rename to resources/testdata/golden/gohugoio8_hu14839804078094713214.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png b/resources/testdata/golden/gohugoio8_hu1619987041333606118.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png
rename to resources/testdata/golden/gohugoio8_hu1619987041333606118.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_a0ffc0f22f22e6920f3cad414d6db6ba.png b/resources/testdata/golden/gohugoio8_hu17499078314270725360.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_a0ffc0f22f22e6920f3cad414d6db6ba.png
rename to resources/testdata/golden/gohugoio8_hu17499078314270725360.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png b/resources/testdata/golden/gohugoio8_hu18164141965527013334.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png
rename to resources/testdata/golden/gohugoio8_hu18164141965527013334.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_871826faffc414ca3746f65fc9910eed.png b/resources/testdata/golden/gohugoio8_hu3062751268037078239.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_871826faffc414ca3746f65fc9910eed.png
rename to resources/testdata/golden/gohugoio8_hu3062751268037078239.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_3.png b/resources/testdata/golden/gohugoio8_hu3419127525388578393.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_100x100_fill_box_center_3.png
rename to resources/testdata/golden/gohugoio8_hu3419127525388578393.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_3980c5868e0b6f20ec95424dfdcb1d67.png b/resources/testdata/golden/gohugoio8_hu3589035650602432929.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_3980c5868e0b6f20ec95424dfdcb1d67.png
rename to resources/testdata/golden/gohugoio8_hu3589035650602432929.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c5140f11378ddb13843432a5b489594a.png b/resources/testdata/golden/gohugoio8_hu5613016746654861839.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_c5140f11378ddb13843432a5b489594a.png
rename to resources/testdata/golden/gohugoio8_hu5613016746654861839.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_3.png b/resources/testdata/golden/gohugoio8_hu5911891014248513491.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x100_resize_box_3.png
rename to resources/testdata/golden/gohugoio8_hu5911891014248513491.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_3.png b/resources/testdata/golden/gohugoio8_hu5964606204514209824.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x100_fill_nearestneighbor_topleft_3.png
rename to resources/testdata/golden/gohugoio8_hu5964606204514209824.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_3.png b/resources/testdata/golden/gohugoio8_hu7954024233587195445.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_600x0_resize_box_3.png
rename to resources/testdata/golden/gohugoio8_hu7954024233587195445.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_3.png b/resources/testdata/golden/gohugoio8_hu8497843748973739779.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_200x0_resize_q50_r90_box_3.png
rename to resources/testdata/golden/gohugoio8_hu8497843748973739779.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_3.png b/resources/testdata/golden/gohugoio8_hu8680424146433003017.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fill_gaussian_smart1_3.png
rename to resources/testdata/golden/gohugoio8_hu8680424146433003017.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_3.png b/resources/testdata/golden/gohugoio8_hu881093787809754101.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_300x200_fit_linear_3.png
rename to resources/testdata/golden/gohugoio8_hu881093787809754101.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_2e05d39f4cb329be10e8c515494cef76.png b/resources/testdata/golden/gohugoio8_hu9175902413241406055.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_2e05d39f4cb329be10e8c515494cef76.png
rename to resources/testdata/golden/gohugoio8_hu9175902413241406055.png
diff --git a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d17d0184674fcf0a4d770c90bed503db.png b/resources/testdata/golden/gohugoio8_hu971124074666261597.png
similarity index 100%
rename from resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d17d0184674fcf0a4d770c90bed503db.png
rename to resources/testdata/golden/gohugoio8_hu971124074666261597.png
diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_q75_bge3e615_box_3.jpg b/resources/testdata/golden/gopher-hero8_hu18029321441728138356.jpg
similarity index 100%
rename from resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_q75_bge3e615_box_3.jpg
rename to resources/testdata/golden/gopher-hero8_hu18029321441728138356.jpg
diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_filter_10590793696706257122.png b/resources/testdata/golden/gopher-hero8_hu7599356179908845799.png
similarity index 100%
rename from resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_filter_10590793696706257122.png
rename to resources/testdata/golden/gopher-hero8_hu7599356179908845799.png
diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_bge3e615_box_3.png b/resources/testdata/golden/gopher-hero8_hu8339547226364980332.png
similarity index 100%
rename from resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_bge3e615_box_3.png
rename to resources/testdata/golden/gopher-hero8_hu8339547226364980332.png
diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_30x0_resize_box_3.png b/resources/testdata/golden/gopher-hero8_hu9718283601959448032.png
similarity index 100%
rename from resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_30x0_resize_box_3.png
rename to resources/testdata/golden/gopher-hero8_hu9718283601959448032.png
diff --git a/resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_q75_bge3e615_box_3.jpg b/resources/testdata/golden/gradient-circle_hu13051041682786618472.jpg
similarity index 100%
rename from resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_q75_bge3e615_box_3.jpg
rename to resources/testdata/golden/gradient-circle_hu13051041682786618472.jpg
diff --git a/resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_filter_10590793696706257122.png b/resources/testdata/golden/gradient-circle_hu2995269612296002085.png
similarity index 100%
rename from resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_filter_10590793696706257122.png
rename to resources/testdata/golden/gradient-circle_hu2995269612296002085.png
diff --git a/resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_bge3e615_box_3.png b/resources/testdata/golden/gradient-circle_hu4182458540361883551.png
similarity index 100%
rename from resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_bge3e615_box_3.png
rename to resources/testdata/golden/gradient-circle_hu4182458540361883551.png
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg b/resources/testdata/golden/sunset_hu10303518067937744186.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x0_resize_q50_r90_box.jpg
rename to resources/testdata/golden/sunset_hu10303518067937744186.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg b/resources/testdata/golden/sunset_hu10327704110867657089.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_bottomleft.jpg
rename to resources/testdata/golden/sunset_hu10327704110867657089.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg b/resources/testdata/golden/sunset_hu10555571028775464444.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_600x0_resize_q75_box.jpg
rename to resources/testdata/golden/sunset_hu10555571028775464444.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg b/resources/testdata/golden/sunset_hu10706797811022665574.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6c5c12ac79d3455ccb1993d51eec3cdf.jpg
rename to resources/testdata/golden/sunset_hu10706797811022665574.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg b/resources/testdata/golden/sunset_hu1184697552324640242.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fit_q75_linear.jpg
rename to resources/testdata/golden/sunset_hu1184697552324640242.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6673ece428cb7d523234ca0d7c299542.jpg b/resources/testdata/golden/sunset_hu13997988183102182682.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_6673ece428cb7d523234ca0d7c299542.jpg
rename to resources/testdata/golden/sunset_hu13997988183102182682.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_filter_16531506165985954191.jpg b/resources/testdata/golden/sunset_hu14647978205320151651.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_filter_16531506165985954191.jpg
rename to resources/testdata/golden/sunset_hu14647978205320151651.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c50a17db1e6d1bd0fe31a9a3444f1587.jpg b/resources/testdata/golden/sunset_hu1508817148191451926.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c50a17db1e6d1bd0fe31a9a3444f1587.jpg
rename to resources/testdata/golden/sunset_hu1508817148191451926.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg b/resources/testdata/golden/sunset_hu15092417974014570823.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x100_fill_q75_nearestneighbor_topleft.jpg
rename to resources/testdata/golden/sunset_hu15092417974014570823.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg b/resources/testdata/golden/sunset_hu15553011452282641860.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_400x200_fill_q75_box_center.jpg
rename to resources/testdata/golden/sunset_hu15553011452282641860.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg b/resources/testdata/golden/sunset_hu16406839322505000040.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3efc2d0f29a8e12c5a690fc6c9288854.jpg
rename to resources/testdata/golden/sunset_hu16406839322505000040.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg b/resources/testdata/golden/sunset_hu17096332274161262571.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_17fd3c558d78ce249b5f0bcbe1ddbffb.jpg
rename to resources/testdata/golden/sunset_hu17096332274161262571.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_filter_18171945436439920693.jpg b/resources/testdata/golden/sunset_hu17735388202135872885.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_filter_18171945436439920693.jpg
rename to resources/testdata/golden/sunset_hu17735388202135872885.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg b/resources/testdata/golden/sunset_hu18138233340135699657.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_fill_q75_gaussian_smart1.jpg
rename to resources/testdata/golden/sunset_hu18138233340135699657.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg b/resources/testdata/golden/sunset_hu18339759113732359089.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_0d1b300da7a815ed567b6dadb6f2ce5e.jpg
rename to resources/testdata/golden/sunset_hu18339759113732359089.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c2d24766b49f3147f5a4137a8db592ac.jpg b/resources/testdata/golden/sunset_hu1940616730388248148.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c2d24766b49f3147f5a4137a8db592ac.jpg
rename to resources/testdata/golden/sunset_hu1940616730388248148.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg b/resources/testdata/golden/sunset_hu2317318497170846135.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_abf356affd7d70d6bec3b3498b572191.jpg
rename to resources/testdata/golden/sunset_hu2317318497170846135.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg b/resources/testdata/golden/sunset_hu2620328362006291004.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_d30c10468b33df9010d185a8fe8f0491.jpg
rename to resources/testdata/golden/sunset_hu2620328362006291004.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg b/resources/testdata/golden/sunset_hu3329557056351636779.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_f6d8fe32ce3e83abf130e91e33456914.jpg
rename to resources/testdata/golden/sunset_hu3329557056351636779.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_510813cc53c37e2d489d2f9fdb13f749.jpg b/resources/testdata/golden/sunset_hu3528013170819345965.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_510813cc53c37e2d489d2f9fdb13f749.jpg
rename to resources/testdata/golden/sunset_hu3528013170819345965.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg b/resources/testdata/golden/sunset_hu4475169669413030903.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_resize_q75_box.jpg
rename to resources/testdata/golden/sunset_hu4475169669413030903.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg b/resources/testdata/golden/sunset_hu4645485093540451131.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c36da6818db1ab630c3f87f65170003b.jpg
rename to resources/testdata/golden/sunset_hu4645485093540451131.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg b/resources/testdata/golden/sunset_hu5700214275785775137.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_cb45fcba865177290c89dc9f41d6ff7a.jpg
rename to resources/testdata/golden/sunset_hu5700214275785775137.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg b/resources/testdata/golden/sunset_hu5943101889480068836.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_de1fe6c0f40e7165355507d0f1748083.jpg
rename to resources/testdata/golden/sunset_hu5943101889480068836.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg b/resources/testdata/golden/sunset_hu6418511461467579286.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_30fc2aab35ca0861bf396d09aebc85a4.jpg
rename to resources/testdata/golden/sunset_hu6418511461467579286.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg b/resources/testdata/golden/sunset_hu7124641571345460976.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_7d9bc4700565266807dc476421066137.jpg
rename to resources/testdata/golden/sunset_hu7124641571345460976.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg b/resources/testdata/golden/sunset_hu7168213667150853195.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x100_fill_q75_box_center.jpg
rename to resources/testdata/golden/sunset_hu7168213667150853195.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg b/resources/testdata/golden/sunset_hu8110718388195953309.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f1b1455c4a7d13c5aeb7510f9a6a581.jpg
rename to resources/testdata/golden/sunset_hu8110718388195953309.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg b/resources/testdata/golden/sunset_hu8726458014885573310.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_9f00027c376fe8556cc9996c47f23f78.jpg
rename to resources/testdata/golden/sunset_hu8726458014885573310.jpg
diff --git a/resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg b/resources/testdata/golden/sunset_hu9995349819947557369.jpg
similarity index 100%
rename from resources/testdata/golden/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_352eb0101b7c88107520ba719432bbb2.jpg
rename to resources/testdata/golden/sunset_hu9995349819947557369.jpg
diff --git a/resources/testdata/golden_webp/fuzzy-cirlcle_hu525d1a6cf670e85f5e8f19890241399b_26792_200x0_resize_q75_h2_box_3.webp b/resources/testdata/golden_webp/fuzzy-cirlcle_hu11078464949742740533.webp
similarity index 100%
rename from resources/testdata/golden_webp/fuzzy-cirlcle_hu525d1a6cf670e85f5e8f19890241399b_26792_200x0_resize_q75_h2_box_3.webp
rename to resources/testdata/golden_webp/fuzzy-cirlcle_hu11078464949742740533.webp
diff --git a/resources/transform_test.go b/resources/transform_test.go
index 7f91360f190..b036a44d778 100644
--- a/resources/transform_test.go
+++ b/resources/transform_test.go
@@ -386,15 +386,15 @@ func TestTransform(t *testing.T) {
 		resizedPublished1, err := img.Resize("40x40")
 		c.Assert(err, qt.IsNil)
 		c.Assert(resizedPublished1.Height(), qt.Equals, 40)
-		c.Assert(resizedPublished1.RelPermalink(), qt.Equals, "/gopher.changed_hu2e827f5a78333ebc04166dd643235dea_1462_40x40_resize_linear_3.png")
-		assertShouldExist(c, spec, "public/gopher.changed_hu2e827f5a78333ebc04166dd643235dea_1462_40x40_resize_linear_3.png", true)
+		c.Assert(resizedPublished1.RelPermalink(), qt.Equals, "/gopher.changed_hu16988682630555427117.png")
+		assertShouldExist(c, spec, "public/gopher.changed_hu16988682630555427117.png", true)
 
 		// Permalink called.
 		resizedPublished2, err := img.Resize("30x30")
 		c.Assert(err, qt.IsNil)
 		c.Assert(resizedPublished2.Height(), qt.Equals, 30)
-		c.Assert(resizedPublished2.Permalink(), qt.Equals, "https://example.com/gopher.changed_hu2e827f5a78333ebc04166dd643235dea_1462_30x30_resize_linear_3.png")
-		assertShouldExist(c, spec, "public/gopher.changed_hu2e827f5a78333ebc04166dd643235dea_1462_30x30_resize_linear_3.png", true)
+		c.Assert(resizedPublished2.Permalink(), qt.Equals, "https://example.com/gopher.changed_hu14141325020798305104.png")
+		assertShouldExist(c, spec, "public/gopher.changed_hu14141325020798305104.png", true)
 
 		// Not published because none of RelPermalink or Permalink was called.
 		resizedNotPublished, err := img.Resize("50x50")
diff --git a/tpl/resources/resources_integration_test.go b/tpl/resources/resources_integration_test.go
index 6bc872bca8e..cfd03dc735e 100644
--- a/tpl/resources/resources_integration_test.go
+++ b/tpl/resources/resources_integration_test.go
@@ -58,7 +58,7 @@ Copy3: {{ $copy3.RelPermalink}}|{{ $copy3.MediaType }}|{{ $copy3.Content | safeJ
 
 	b.AssertFileContent("public/index.html", `
 Image Orig:  /blog/images/pixel.png|image/png|1|1|
-Image Copy1:  /blog/images/copy_hu8aa3346827e49d756ff4e630147c42b5_70_3x4_resize_box_3.png|image/png|3|4|
+Image Copy1:  /blog/images/copy_hu2891316072287293157.png|image/png|3|4|
 Image Copy2:  /blog/images/copy2.png|image/png|3|4
 Image Copy3:  image/png|3|4|
 Orig: /blog/js/foo.js|text/javascript|let foo;|