Random number and password generator using YubiKey in Fortran

Fortran is fast

I have ported over the random_number_generator.py and random_number_password_generator.py Python programs, which use the YubiKey random number generator engine, to generate high-quality random numbers. It’s obvious that a good random number generator is crucial for encryption and other purposes. Given this, one would expect that Yubico has thoroughly tested and produced a highly reliable random number generator (otherwise, security would be at risk). The Python version works fine, but I found the refactored Fortran version to be about two times faster, which was surprising since I assumed the hardware key was the limiting factor.

Porting a Python program I made earlier into Fortran is also a good way to learn Fortran and get some experience.

A significant portion of the refactoring was initially generated by AI, such as with Claude Sonnet 3.5, as a first draft. It sort of worked, but not perfectly. One still requires some understanding of Fortran, much like trying to generate CUDA code with AI. Still, on balance, it saved time and helped with specific problems: so, it was well worth it.

random_number_generator.f90

To begin with, we import the iso_fortran_env library to support integer 32- and 64-bit types. The next line is the mandatory implicit none, a feature, not a bug, of Fortran to turn off the dumb default of implicit typing (don’t ask, it was decades ago).

Next, all variables are declared. The counters are int32 to save space, but I modified the output, maxnum and total_random_numbers to be int64 to enable greater maximum sizes. These numbers are indeed ridiculously large, but you never know when it might be helpful.

By simply compiling and running the initial versions, it was clear that there are marked differences between Python and Fortran. If you input a negative number in the Python version of this program, it simply returns a blank file without crashing or complaining. I didn’t add any error checking to the Python program as it is small and doesn’t crash; if you input an impossibly large number, it doesn’t complain either (it seems to just produce numbers up to a default maximum size and ignores your ridiculous input). Although Python has rules for handling extremely large numbers, the main point is that it does not crash and continues producing seemingly valid output.

In contrast, Fortran handles numbers much more strictly. It’s like driving a manual transmission versus an automatic: if you have a number that exceeds the size of say int32 or int64, it will crash and provide a dump of the bits involved. In fact, hilariously, if you input an extremely large number into Fortran code designed to check whether the number is larger than the maximum size, it will still crash.

Fortunately, AI suggests a way to solve this problem: read the input as a text string first and then convert the characters into an integer number. Interestingly enough, this approach works and prevents the program from crashing. Once the input is identified as too large, the error-checking procedure can then request another input that meets requirements.

Both the input requests for the number of random numbers and the maximum size of any random number call subroutines to first check if the number is too big and also if the number is greater than 0. One subroutine even calls the other, ensuring that the loop will not exit until the input meets requirements. By having each subroutine perform only one task, the code becomes simplified and reusable.

The next part allocates space for the random list array by specifying that the size of the array should be equal to the total number of random numbers set by the user. So if you want 100 random numbers, your random list will be 100 elements long.

The next section creates a FIFO name. This requirement is peculiar to Fortran because Python can simply read command-line output and assign it to a variable. Fortran appears to have difficulty with this and defaults to zero. It seems to have trouble reading certain command-line outputs. The suggestion from the internet and AI is to create a FIFO file, effectively piping the command-line input into a temporary file, which makes it more efficient than creating individual files. A small oddity is that the suggested FIFO routine could crash if the number exceeded 43. Fortunately, I was able to use AI to modify the FIFO sequence to generate a unique FIFO name, avoiding the crash. If there’s one recurring theme, it’s that Fortran tends to crash when resources are exhausted.

The next part of the code is the actual generation of random numbers using the YubiKey hardware key. The entire premise of this program relies on the assumption that Yubico has invested significant effort in generating a high-quality random number generator within their hardware. I don’t wish to delve into that rabbit hole at present. Even verifying that numbers are random enough gets involved. Still, it is a reasonable assumption and it extends the value of the YubiKey.

We begin by initializing num as zero and then looping until it reaches the total random number value:

  1. Execute the command echo SCD random 128 on the command line, which requests a random 128-bit output from the YubiKey. This will not be limited to numbers; instead, it will produce random bits. The GPG Connect agent initiates this process.
  2. The SED command with the backslash capital ‘D’ trims the initial capital ‘D’, which appears as a header in all of the output from the YubiKey (an artefact of the key’s output).
  3. tr -dc 0-9 limits the output to numbers 0 through 9.
  4. All these numbers are then piped into the unique FIFO file using the ‘&’ at the end, which is a feature of the FIFO procedure.

Once a random number is generated and saved to the FIFO file, the program then opens the FIFO file, reads it, and if there are any errors, indicates that. Otherwise, it will transfer that number generated into the output variable. There’s error checking, but the usual io_stat error = -1, which indicates an error reading from the key is probably just the YubiKey being swamped and timing out. Importantly, and here manual intervention and thinking is required, this error doesn’t really matter. The AI-generated code stopped the program, but if you delete the stop and allow it to continue, it will simply generate another random number: it will just keep generating numbers until you reach your maximum. Once that’s finished, the number is added to the random list as long as the output is less than the maximum number, and the program increments the counter and loops.

Once the loop is complete and we have all the random numbers, we call the write_to_csv subroutine with the random list array and the total random numbers. We then save them into a CSV file (which is overwritten each time the program is run).

Finally, housekeeping takes over as the program deallocates the array and deletes the FIFO temporary files.

The contains statement marks the end of the program execution section. What follows are functions, subroutines, and other parts that are referenced in the program.

The getpid function, which is used by FIFO, generates a unique number. The subroutine writing to the CSV uses a hard-coded file name “random_numbers_yubikey_generated.csv.” Using subroutines in Fortran is similar to calling a library in Python, as you can see in line 55. Instead of having long blocks of hard to read code, you simply make a one-line call to a subroutine.

The other two subroutines I created for error checking were needed to avoid duplicate code, as it was used more than once. As you can see in lines 14 and 15, and 18 and 19, you simply call read_max and call check_max, much like in Python.

The read_max subroutine is nearly akin to a mini-program, as it declares variables and parameters for the maximum number allowed. The underscore 8 is necessary in Fortran to indicate an int64 number; without it, the compiler complains. The character variable, which has an arbitrary length of 200, represents the line number that would be the input variable. If someone types a number with a hundred digits, the program can handle it without crashing, as the error checking routine complains that the number is larger than the maximum allowed.

The subroutine begins by reading the line as text characters, indicated by the '(A)' formatting, which reads it as alphanumeric. It then converts this line into an integer. The primary concern is to ensure that the input is a valid number.

The second subroutine, check_max, calls the previous routine to verify if the number is too large. Since we need to read in the input as a text string initially and then convert it into a number to avoid crashing the program, this initial step is contained within its own subroutine. This second routine performs normal error checking to ensure that the value entered meets requirements (i.e., is not too small or large) and loops until the value meets these criteria.

These two routines collectively verify that the input is greater than zero and less than the maximum number, preventing the program from crashing due to an extremely large number input. However, separating these procedures into distinct subroutines is important for maintaining organization and modularity. Once a valid number is obtained, the rest of the program can proceed.

random_password_generator.f90

The second program, the password generation program, is relatively straightforward. We start with num at zero and then loop while the number is less than the total random numbers (hardcoded for simplicity). The same command on the command line generates random numbers. Since the output is simply displayed to the screen (not saved to a file or variable), we don’t need to concern ourselves with setting up a FIFO procedure to capture the command-line output. In addition to digits, letters and symbols are allowed.

The output is not saved for security reasons. By copying a password to your password manager, you should not leave it unencrypted in storage for example, the password should be safer (although, one could suppose that a screen recorder could still watch you copy and paste the selected password – unless you do it by hand–and no screen recorder will know which password you picked). In an era where passwords will likely be overtaken by the passkey, this small program could still be useful because no amount of hacking on your computer would affect or corrupt password generation as it is performed on the YubiKey hardware device itself, not within the computer.

Check it out on github: random_number_generator.f90 and random_password_generator.f90.

Using the intel compiler, “ifx -O3 -static-intel random_number_generator7.f90 -o random_number_generator” and “ifx -O3 -static-intel random_password_generator4.f90 -o random_password_generator” compiles the programs in Linux. gfortran is usually included in most Linux distributions and can be used as a Fortran compiler instead if preferred.

Leave a comment