Skip to content

Integrity Signing ​

DD provides an HMAC-based log-integrity signing mechanism that can verify log entries have not been tampered with.

IntegritySigner ​

Log-entry signer supporting HMAC signing and monotonic sequence-number tracking (for after-the-fact detection of missing/replayed entries; the caller must compare sequence numbers themselves).

Creation ​

go
func NewIntegritySigner(cfg IntegrityConfig) (*IntegritySigner, error)

Creates a signer using the provided IntegrityConfig. You can use DefaultIntegrityConfigSafe() to generate a cryptographically secure random key.

Errors are returned when: SecretKey is fewer than 32 bytes, or HashAlgorithm is unsupported.

Key security

NewIntegritySigner copies the provided SecretKey and immediately zeroes the original cfg.SecretKey (to prevent key material from lingering in two memory locations), so the passed config cannot be reused to create a second signer — call IntegrityConfig.Clone() on the config or generate a fresh key per signer. The caller should still avoid exposing the original key in logs or serialized output.

go
// Safe creation (recommended for production)
cfg, err := dd.DefaultIntegrityConfigSafe()
if err != nil {
    log.Fatal(err)
}
signer, err := dd.NewIntegritySigner(cfg)
if err != nil {
    log.Fatal(err)
}

// Custom config
cfg := dd.IntegrityConfig{
    SecretKey:      []byte("my-secret-key-that-is-at-least-32b!"),
    HashAlgorithm:  dd.HashAlgorithmSHA256,
    IncludeTimestamp: true,
    IncludeSequence:  true,
}
signer, err = dd.NewIntegritySigner(cfg)
if err != nil {
    log.Fatal(err)
}

Signing Methods ​

Sign ​

go
func (s *IntegritySigner) Sign(message string) string

Generates an HMAC signature for a log message. Thread-safe; safe to call concurrently.

go
sig := signer.Sign("user login admin 192.168.1.1")
// -> "[SIG:1713456789000000000:1:base64signature...]"

SignFields ​

go
func (s *IntegritySigner) SignFields(message string, fields []Field) string

Generates a signature for a message with fields; the signature covers the message and all field values. Thread-safe; safe to call concurrently.

go
sig := signer.SignFields("user login", []dd.Field{
    dd.String("user", "admin"),
    dd.String("ip", "192.168.1.1"),
})

Verification Methods ​

Verify ​

go
func (s *IntegritySigner) Verify(entry string) (*LogIntegrity, error)

Verifies the integrity of a log entry. Thread-safe; safe to call concurrently. When no signature is found in the entry, returns Valid=false (Message is the whole entry, with no error); the legacy signature format (bare base64 without timestamp/sequence metadata) is also accepted.

go
integrity, err := signer.Verify(signedEntry)
if err != nil {
    // Verification error (e.g. signer is nil)
}
if !integrity.Valid {
    // Invalid signature: signature mismatch or malformed
}
if integrity.Sequence != expectedSeq {
    // Sequence-number discontinuity: entries may have been removed
}

Other Methods ​

MethodSignatureDescription
GetSequence() uint64Current sequence number
ResetSequence()Reset the sequence number
Stats() IntegrityStatsSigning statistics

IntegrityConfig ​

Signing configuration.

go
type IntegrityConfig struct {
    SecretKey        []byte        // HMAC key (SHA-256 requires >= 32 bytes; store securely and rotate regularly)
    HashAlgorithm    HashAlgorithm // Hash algorithm (default SHA256)
    IncludeTimestamp bool          // Sign includes timestamp
    IncludeSequence  bool          // Sign includes a monotonically increasing sequence number (Verify result carries it; the caller must track it to detect replay/reorder)
    SignaturePrefix  string        // Signature prefix (default "[SIG:"; when empty NewIntegritySigner fills the default)
}

Safe Creation ​

go
func DefaultIntegrityConfigSafe() (IntegrityConfig, error)

Safely creates the default config (auto-generates a cryptographically secure 32-byte random key; timestamp and sequence inclusion are on by default). Returns an error instead of panicking if key generation fails. Recommended for production use.

Methods ​

MethodSignatureDescription
Validate() errorValidate config legality (SecretKey must be >= 32 bytes; HashAlgorithm must be supported)
Clone() IntegrityConfigDeep-copy config (SecretKey is copied to a new slice)
MarshalJSON() ([]byte, error)JSON serialization (the key itself is not serialized; only secretKeyLength is output)
go
cfg, err := dd.DefaultIntegrityConfigSafe()
if err != nil {
    log.Fatal(err)
}
signer, err := dd.NewIntegritySigner(cfg)
if err != nil {
    log.Fatal(err)
}

LogIntegrity ​

Log integrity verification result.

go
type LogIntegrity struct {
    Valid     bool       // Whether the signature is valid
    Timestamp time.Time  // Signature timestamp
    Sequence  uint64     // Sequence number
    Message   string     // Original message
}

IntegrityStats ​

Signing statistics.

go
type IntegrityStats struct {
    Sequence         uint64 // Current sequence number
    Algorithm        string // Algorithm name
    IncludeTimestamp bool   // Whether timestamp is included
    IncludeSequence  bool   // Whether sequence is included
}

HashAlgorithm ​

ConstantDescription
HashAlgorithmSHA256SHA-256 algorithm

Implements a String() method returning the algorithm name.

Complete Examples ​

Log Signing Flow ​

go
cfg, err := dd.DefaultIntegrityConfigSafe()
if err != nil {
    log.Fatal(err)
}
signer, err := dd.NewIntegritySigner(cfg)
if err != nil {
    log.Fatal(err)
}

// Sign a log
message := "user login"
signature := signer.Sign(message)

// Store the signed log entry
logEntry := message + signature

// Verify the log
result, err := signer.Verify(logEntry)
if err != nil {
    fmt.Println("integrity verification failed:", err)
} else if result.Valid {
    fmt.Printf("verified - sequence: %d\n", result.Sequence)
}

Audit Integration ​

go
cfg, err := dd.DefaultIntegrityConfigSafe()
if err != nil {
    log.Fatal(err)
}
signer, err := dd.NewIntegritySigner(cfg)
if err != nil {
    log.Fatal(err)
}

auditCfg := dd.DefaultAuditConfig()
auditCfg.IntegritySigner = signer
audit, _ := dd.NewAuditLogger(auditCfg)
defer audit.Close()

// Audit logs are signed automatically
audit.Log(dd.AuditEvent{
    Type:     dd.AuditEventSecurityViolation,
    Message:  "SQL injection attempt",
    Severity: dd.AuditSeverityCritical,
    Metadata: map[string]any{"input": "' OR 1=1"},
})

// Verify the audit log
stats := signer.Stats()
fmt.Printf("algorithm: %s, sequence: %d\n", stats.Algorithm, stats.Sequence)

Next Steps ​