diff --git a/go.mod b/go.mod
index de799c1..b8f8917 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
 	github.com/gofrs/uuid v4.4.0+incompatible // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.12 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
@@ -38,6 +39,7 @@ require (
 	github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
+	github.com/zeebo/blake3 v0.2.3 // indirect
 	golang.org/x/sys v0.13.0 // indirect
 	golang.org/x/term v0.13.0 // indirect
 	golang.org/x/text v0.13.0 // indirect
diff --git a/go.sum b/go.sum
index a709cde..3d87649 100644
--- a/go.sum
+++ b/go.sum
@@ -28,6 +28,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
+github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -81,6 +83,10 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
 github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
+github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
+github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
+github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
diff --git a/hashtools/blake2.go b/hashtools/blake2.go
index a2262cd..d9a9c19 100644
--- a/hashtools/blake2.go
+++ b/hashtools/blake2.go
@@ -18,7 +18,8 @@ func init() {
 
 	Register(blake2bBase.With(&HashTool{
 		Name:          "BLAKE2s-256",
-		Hash:          crypto.BLAKE2s_256,
+		NewHash:       crypto.BLAKE2s_256.New,
+		CryptoHashID:  crypto.BLAKE2b_256,
 		DigestSize:    crypto.BLAKE2s_256.Size(),
 		BlockSize:     crypto.BLAKE2s_256.New().BlockSize(),
 		SecurityLevel: 128,
@@ -27,7 +28,8 @@ func init() {
 	}))
 	Register(blake2bBase.With(&HashTool{
 		Name:          "BLAKE2b-256",
-		Hash:          crypto.BLAKE2b_256,
+		NewHash:       crypto.BLAKE2b_256.New,
+		CryptoHashID:  crypto.BLAKE2b_256,
 		DigestSize:    crypto.BLAKE2b_256.Size(),
 		BlockSize:     crypto.BLAKE2b_256.New().BlockSize(),
 		SecurityLevel: 128,
@@ -35,7 +37,8 @@ func init() {
 	}))
 	Register(blake2bBase.With(&HashTool{
 		Name:          "BLAKE2b-384",
-		Hash:          crypto.BLAKE2b_384,
+		NewHash:       crypto.BLAKE2b_384.New,
+		CryptoHashID:  crypto.BLAKE2b_384,
 		DigestSize:    crypto.BLAKE2b_384.Size(),
 		BlockSize:     crypto.BLAKE2b_384.New().BlockSize(),
 		SecurityLevel: 192,
@@ -43,7 +46,8 @@ func init() {
 	}))
 	Register(blake2bBase.With(&HashTool{
 		Name:          "BLAKE2b-512",
-		Hash:          crypto.BLAKE2b_512,
+		NewHash:       crypto.BLAKE2b_512.New,
+		CryptoHashID:  crypto.BLAKE2b_512,
 		DigestSize:    crypto.BLAKE2b_512.Size(),
 		BlockSize:     crypto.BLAKE2b_512.New().BlockSize(),
 		SecurityLevel: 256,
diff --git a/hashtools/blake3.go b/hashtools/blake3.go
new file mode 100644
index 0000000..2cec861
--- /dev/null
+++ b/hashtools/blake3.go
@@ -0,0 +1,26 @@
+package hashtools
+
+import (
+	"hash"
+
+	"github.com/zeebo/blake3"
+
+	"github.com/safing/jess/lhash"
+)
+
+func init() {
+	Register(&HashTool{
+		Name:          "BLAKE3",
+		NewHash:       newBlake3,
+		DigestSize:    newBlake3().Size(),
+		BlockSize:     newBlake3().BlockSize(),
+		SecurityLevel: 128,
+		Comment:       "cryptographic hash function based on Bao and BLAKE2",
+		Author:        "Jean-Philippe Aumasson et al., 2020",
+		labeledAlg:    lhash.BLAKE3,
+	})
+}
+
+func newBlake3() hash.Hash {
+	return blake3.New()
+}
diff --git a/hashtools/hashtool.go b/hashtools/hashtool.go
index a86fff2..18a3479 100644
--- a/hashtools/hashtool.go
+++ b/hashtools/hashtool.go
@@ -10,7 +10,9 @@ import (
 // HashTool holds generic information about a hash tool.
 type HashTool struct {
 	Name string
-	Hash crypto.Hash
+
+	NewHash      func() hash.Hash
+	CryptoHashID crypto.Hash
 
 	DigestSize    int // in bytes
 	BlockSize     int // in bytes
@@ -24,7 +26,7 @@ type HashTool struct {
 
 // New returns a new hash.Hash instance of the hash tool.
 func (ht *HashTool) New() hash.Hash {
-	return ht.Hash.New()
+	return ht.NewHash()
 }
 
 // With uses the original HashTool as a template for a new HashTool and returns the new HashTool.
@@ -32,8 +34,11 @@ func (ht *HashTool) With(changes *HashTool) *HashTool {
 	if changes.Name == "" {
 		changes.Name = ht.Name
 	}
-	if changes.Hash == 0 {
-		changes.Hash = ht.Hash
+	if changes.NewHash == nil {
+		changes.NewHash = ht.NewHash
+	}
+	if changes.CryptoHashID == 0 {
+		changes.CryptoHashID = ht.CryptoHashID
 	}
 	if changes.DigestSize == 0 {
 		changes.DigestSize = ht.DigestSize
diff --git a/hashtools/sha.go b/hashtools/sha.go
index ea16311..b7d1834 100644
--- a/hashtools/sha.go
+++ b/hashtools/sha.go
@@ -20,7 +20,8 @@ func init() {
 	}
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-224",
-		Hash:          crypto.SHA224,
+		NewHash:       crypto.SHA224.New,
+		CryptoHashID:  crypto.SHA224,
 		DigestSize:    crypto.SHA224.Size(),
 		BlockSize:     crypto.SHA224.New().BlockSize(),
 		SecurityLevel: 112,
@@ -29,7 +30,8 @@ func init() {
 	}))
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-256",
-		Hash:          crypto.SHA256,
+		NewHash:       crypto.SHA256.New,
+		CryptoHashID:  crypto.SHA256,
 		DigestSize:    crypto.SHA256.Size(),
 		BlockSize:     crypto.SHA256.New().BlockSize(),
 		SecurityLevel: 128,
@@ -37,7 +39,8 @@ func init() {
 	}))
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-384",
-		Hash:          crypto.SHA384,
+		NewHash:       crypto.SHA384.New,
+		CryptoHashID:  crypto.SHA384,
 		DigestSize:    crypto.SHA384.Size(),
 		BlockSize:     crypto.SHA384.New().BlockSize(),
 		SecurityLevel: 192,
@@ -45,7 +48,8 @@ func init() {
 	}))
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-512",
-		Hash:          crypto.SHA512,
+		NewHash:       crypto.SHA512.New,
+		CryptoHashID:  crypto.SHA512,
 		DigestSize:    crypto.SHA512.Size(),
 		BlockSize:     crypto.SHA512.New().BlockSize(),
 		SecurityLevel: 256,
@@ -53,7 +57,8 @@ func init() {
 	}))
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-512-224",
-		Hash:          crypto.SHA512_224,
+		NewHash:       crypto.SHA512_224.New,
+		CryptoHashID:  crypto.SHA512_224,
 		DigestSize:    crypto.SHA512_224.Size(),
 		BlockSize:     crypto.SHA512_224.New().BlockSize(),
 		SecurityLevel: 112,
@@ -61,7 +66,8 @@ func init() {
 	}))
 	Register(sha2Base.With(&HashTool{
 		Name:          "SHA2-512-256",
-		Hash:          crypto.SHA512_256,
+		NewHash:       crypto.SHA512_256.New,
+		CryptoHashID:  crypto.SHA512_256,
 		DigestSize:    crypto.SHA512_256.Size(),
 		BlockSize:     crypto.SHA512_256.New().BlockSize(),
 		SecurityLevel: 128,
@@ -75,7 +81,8 @@ func init() {
 	}
 	Register(sha3Base.With(&HashTool{
 		Name:          "SHA3-224",
-		Hash:          crypto.SHA3_224,
+		NewHash:       crypto.SHA3_224.New,
+		CryptoHashID:  crypto.SHA3_224,
 		DigestSize:    crypto.SHA3_224.Size(),
 		BlockSize:     crypto.SHA3_224.New().BlockSize(),
 		SecurityLevel: 112,
@@ -83,7 +90,8 @@ func init() {
 	}))
 	Register(sha3Base.With(&HashTool{
 		Name:          "SHA3-256",
-		Hash:          crypto.SHA3_256,
+		NewHash:       crypto.SHA3_256.New,
+		CryptoHashID:  crypto.SHA3_256,
 		DigestSize:    crypto.SHA3_256.Size(),
 		BlockSize:     crypto.SHA3_256.New().BlockSize(),
 		SecurityLevel: 128,
@@ -91,7 +99,8 @@ func init() {
 	}))
 	Register(sha3Base.With(&HashTool{
 		Name:          "SHA3-384",
-		Hash:          crypto.SHA3_384,
+		NewHash:       crypto.SHA3_384.New,
+		CryptoHashID:  crypto.SHA3_384,
 		DigestSize:    crypto.SHA3_384.Size(),
 		BlockSize:     crypto.SHA3_384.New().BlockSize(),
 		SecurityLevel: 192,
@@ -99,7 +108,8 @@ func init() {
 	}))
 	Register(sha3Base.With(&HashTool{
 		Name:          "SHA3-512",
-		Hash:          crypto.SHA3_512,
+		NewHash:       crypto.SHA3_512.New,
+		CryptoHashID:  crypto.SHA3_512,
 		DigestSize:    crypto.SHA3_512.Size(),
 		BlockSize:     crypto.SHA3_512.New().BlockSize(),
 		SecurityLevel: 256,
diff --git a/hashtools/tools_test.go b/hashtools/tools_test.go
index 4884104..62ac492 100644
--- a/hashtools/tools_test.go
+++ b/hashtools/tools_test.go
@@ -1,16 +1,18 @@
 package hashtools
 
-import "testing"
+import (
+	"encoding/hex"
+	"testing"
+)
 
 func TestAll(t *testing.T) {
 	t.Parallel()
 
-	testData := []byte("The quick brown fox jumps over the lazy dog. ")
+	testData := []byte("The quick brown fox jumps over the lazy dog.")
 
 	all := AsList()
 	for _, hashTool := range all {
-
-		// take detour in getting hash.Hash for testing
+		// Test hash usage.
 		hash, err := New(hashTool.Name)
 		if err != nil {
 			t.Fatalf("failed to get HashTool %s", hashTool.Name)
@@ -30,5 +32,97 @@ func TestAll(t *testing.T) {
 			t.Errorf("hashTool %s is broken or reports invalid digest size. Expected %d, got %d.", hashTool.Name, hashTool.DigestSize, len(sum))
 		}
 
+		// Check hash outputs.
+		expectedOutputs, ok := testOutputs[hashTool.Name]
+		if !ok {
+			t.Errorf("no test outputs available for %s", hashTool.Name)
+			continue
+		}
+
+		// Test empty string.
+		hash.Reset()
+		_, _ = hash.Write(testInputEmpty)
+		hexSum := hex.EncodeToString(hash.Sum(nil))
+		if hexSum != expectedOutputs[0] {
+			t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v",
+				hashTool.Name, expectedOutputs[0], hexSum)
+		}
+
+		// Test fox string.
+		hash.Reset()
+		_, _ = hash.Write(testInputFox)
+		hexSum = hex.EncodeToString(hash.Sum(nil))
+		if hexSum != expectedOutputs[1] {
+			t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v",
+				hashTool.Name, expectedOutputs[1], hexSum)
+		}
 	}
 }
+
+var (
+	testInputEmpty = []byte("")
+	testInputFox   = []byte("The quick brown fox jumps over the lazy dog.")
+)
+
+var testOutputs = map[string][2]string{
+	"SHA2-224": {
+		"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
+		"619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c",
+	},
+	"SHA2-256": {
+		"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+		"ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c",
+	},
+	"SHA2-384": {
+		"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
+		"ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7",
+	},
+	"SHA2-512": {
+		"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
+		"91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed",
+	},
+	"SHA2-512-224": {
+		"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4",
+		"6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de",
+	},
+	"SHA2-512-256": {
+		"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
+		"1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3",
+	},
+	"SHA3-224": {
+		"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7",
+		"2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0",
+	},
+	"SHA3-256": {
+		"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
+		"a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d",
+	},
+	"SHA3-384": {
+		"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004",
+		"1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9",
+	},
+	"SHA3-512": {
+		"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
+		"18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8",
+	},
+	"BLAKE2s-256": {
+		"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9",
+		"95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2",
+	},
+	"BLAKE2b-256": {
+		"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
+		"69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712",
+	},
+	"BLAKE2b-384": {
+		"b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100",
+		"16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337",
+	},
+	"BLAKE2b-512": {
+		"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
+		"87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2",
+	},
+	"BLAKE3": {
+		"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
+		"4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d",
+	},
+}
diff --git a/lhash/algs.go b/lhash/algs.go
index e4a15cc..50c6c35 100644
--- a/lhash/algs.go
+++ b/lhash/algs.go
@@ -18,6 +18,8 @@ import (
 	// Register BLAKE2 in Go's internal registry.
 	_ "golang.org/x/crypto/blake2b"
 	_ "golang.org/x/crypto/blake2s"
+
+	"github.com/zeebo/blake3"
 )
 
 // Algorithm is an identifier for a hash function.
@@ -41,6 +43,8 @@ const (
 	BLAKE2b_256 Algorithm = 25
 	BLAKE2b_384 Algorithm = 26
 	BLAKE2b_512 Algorithm = 27
+
+	BLAKE3 Algorithm = 32
 )
 
 func (a Algorithm) new() hash.Hash {
@@ -70,7 +74,7 @@ func (a Algorithm) new() hash.Hash {
 	case SHA3_512:
 		return crypto.SHA3_512.New()
 
-	// BLAKE2
+		// BLAKE2
 	case BLAKE2s_256:
 		return crypto.BLAKE2s_256.New()
 	case BLAKE2b_256:
@@ -80,6 +84,10 @@ func (a Algorithm) new() hash.Hash {
 	case BLAKE2b_512:
 		return crypto.BLAKE2b_512.New()
 
+		// BLAKE3
+	case BLAKE3:
+		return blake3.New()
+
 	default:
 		return nil
 	}
@@ -122,6 +130,10 @@ func (a Algorithm) String() string {
 	case BLAKE2b_512:
 		return "BLAKE2b_512"
 
+		// BLAKE3
+	case BLAKE3:
+		return "BLAKE3"
+
 	default:
 		return "unknown"
 	}
diff --git a/lhash/labeledhash_test.go b/lhash/labeledhash_test.go
index 5facd3c..ee9079a 100644
--- a/lhash/labeledhash_test.go
+++ b/lhash/labeledhash_test.go
@@ -32,27 +32,29 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
 	// test empty
 	lh := Digest(alg, testEmpty)
 	if !bytes.Equal(lh.Bytes()[2:], emptyBytes) {
-		t.Errorf("alg %d: test empty: digest mismatch, expected %+v, got %+v", alg, emptyBytes, lh.Bytes()[2:])
+		t.Errorf("alg %s: test empty: digest mismatch, expected %+v, got %+v",
+			alg, hex.EncodeToString(emptyBytes), hex.EncodeToString(lh.Bytes()[2:]))
 	}
 
 	// test fox
 	lh = Digest(alg, testFoxData)
 	if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
-		t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
+		t.Errorf("alg %s: test fox: digest mismatch, expected %+v, got %+v",
+			alg, hex.EncodeToString(foxBytes), hex.EncodeToString(lh.Bytes()[2:]))
 	}
 
 	// test matching with serialized/loaded labeled hash
 	if !lh.Matches(testFoxData) {
-		t.Errorf("alg %d: failed to match reference", alg)
+		t.Errorf("alg %s: failed to match reference", alg)
 	}
 	if !lh.MatchesString(testFox) {
-		t.Errorf("alg %d: failed to match reference", alg)
+		t.Errorf("alg %s: failed to match reference", alg)
 	}
 	if lh.Matches(noMatchData) {
-		t.Errorf("alg %d: failed to non-match garbage", alg)
+		t.Errorf("alg %s: failed to non-match garbage", alg)
 	}
 	if lh.MatchesString(noMatch) {
-		t.Errorf("alg %d: failed to non-match garbage", alg)
+		t.Errorf("alg %s: failed to non-match garbage", alg)
 	}
 
 	// Test representations
@@ -61,7 +63,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
 	lhs := Digest(alg, testFoxData)
 	loaded, err := FromHex(lhs.Hex())
 	if err != nil {
-		t.Errorf("alg %d: failed to load from hex string: %s", alg, err)
+		t.Errorf("alg %s: failed to load from hex string: %s", alg, err)
 		return
 	}
 	testFormat(t, alg, lhs, loaded)
@@ -70,7 +72,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
 	lhs = Digest(alg, testFoxData)
 	loaded, err = FromBase64(lhs.Base64())
 	if err != nil {
-		t.Errorf("alg %d: failed to load from base64 string: %s", alg, err)
+		t.Errorf("alg %s: failed to load from base64 string: %s", alg, err)
 		return
 	}
 	testFormat(t, alg, lhs, loaded)
@@ -79,7 +81,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
 	lhs = Digest(alg, testFoxData)
 	loaded, err = FromBase58(lhs.Base58())
 	if err != nil {
-		t.Errorf("alg %d: failed to load from base58 string: %s", alg, err)
+		t.Errorf("alg %s: failed to load from base58 string: %s", alg, err)
 		return
 	}
 	testFormat(t, alg, lhs, loaded)
@@ -92,47 +94,88 @@ func testFormat(t *testing.T, alg Algorithm, lhs, loaded *LabeledHash) {
 
 	// Test equality.
 	if !lhs.Equal(loaded) {
-		t.Errorf("alg %d: equality test failed", alg)
+		t.Errorf("alg %s: equality test failed", alg)
 	}
 	if lhs.Equal(noMatchLH) {
-		t.Errorf("alg %d: non-equality test failed", alg)
+		t.Errorf("alg %s: non-equality test failed", alg)
 	}
 
 	// Test matching.
 	if !loaded.Matches(testFoxData) {
-		t.Errorf("alg %d: failed to match reference", alg)
+		t.Errorf("alg %s: failed to match reference", alg)
 	}
 	if !loaded.MatchesString(testFox) {
-		t.Errorf("alg %d: failed to match reference", alg)
+		t.Errorf("alg %s: failed to match reference", alg)
 	}
 	if loaded.Matches(noMatchData) {
-		t.Errorf("alg %d: failed to non-match garbage", alg)
+		t.Errorf("alg %s: failed to non-match garbage", alg)
 	}
 	if loaded.MatchesString(noMatch) {
-		t.Errorf("alg %d: failed to non-match garbage", alg)
+		t.Errorf("alg %s: failed to non-match garbage", alg)
 	}
 }
 
 func TestHash(t *testing.T) {
 	t.Parallel()
 
+	testAlgorithm(t, SHA2_224,
+		"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
+		"619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c",
+	)
 	testAlgorithm(t, SHA2_256,
 		"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
 		"ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c",
 	)
-
+	testAlgorithm(t, SHA2_384,
+		"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
+		"ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7",
+	)
 	testAlgorithm(t, SHA2_512,
 		"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
 		"91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed",
 	)
-
+	testAlgorithm(t, SHA2_512_224,
+		"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4",
+		"6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de",
+	)
+	testAlgorithm(t, SHA2_512_256,
+		"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
+		"1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3",
+	)
+	testAlgorithm(t, SHA3_224,
+		"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7",
+		"2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0",
+	)
+	testAlgorithm(t, SHA3_256,
+		"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
+		"a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d",
+	)
+	testAlgorithm(t, SHA3_384,
+		"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004",
+		"1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9",
+	)
 	testAlgorithm(t, SHA3_512,
 		"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
 		"18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8",
 	)
-
+	testAlgorithm(t, BLAKE2s_256,
+		"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9",
+		"95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2",
+	)
+	testAlgorithm(t, BLAKE2b_256,
+		"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
+		"69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712",
+	)
+	testAlgorithm(t, BLAKE2b_384,
+		"b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100",
+		"16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337",
+	)
 	testAlgorithm(t, BLAKE2b_512,
 		"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
 		"87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2",
 	)
+	testAlgorithm(t, BLAKE3,
+		"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
+		"4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d",
+	)
 }
diff --git a/suites.go b/suites.go
index c9b5915..dcdbb2b 100644
--- a/suites.go
+++ b/suites.go
@@ -1,72 +1,7 @@
 package jess
 
+// Currently Recommended Suites.
 var (
-	// Suite Lists.
-	suitesMap  = make(map[string]*Suite)
-	suitesList []*Suite
-
-	// Suite Definitions.
-
-	// SuiteKeyV1 is a cipher suite for encryption with a key.
-	SuiteKeyV1 = registerSuite(&Suite{
-		ID:            "key_v1",
-		Tools:         []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
-		Provides:      NewRequirements(),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuitePasswordV1 is a cipher suite for encryption with a password.
-	SuitePasswordV1 = registerSuite(&Suite{
-		ID:            "pw_v1",
-		Tools:         []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
-		Provides:      NewRequirements(),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
-	SuiteRcptOnlyV1 = registerSuite(&Suite{
-		ID:            "rcpt_v1",
-		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
-		Provides:      NewRequirements().Remove(SenderAuthentication),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuiteSignV1 is a cipher suite for signing (no encryption).
-	SuiteSignV1 = registerSuite(&Suite{
-		ID:            "sign_v1",
-		Tools:         []string{"Ed25519(BLAKE2b-256)"},
-		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuiteSignFileV1 is a cipher suite for signing files (no encryption).
-	// SHA2_256 is chosen for better compatibility with other tool sets and workflows.
-	SuiteSignFileV1 = registerSuite(&Suite{
-		ID:            "signfile_v1",
-		Tools:         []string{"Ed25519(SHA2-256)"},
-		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
-	SuiteCompleteV1 = registerSuite(&Suite{
-		ID:            "v1",
-		Tools:         []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
-		Provides:      NewRequirements(),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-	// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
-	SuiteWireV1 = registerSuite(&Suite{
-		ID:            "w1",
-		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
-		Provides:      NewRequirements().Remove(SenderAuthentication),
-		SecurityLevel: 128,
-		Status:        SuiteStatusRecommended,
-	})
-
-	// Currently Recommended Suites.
-
 	// SuiteKey is a cipher suite for encryption with a key.
 	SuiteKey = SuiteKeyV1
 	// SuitePassword is a cipher suite for encryption with a password.
@@ -83,6 +18,12 @@ var (
 	SuiteWire = SuiteWireV1
 )
 
+// Suite Lists.
+var (
+	suitesMap  = make(map[string]*Suite)
+	suitesList []*Suite
+)
+
 func registerSuite(suite *Suite) (suiteID string) {
 	// add if not exists
 	_, ok := suitesMap[suite.ID]
diff --git a/suites_v1.go b/suites_v1.go
new file mode 100644
index 0000000..455246e
--- /dev/null
+++ b/suites_v1.go
@@ -0,0 +1,61 @@
+package jess //nolint:dupl
+
+var (
+	// SuiteKeyV1 is a cipher suite for encryption with a key.
+	SuiteKeyV1 = registerSuite(&Suite{
+		ID:            "key_v1",
+		Tools:         []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuitePasswordV1 is a cipher suite for encryption with a password.
+	SuitePasswordV1 = registerSuite(&Suite{
+		ID:            "pw_v1",
+		Tools:         []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
+	SuiteRcptOnlyV1 = registerSuite(&Suite{
+		ID:            "rcpt_v1",
+		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteSignV1 is a cipher suite for signing (no encryption).
+	SuiteSignV1 = registerSuite(&Suite{
+		ID:            "sign_v1",
+		Tools:         []string{"Ed25519(BLAKE2b-256)"},
+		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteSignFileV1 is a cipher suite for signing files (no encryption).
+	// SHA2_256 is chosen for better compatibility with other tool sets and workflows.
+	SuiteSignFileV1 = registerSuite(&Suite{
+		ID:            "signfile_v1",
+		Tools:         []string{"Ed25519(SHA2-256)"},
+		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
+	SuiteCompleteV1 = registerSuite(&Suite{
+		ID:            "v1",
+		Tools:         []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+	// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
+	SuiteWireV1 = registerSuite(&Suite{
+		ID:            "w1",
+		Tools:         []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusRecommended,
+	})
+)
diff --git a/suites_v2.go b/suites_v2.go
new file mode 100644
index 0000000..2bcf907
--- /dev/null
+++ b/suites_v2.go
@@ -0,0 +1,61 @@
+package jess //nolint:dupl
+
+var (
+	// SuiteKeyV2 is a cipher suite for encryption with a key.
+	SuiteKeyV2 = registerSuite(&Suite{
+		ID:            "key_v2",
+		Tools:         []string{"BLAKE3-KDF", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuitePasswordV2 is a cipher suite for encryption with a password.
+	SuitePasswordV2 = registerSuite(&Suite{
+		ID:            "pw_v2",
+		Tools:         []string{"SCRYPT-20", "BLAKE3-KDF", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuiteRcptOnlyV2 is a cipher suite for encrypting for someone, but without verifying the sender/source.
+	SuiteRcptOnlyV2 = registerSuite(&Suite{
+		ID:            "rcpt_v2",
+		Tools:         []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuiteSignV2 is a cipher suite for signing (no encryption).
+	SuiteSignV2 = registerSuite(&Suite{
+		ID:            "sign_v2",
+		Tools:         []string{"Ed25519(BLAKE3)"},
+		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuiteSignFileV2 is a cipher suite for signing files (no encryption).
+	// SHA2_256 is chosen for better compatibility with other tool sets and workflows.
+	SuiteSignFileV2 = registerSuite(&Suite{
+		ID:            "signfile_v2",
+		Tools:         []string{"Ed25519(BLAKE3)"},
+		Provides:      newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuiteCompleteV2 is a cipher suite for both encrypting for someone and signing.
+	SuiteCompleteV2 = registerSuite(&Suite{
+		ID:            "v2",
+		Tools:         []string{"ECDH-X25519", "Ed25519(BLAKE3)", "BLAKE3-KDF", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements(),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+	// SuiteWireV2 is a cipher suite for network communication, including authentication of the server, but not the client.
+	SuiteWireV2 = registerSuite(&Suite{
+		ID:            "w2",
+		Tools:         []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"},
+		Provides:      NewRequirements().Remove(SenderAuthentication),
+		SecurityLevel: 128,
+		Status:        SuiteStatusPermitted,
+	})
+)
diff --git a/tools/all/all.go b/tools/all/all.go
index b1d0080..25c0deb 100644
--- a/tools/all/all.go
+++ b/tools/all/all.go
@@ -3,6 +3,7 @@ package all
 
 import (
 	// Import all tool subpackages.
+	_ "github.com/safing/jess/tools/blake3"
 	_ "github.com/safing/jess/tools/ecdh"
 	_ "github.com/safing/jess/tools/gostdlib"
 )
diff --git a/tools/blake3/kdf.go b/tools/blake3/kdf.go
new file mode 100644
index 0000000..f95a8af
--- /dev/null
+++ b/tools/blake3/kdf.go
@@ -0,0 +1,68 @@
+package blake3
+
+import (
+	"errors"
+	"fmt"
+	"io"
+
+	"github.com/zeebo/blake3"
+
+	"github.com/safing/jess/tools"
+)
+
+func init() {
+	tools.Register(&tools.Tool{
+		Info: &tools.ToolInfo{
+			Name:          "BLAKE3-KDF",
+			Purpose:       tools.PurposeKeyDerivation,
+			SecurityLevel: 128,
+			Comment:       "cryptographic hash function based on Bao and BLAKE2",
+			Author:        "Jean-Philippe Aumasson et al., 2020",
+		},
+		Factory: func() tools.ToolLogic { return &KDF{} },
+	})
+}
+
+// KDF implements the cryptographic interface for BLAKE3 key derivation.
+type KDF struct {
+	tools.ToolLogicBase
+	reader io.Reader
+}
+
+// InitKeyDerivation implements the ToolLogic interface.
+func (keyder *KDF) InitKeyDerivation(nonce []byte, material ...[]byte) error {
+	// Check params.
+	if len(material) < 1 || len(material[0]) == 0 || len(nonce) == 0 {
+		return errors.New("must supply at least one key and a nonce as key material")
+	}
+
+	// Setup KDF.
+	// Use nonce as kdf context.
+	h := blake3.NewDeriveKey(string(nonce))
+	// Then add all the key material.
+	for _, m := range material {
+		_, _ = h.Write(m)
+	}
+	// Get key reader.
+	keyder.reader = h.Digest()
+
+	return nil
+}
+
+// DeriveKey implements the ToolLogic interface.
+func (keyder *KDF) DeriveKey(size int) ([]byte, error) {
+	key := make([]byte, size)
+	return key, keyder.DeriveKeyWriteTo(key)
+}
+
+// DeriveKeyWriteTo implements the ToolLogic interface.
+func (keyder *KDF) DeriveKeyWriteTo(newKey []byte) error {
+	n, err := io.ReadFull(keyder.reader, newKey)
+	if err != nil {
+		return fmt.Errorf("failed to generate key: %w", err)
+	}
+	if n != len(newKey) {
+		return errors.New("failed to generate key: EOF")
+	}
+	return nil
+}
diff --git a/tools/gostdlib/rsa-pss.go b/tools/gostdlib/rsa-pss.go
index 5038f09..a603f05 100644
--- a/tools/gostdlib/rsa-pss.go
+++ b/tools/gostdlib/rsa-pss.go
@@ -2,6 +2,7 @@ package gostdlib
 
 import (
 	"crypto/rsa"
+	"errors"
 
 	"github.com/safing/jess/tools"
 )
@@ -38,11 +39,14 @@ func (pss *RsaPSS) Sign(data, associatedData []byte, signet tools.SignetInt) ([]
 	if err != nil {
 		return nil, err
 	}
+	if pss.HashTool().CryptoHashID == 0 {
+		return nil, errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions")
+	}
 
 	return rsa.SignPSS(
 		pss.Helper().Random(),
 		rsaPrivKey,
-		pss.HashTool().Hash,
+		pss.HashTool().CryptoHashID,
 		hashsum,
 		nil, // *rsa.PSSOptions
 	)
@@ -59,10 +63,13 @@ func (pss *RsaPSS) Verify(data, associatedData, signature []byte, signet tools.S
 	if err != nil {
 		return err
 	}
+	if pss.HashTool().CryptoHashID == 0 {
+		return errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions")
+	}
 
 	return rsa.VerifyPSS(
 		rsaPubKey,
-		pss.HashTool().Hash,
+		pss.HashTool().CryptoHashID,
 		hashsum,
 		signature,
 		nil, // *rsa.PSSOptions