Skip to main content

TSIG Authentication

Transaction Signature (TSIG) provides cryptographic authentication for DNS messages, primarily used to secure zone transfers (AXFR/IXFR) and NOTIFY messages between DNS servers.

Overview

TSIG uses shared secret keys and HMAC algorithms to sign DNS messages, ensuring that:
  • Messages come from an authenticated source
  • Messages have not been tampered with
  • Messages are received within an acceptable time window
From tsig.c:5, NSD implements RFC 2845 TSIG with support for multiple HMAC algorithms.
TSIG provides transaction-level security, not data-level security. For data authentication, use DNSSEC. TSIG is ideal for securing server-to-server communications.

TSIG Architecture

Key Components

  1. Shared Secret: Base64-encoded key material known to both parties
  2. Algorithm: HMAC function used for signing (MD5, SHA1, SHA256, SHA384, SHA512)
  3. Key Name: Domain name identifying the key
  4. MAC: Message Authentication Code computed over the DNS message
  5. Time Values: Timestamp and fudge factor for replay protection

Signing Process

From the source code (tsig.c:484):
void tsig_sign(tsig_record_type *tsig)
{
    uint64_t current_time = (uint64_t) time(NULL);
    tsig->signed_time_high = (uint16_t) (current_time >> 32);
    tsig->signed_time_low = (uint32_t) current_time;
    tsig->signed_time_fudge = 300; /* 5 minutes */

    tsig_digest_variables(tsig, tsig->response_count > 1);

    tsig->algorithm->hmac_final(tsig->context,
                                tsig->prior_mac_data,
                                &tsig->prior_mac_size);

    tsig->mac_size = tsig->prior_mac_size;
    tsig->mac_data = tsig->prior_mac_data;
}

Supported Algorithms

NSD supports multiple HMAC algorithms for TSIG. From tsig.c:236:
tsig_algorithm_type * tsig_get_algorithm_by_name(const char *name)
{
    // Supports: hmac-md5, hmac-sha1, hmac-sha224, 
    //           hmac-sha256, hmac-sha384, hmac-sha512
    // Can also use short names: md5, sha1, sha256, etc.
}

Algorithm Comparison

AlgorithmKey SizeSecurityPerformanceRecommendation
hmac-md5128 bitsWeakFastestLegacy only
hmac-sha1160 bitsDeprecatedFastAvoid
hmac-sha224224 bitsGoodMediumAcceptable
hmac-sha256256 bitsStrongMediumRecommended
hmac-sha384384 bitsVery StrongSlowerHigh security
hmac-sha512512 bitsVery StrongSlowerHigh security
hmac-md5 and hmac-sha1 are cryptographically weak and should only be used for compatibility with legacy systems. New deployments must use hmac-sha256 or stronger.

Configuration

Key Generation

1
Generate Secret
2
Generate a cryptographically random secret:
3
# SHA-256 key (32 bytes = 256 bits)
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64

# SHA-512 key (64 bytes = 512 bits)
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64
4
Alternative: Using OpenSSL
5
# Generate 256-bit key
openssl rand -base64 32

# Generate 512-bit key
openssl rand -base64 64
6
Define Key in NSD
7
Add the key to nsd.conf:
8
key:
    name: "transfer-key"
    algorithm: hmac-sha256
    secret: "6KM6qiKfwfEpamEq72HQdA=="

Basic TSIG Configuration

# Define the TSIG key
key:
    name: "transfer-key"
    algorithm: hmac-sha256
    secret: "your-base64-secret-here=="

# Primary zone allowing transfers with TSIG
zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    
    # Allow zone transfers only with TSIG
    provide-xfr: 192.0.2.10 transfer-key
    provide-xfr: 2001:db8::10 transfer-key
    
    # Send notifies with TSIG
    notify: 192.0.2.10 transfer-key
    notify: 2001:db8::10 transfer-key

Multiple Keys Configuration

For different security levels or key rotation:
# Production transfer key
key:
    name: "prod-xfr-key"
    algorithm: hmac-sha256
    secret: "productionkeysecret=="

# Staging transfer key
key:
    name: "staging-xfr-key"
    algorithm: hmac-sha256
    secret: "stagingkeysecret=="

# Management key (notify only)
key:
    name: "mgmt-notify-key"
    algorithm: hmac-sha256
    secret: "managementkeysecret=="

zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    
    # Multiple servers with different keys
    provide-xfr: 192.0.2.10 prod-xfr-key
    provide-xfr: 192.0.2.20 staging-xfr-key
    
    notify: 192.0.2.10 prod-xfr-key
    notify: 192.0.2.20 staging-xfr-key
    notify: 192.0.2.100 mgmt-notify-key

TSIG with Zone Transfers

AXFR with TSIG

# Force AXFR (no IXFR) with TSIG authentication
zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    request-xfr: AXFR 192.0.2.1 transfer-key
    allow-notify: 192.0.2.1 transfer-key

IXFR over UDP with TSIG

When using UDP for IXFR, TSIG is critical for security. UDP transfers without TSIG are vulnerable to spoofing attacks.
zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    
    # IXFR over UDP (requires TSIG for security)
    request-xfr: UDP 192.0.2.1 transfer-key
    allow-notify: 192.0.2.1 transfer-key

Access Control with TSIG

Query ACLs with TSIG

Restrict queries to specific addresses and keys:
zone:
    name: "internal.example.com"
    zonefile: "/var/nsd/zones/internal.example.com.zone"
    
    # Allow queries only from specific networks with TSIG
    allow-query: 10.0.0.0/8 internal-key
    allow-query: 172.16.0.0/12 internal-key
    
    # Block all other queries
    allow-query: 0.0.0.0/0 BLOCKED
    allow-query: ::0/0 BLOCKED

Combining NOKEY and TSIG

zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    
    # Allow transfers from trusted network without TSIG
    provide-xfr: 192.0.2.0/24 NOKEY
    
    # Require TSIG from untrusted networks
    provide-xfr: 0.0.0.0/0 secure-transfer-key
    
    # Block specific networks entirely
    provide-xfr: 203.0.113.0/24 BLOCKED

Time-Based Security

Time Validation

TSIG includes timestamp validation to prevent replay attacks. From tsig.c:386:
int tsig_from_query(tsig_record_type *tsig)
{
    signed_time = ((((uint64_t) tsig->signed_time_high) << 32) |
                   ((uint64_t) tsig->signed_time_low));
    
    current_time = (uint64_t) time(NULL);
    
    // Check if time is within fudge window (default 300 seconds)
    if ((current_time < signed_time - tsig->signed_time_fudge)
        || (current_time > signed_time + tsig->signed_time_fudge))
    {
        tsig->error_code = TSIG_ERROR_BADTIME;
        return 0;
    }
}
Default fudge: 300 seconds (5 minutes)
Ensure server clocks are synchronized using NTP. Clock skew exceeding the fudge window (300 seconds) will cause TSIG validation failures.

Time Synchronization

# Check time sync status
timedatectl status

# Verify NTP is active
systemctl status systemd-timesyncd
# or
systemctl status ntpd

# Check clock offset
ntpq -p

Error Codes

From tsig.h:20 and tsig.c:260:
Error CodeNameDescription
0NOERRORValidation successful
16BADSIGMAC validation failed
17BADKEYKey not found or algorithm mismatch
18BADTIMETime not within fudge window

Handling Errors

const char * tsig_error(int error_code)
{
    switch (error_code) {
    case TSIG_ERROR_NOERROR:
        return "No Error";
    case TSIG_ERROR_BADSIG:
        return "Bad Signature";
    case TSIG_ERROR_BADKEY:
        return "Bad Key";
    case TSIG_ERROR_BADTIME:
        return "Bad Time";
    }
}

Key Management

Key Rotation

1
Add New Key
2
Add the new key alongside the old key:
3
key:
    name: "old-transfer-key"
    algorithm: hmac-sha256
    secret: "oldkeysecret=="

key:
    name: "new-transfer-key"
    algorithm: hmac-sha256
    secret: "newkeysecret=="
4
Configure Both Keys
5
Configure servers to accept both keys:
6
# On primary
zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    provide-xfr: 192.0.2.10 old-transfer-key
    provide-xfr: 192.0.2.10 new-transfer-key
    notify: 192.0.2.10 new-transfer-key
7
Update Secondaries
8
Update each secondary to use the new key:
9
zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    request-xfr: AXFR 192.0.2.1 new-transfer-key
    allow-notify: 192.0.2.1 old-transfer-key
    allow-notify: 192.0.2.1 new-transfer-key
10
Remove Old Key
11
After all secondaries are updated and confirmed working:
12
# Remove old key from configuration
key:
    name: "new-transfer-key"
    algorithm: hmac-sha256
    secret: "newkeysecret=="

zone:
    name: "example.com"
    zonefile: "/var/nsd/zones/example.com.zone"
    provide-xfr: 192.0.2.10 new-transfer-key
    notify: 192.0.2.10 new-transfer-key

Key Storage Security

Protect your TSIG keys:
# Store keys in separate file with restricted permissions
chmod 600 /etc/nsd/keys.conf
chown root:nsd /etc/nsd/keys.conf

# Verify permissions
ls -la /etc/nsd/keys.conf
# Should show: -rw------- 1 root nsd

Debugging and Monitoring

Enable TSIG Logging

server:
    verbosity: 2
At verbosity 2, NSD logs:
  • TSIG key validation failures
  • BADTIME errors
  • BADSIG errors

Test TSIG with dig

# Query with TSIG (using tsig-keygen format)
dig @192.0.2.1 example.com AXFR \
    -y hmac-sha256:transfer-key:6KM6qiKfwfEpamEq72HQdA==

# Alternative format
dig @192.0.2.1 example.com AXFR \
    -k /path/to/key.tsig

Key File Format for dig

# /path/to/key.tsig
key "transfer-key" {
    algorithm hmac-sha256;
    secret "6KM6qiKfwfEpamEq72HQdA==";
};

Common Issues

Symptom: Transfer fails with TSIG BADTIME errorCause: Clock skew between servers exceeds 300 secondsSolution:
# Check time difference
date -u; ssh remote-server date -u

# Sync clocks with NTP
ntpdate pool.ntp.org
# or
timedatectl set-ntp true
Symptom: Transfer fails with TSIG BADSIG errorCause:
  • Mismatched secrets
  • Different algorithms
  • Trailing whitespace in secret
Solution:
# Verify key configuration matches on both servers
nsd-checkconf /etc/nsd/nsd.conf | grep -A3 "name: transfer-key"

# Generate new key and reconfigure if needed
openssl rand -base64 32
Symptom: Transfer fails with TSIG BADKEY errorCause:
  • Key name mismatch
  • Key not defined in configuration
  • Case sensitivity in key name
Solution:
# List configured keys
nsd-checkconf /etc/nsd/nsd.conf | grep "name:"

# Ensure key name matches exactly (case-sensitive)

Performance Considerations

Algorithm Performance

From benchmarks, relative performance:
  • hmac-md5: 1.0× (baseline, but insecure)
  • hmac-sha256: 0.8× (recommended)
  • hmac-sha512: 0.6×
The performance difference is negligible for zone transfers. Choose hmac-sha256 or stronger for security, not performance.

Memory Usage

TSIG context uses minimal memory:
struct tsig_record
{
    void *context;                    // HMAC context (~200 bytes)
    uint8_t *prior_mac_data;          // Previous MAC (32-64 bytes)
    uint8_t *mac_data;                // Current MAC (32-64 bytes)
    region_type *rr_region;           // Memory region
    region_type *context_region;      // HMAC context region
};
Typical memory: 500-1000 bytes per active TSIG operation

Build docs developers (and LLMs) love