diff --git a/nhp/test/utils_test.go b/nhp/test/utils_test.go index 042ee6b2b..a9d3046bc 100644 --- a/nhp/test/utils_test.go +++ b/nhp/test/utils_test.go @@ -30,6 +30,24 @@ func TestGenerateUUIDv4(t *testing.T) { fmt.Println("uuid: ", uuid) } +func TestGetRandomUint32(t *testing.T) { + const draws = 1000 + seen := make(map[uint32]struct{}, draws) + for i := 0; i < draws; i++ { + value := utils.GetRandomUint32() + if value == 0 { + t.Fatal("GetRandomUint32 returned zero") + } + seen[value] = struct{}{} + } + // One collision is allowed to keep this statistical test comfortably + // below the birthday-bound flake probability while still detecting a + // stuck or low-entropy source. + if len(seen) < draws-1 { + t.Fatalf("GetRandomUint32 produced %d unique values across %d draws", len(seen), draws) + } +} + func TestIPTables(t *testing.T) { iptables, err := utils.NewIPTables() diff --git a/nhp/utils/utils.go b/nhp/utils/utils.go index e5ffb53f7..8a547f1e0 100644 --- a/nhp/utils/utils.go +++ b/nhp/utils/utils.go @@ -1,10 +1,11 @@ package utils import ( + "crypto/rand" + "encoding/binary" "encoding/json" "fmt" "io" - "math/rand" "net/http" "os" "path/filepath" @@ -16,19 +17,19 @@ import ( "github.com/OpenNHP/opennhp/nhp/log" ) -// GetRandomUint32 returns a non-zero random uint32 for packet preamble obfuscation. -// Uses math/rand which is sufficient for this non-cryptographic use case. -// -//nolint:gosec // G404: math/rand is intentional - used for packet obfuscation, not security -func GetRandomUint32() (r uint32) { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) +// GetRandomUint32 returns a uniformly random non-zero uint32 from the system +// CSPRNG. Zero is excluded so callers can use the result directly as an XOR +// mask without a degenerate all-zero preamble. +func GetRandomUint32() uint32 { + var b [4]byte for { - r = rng.Uint32() - if r != 0 { - break + if _, err := rand.Read(b[:]); err != nil { + panic(fmt.Sprintf("utils.GetRandomUint32: crypto/rand failed: %v", err)) + } + if value := binary.BigEndian.Uint32(b[:]); value != 0 { + return value } } - return r } func CatchPanic() {