Commit 0fa2621

mo khan <mo@mokhan.ca>
2025-05-06 20:55:52
feat: add ability to verify an HMAC signature
1 parent 4b35870
Changed files (3)
pkg/crypt/hmac.go
@@ -12,15 +12,6 @@ type HMACSigner struct {
 	factory func() hash.Hash
 }
 
-func (s *HMACSigner) Sign(data []byte) ([]byte, error) {
-	mac := hmac.New(s.factory, s.key)
-	_, err := mac.Write(data)
-	if err != nil {
-		return nil, err
-	}
-	return mac.Sum(nil), nil
-}
-
 func WithAlgorithm(factory x.Factory[hash.Hash]) x.Option[*HMACSigner] {
 	return x.With[*HMACSigner](func(item *HMACSigner) {
 		item.factory = factory
@@ -32,3 +23,21 @@ func WithKey(key []byte) x.Option[*HMACSigner] {
 		item.key = key
 	})
 }
+
+func (s *HMACSigner) Sign(data []byte) ([]byte, error) {
+	mac := hmac.New(s.factory, s.key)
+	_, err := mac.Write(data)
+	if err != nil {
+		return nil, err
+	}
+	return mac.Sum(nil), nil
+}
+
+func (s *HMACSigner) Verify(data []byte, signature []byte) bool {
+	actual, err := s.Sign(data)
+	if err != nil {
+		return false
+	}
+
+	return hmac.Equal(actual, signature)
+}
pkg/crypt/hmac_test.go
@@ -6,7 +6,6 @@ import (
 	"crypto/sha1"
 	"crypto/sha256"
 	"crypto/sha512"
-	"fmt"
 	"hash"
 	"testing"
 
@@ -16,22 +15,29 @@ import (
 )
 
 func TestHMAC(t *testing.T) {
-	t.Run("Sign", func(t *testing.T) {
-		data := x.Must(pls.GenerateRandomBytes(64))
-
-		for _, hash := range []x.Factory[hash.Hash]{md5.New, sha1.New, sha256.New, sha512.New} {
-			t.Run(fmt.Sprintf("generates an HMAC %v signature", hash), func(t *testing.T) {
-				key := x.Must(pls.GenerateRandomBytes(32))
-				signer := x.New[*HMACSigner](WithKey(key), WithAlgorithm(hash))
-				mac := hmac.New(hash, key)
-				mac.Write(data)
-				expected := mac.Sum(nil)
-
-				result := x.Must(signer.Sign(data))
-
-				assert.NotEmpty(t, result)
-				assert.Equal(t, expected, result)
-			})
-		}
-	})
+	data := x.Must(pls.GenerateRandomBytes(64))
+
+	for _, hash := range []x.Factory[hash.Hash]{md5.New, sha1.New, sha256.New, sha512.New} {
+		key := x.Must(pls.GenerateRandomBytes(32))
+		signer := x.New[*HMACSigner](WithKey(key), WithAlgorithm(hash))
+
+		mac := hmac.New(hash, key)
+		mac.Write(data)
+		expectedSignature := mac.Sum(nil)
+
+		t.Run("Sign", func(t *testing.T) {
+			result := x.Must(signer.Sign(data))
+
+			assert.NotEmpty(t, result)
+			assert.Equal(t, expectedSignature, result)
+		})
+
+		t.Run("Verify", func(t *testing.T) {
+			assert.True(t, signer.Verify(data, expectedSignature))
+
+			assert.False(t, signer.Verify(data, []byte{}))
+			assert.False(t, signer.Verify(data, x.Must(pls.GenerateRandomBytes(32))))
+			assert.False(t, signer.Verify(x.Must(pls.GenerateRandomBytes(32)), expectedSignature))
+		})
+	}
 }
pkg/crypt/signer.go
@@ -2,4 +2,5 @@ package crypt
 
 type Signer interface {
 	Sign([]byte) ([]byte, error)
+	Verify([]byte, []byte) bool
 }