# DNSSEC key tag (keyid) and DS signature calculation in python

This one took me a considerable amount of hours to figure out so here it is.

While trying to automate DNS zone generation I had to calculate some of the values programmatically. Two of the auto-generated values had to do with DNSSEC entries: The key tag (or keyid) and the DS record’s signatures.

The required details on how these are calculated are found in the following places:

For the calculations you need to provide the following:

• For the key tag: flags, protocol, algorithm, public key
• For the DS signatures: owner (the domain name), flags, protocol, algorithm, public key

### Code

I used python for this but the approach is the same for other languages since the algorithms are the same.

So here it is:

```import struct
import hashlib
import base64

def calc_keyid(flags, protocol, algorithm, st):
"""
@param owner        The corresponding domain
@param flags        The flags of the entry (256 or 257)
@param protocol     Should always be 3
@param algorithm    Should always be 5
@param st           The public key as listed in the DNSKEY record.
Spaces are removed.
@return The key tag
"""
# Remove spaces and create the wire format
st0=st.replace(' ', '')
st2=struct.pack('!HBB', int(flags), int(protocol), int(algorithm))
st2+=base64.b64decode(st0)

# Calculate the tag
cnt=0
for idx in xrange(len(st2)):
s=struct.unpack('B', st2[idx])
if (idx % 2) == 0:
cnt+=s<<8
else:
cnt+=s

ret=((cnt & 0xFFFF) + (cnt>>16)) & 0xFFFF

return(ret)

def calc_ds(owner, flags, protocol, algorithm, st):
"""
@param flags        Usually it is 257 or something that indicates a KSK.
It can be 256 though.
@param protocol     Should always be 3
@param algorithm    Should always be 5
@param st           The public key as listed in the DNSKEY record.
Spaces are removed.
@return A dictionary of hashes where the key is the hashing algorithm.
"""

# Remove spaces and create the wire format
st0=st.replace(' ', '')
st2=struct.pack('!HBB', int(flags), int(protocol), int(algorithm))
st2+=base64.b64decode(st0)

# Ensure a trailing dot
if owner[-1]=='.':
owner2=owner
else:
owner2=owner+'.'

# Create the name wire format
owner3=''
for i in owner2.split('.'):
owner3+=struct.pack('B', len(i))+i

# Calculate the hashes
st3=owner3+st2
ret={
'sha1':    hashlib.sha1(st3).hexdigest().upper(),
'sha256':  hashlib.sha256(st3).hexdigest().upper(),
}

return(ret)
```

### Data

The following were created by bind’s dnssec tools:

```\$ cat Ktest.hell.gr.+005+33630.key
; This is a zone-signing key, keyid 33630, for test.hell.gr.
; Created: 20101007114826 (Thu OctÂ  7 14:48:26 2010)
; Publish: 20101007114826 (Thu OctÂ  7 14:48:26 2010)
; Activate: 20101007114826 (Thu OctÂ  7 14:48:26 2010)
test.hell.gr. IN DNSKEY 256 3 5 AwEAAb+lTDjZCfq7D5N9cNd1ug30wLrbCXB9mVJJQGlQQHpiHHlMaLGG
sV2/j5+eojHp+WQUzNpOzrULF6msbEvUuV2gSEnpbueRV4twO8muGE+x
eUuseSoHh/aTpA8Z9SPubb01mduqqaUEN5Juz2Q4hF0dSUSJYlJPKhp6
NrOgoeyj

\$ cat dsset-test.hell.gr.
test.hell.gr.Â Â  Â Â Â  Â IN DS 33630 5 1 A2AD2648B353365631EBC9C70EDA1E0C04563FCC
test.hell.gr.Â Â  Â Â Â  Â IN DS 33630 5 2 4177EAEC09A37178357871EBE3FB361CABB2861F12A1D51DDE18CBA2 439BB5C1
```

### Result

```>>> domain='test.hell.gr'
>>> flags=256
>>> protocol=3
>>> algorithm=5
>>> key='AwEAAb+...goeyj' # Truncated
>>> calc_keyid(flags, protocol, algorithm, key)
33630
>>> r=calc_ds(domain, flags, protocol, algorithm, key)
>>> r['sha1']