An introduction to hSub
What is an hSub?
hSub is an acronym for Hashed Subject. It's function is to provide a means
for individuals to identify their own messages from within a shared mailbox
using a specially coded Subject header.
What's a shared mailbox?
As its name suggests, a shared mailbox is one used by more than one person.
All of the messages in the mailbox are encrypted and can only be decrypted
by their owner. In the context of this page, the shared mailbox in use is
the Usenet Newsgroup alt.anonymous.messages.
Using an hSub to identify the message owner
Actually it's impossible to identify a message owner using an hSub. The hSub
employs a one-way hashing cipher which, as its name suggests, can only be
encoded, not decoded. This is an important concept to grasp in understanding
an hSub. The only way to verify if a message belongs to an individual is to
encode another hSub using the same criteria as was used to generate the one
being checked. If the criteria matches, the resulting hSub will be identical.
This is known as a hash collision.
Getting technical
An hSub consists of two parts, a random number and a passphrase. The random
number is public but the passphrase is private. The random number is always
generated by the server or service and is openly included in the published
hSub. Each user of the shared mailbox then tries to generate an hSub using
that same random number. A collision informs the user that the message being
tested is for them.
The Gory Details
If you just want to use hSubs, you can ignore this section. If you want a
technical understanding, please read on.
As mentioned in the above section, an hSub is generated using a random number
and a passphrase. The actual process is:

The resulting hSub is always 80 hex digits (320bits) but is frequently
truncated, for example, 48 digits to make it indistinguishable from the older
eSub format. When validating an hSub, the length of the original should define
how many bits must collide.
Reference Implementation
The following Python code provides a reference for generating and testing
hSubs.
#!/usr/bin/python
#
# vim: tabstop=4 expandtab shiftwidth=4 autoindent
#
# Copyright (C) 2010 Steve Crook
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
from hashlib import sha256
from os import urandom
HSUBLEN = 48
def hash(text, iv = None, hsublen = HSUBLEN):
"""Create an hSub (Hashed Subject). This is constructed as:
--------------------------------------
| 64bit iv | 256bit SHA2 'iv + text' |
--------------------------------------"""
# Generate a 64bit random IV if none is provided.
if iv is None: iv = cryptorandom()
# Concatenate our IV with a SHA256 hash of text + IV.
hsub = iv + sha256(iv + text).digest()
return hsub.encode('hex')[:hsublen]
def check(text, hsub):
"""Create an hSub using a known iv, (stripped from a passed hSub). If
the supplied and generated hSub's collide, the message is probably for
us."""
# We are prepared to check variable length hsubs within boundaries.
# The low bound is the current Type-I esub length. The high bound
# is the 256 bits within SHA2-256.
hsublen = len(hsub)
# 48 digits = 192 bit hsub, the smallest we allow.
# 80 digits = 320 bit hsub, the full length of SHA256 + 64 bit IV
if hsublen < 48 or hsublen > 80: return False
iv = hexiv(hsub)
if not iv: return False
# Return True if our generated hSub collides with the supplied
# sample.
return hash(text, iv, hsublen) == hsub
def cryptorandom(bytes = 8):
"""Return a string of random bytes. By default we return the default
IV length (64bits)."""
return urandom(bytes)
def hexiv(hsub, digits = 16):
"""Return the decoded IV from an hsub. By default the IV is the first
64bits of the hsub. As it's hex encoded, this equates to 16 digits."""
# We don't want to process IVs of inadequate length.
if len(hsub) < digits: return False
try:
iv = hsub[:digits].decode('hex')
except TypeError:
# Not all Subjects are hSub'd so just bail out if it's non-hex.
return False
return iv
def main():
"""Only used for testing purposes. We Generate an hSub and then check it
using the same input text."""
passphrase = "Pass phrase"
hsub = hash(passphrase)
iv = hexiv(hsub)
print "Passphrase: " + passphrase
print "IV: %s" % iv.encode('hex')
print "hsub: " + hsub
print "hsub length: %d bytes" % len(hsub)
print "Should return True: %s" % check(passphrase, hsub)
print "Should return False: %s" % check('false', hsub)
# Call main function.
if (__name__ == "__main__"):
main()