The first step for me was to convert the original tab-delimited wordlist (diceware_wordlist_asc.txt) into an XML-formatted one. This will allow us to use XML query commands, rather than parsing a wordlist line by line. The original wordlist (included unmodified in the source code download) contains the author’s PGP signature, which should be removed prior to XML processing. To prevent downloading of the wordlist, we'll give it a .config extension (wordlist.config), which is mapped to HttpForbiddenHandler. You could also use database storage of the wordlist if you wanted. I have included the class I used to convert the wordlist (wordlist.vb), and the page used to control the conversion (ConvertWordlist.aspx).
Now that we have a word list in an easily queryable format, we’ll use another class to generate our passphrases. We’ll call the class Passphrase, and include a constructor and a GeneratePassphrase method. In our example, we’ll generate a passphrase consisting of two words and a two-digit number. We’ll need five randomly chosen numbers between 1 and 6 for each word, and a randomly chosen two-digit number. To generate the five randomly chosen numbers, we’ll use five different pseudo-random number generators (PRNGs), with five different initialization vectors (IVs, which are numbers used by the PRNGs when they are created to try and ensure a greater degree of uniqueness). The first two IVs will be based on the number of ticks on the processor’s clock; the first IV will be the last 8 digits of this value, and the second IV will be the last 5 digits of this value. The other IVs will be the day of year, day of week, and the two multiplied. The IVs will be created when the class constructor is called, and are suitable for a system that is only used to generate a couple of passphrases on any given day. If you will be generating a large number of passphrases, you may need more variation in your IVs, and reference (11) RFC 4086 (RFC4086) is a recommended source for suggestions. The initialization vectors are shown in Listing 1.
Listing 1: Initialization Vectors
Dim _IV1 As Integer = CInt(Right(CStr(DateTime.Now.Ticks), 8))
Dim _IV2 As Integer = CInt(Right(CStr(DateTime.Now.Ticks), 5))
Dim _IV3 As Integer = DatePart(DateInterval.DayOfYear, Today())
Dim _IV4 As Integer = DatePart(DateInterval.Weekday, Today())
Dim _IV5 As Integer = _IV3 * _IV4
The second step in generating our passphrase is to generate five random numbers between 1 and 6, and concatenate them into a single value. We’ll use the .NET Framework’s Random class for this, as shown in Listing 2. We’ll also generate our two-digit number at this time, using one of the random numbers as well. This will also be performed when the constructor is called.
Listing 2: Simulating Rolls of Five Dice
Dim _Rand1 As New Random(_IV1)
Dim _Rand2 As New Random(_IV2)
Dim _Rand3 As New Random(_IV3)
Dim _Rand4 As New Random(_IV4)
Dim _Rand5 As New Random(_IV5)
_RV1 = CStr(_Rand1.Next(1, 6)) & CStr(_Rand2.Next(1, 6)) _
& CStr(_Rand3.Next(1, 6)) & CStr(_Rand4.Next(1, 6)) & CStr(_Rand4.Next(1, 6))
_RV2 = CStr(_Rand5.Next(1, 6)) & CStr(_Rand4.Next(1, 6)) _
& CStr(_Rand3.Next(1, 6)) & CStr(_Rand2.Next(1, 6)) & CStr(_Rand1.Next(1, 6))
_RV3 = CInt(_Rand2.NextDouble * 100)
The final step is cross-referencing our dice rolls with our wordlist, and concatenating the two words and two-digit number together. The .NET Framework makes operations such as this very easy, which is one reason why we converted the word list to XML in the first place. We simply need to load the wordlist into an XmlDataDocument object, and then execute an XPath query to retrieve the nodes that correspond to our simulated dice rolls. Finally, we concatenate the inner text of the ‘word’ elements (which is the last child of each node retrieved) with our two-digit number, and return our passphrase.
Listing 3: Querying the XML
Dim _WordList As New XmlDataDocument
_Query1 = "descendant::listWord[code='" & _RV1 & "']"
_Query2 = "descendant::listWord[code='" & _RV2 & "']"
Dim _node1 As XmlNode = _WordList.SelectSingleNode(_Query1)
Dim _node2 As XmlNode = _WordList.SelectSingleNode(_Query2)
_Word1 = _node1.LastChild.InnerText
_Word2 = _node2.LastChild.InnerText
_Phrase = _Word1 & " " & _Word2 & " " & CStr(_RV3)
The complete code is included in the sample project.