Secure Programming Techniques, Part 4

Editor's note: In the previous installment in this multipart series of excerpts from Chapter 16 of Practical Unix & Internet Security, 3rd Edition, we offered tips on writing SUID/SGID programs, and on using chroot(). In this week's final excerpt of the series, we have tips on using passwords more securely, and on generating random numbers, both of which play important roles in maintaining computer security.

Tips on Using Passwords

Lots of computer programs use passwords for user authentication. Beyond the standard Unix password, users soon find that they have passwords for special electronic mail
accounts, special accounting programs, and even fantasy role-playing
games.

Few users are good at memorizing passwords, and there is a great
temptation to use a single password for all uses. This is a bad idea.
Users should be encouraged not to type their login password into some
MUD that's running over at the local university, for
example.

As a programmer, there are several steps that you can take in
programs that ask for passwords to make the process more secure:

Don't echo the password as the user types it. Normally, Unix turns off echo when
people type passwords. You can do this yourself by using the
getpass( ) function. In recent years, however, a
trend has evolved to echo asterisks (*) for each character of the
password typed. This provides some help for the person typing the
password to see if they have made a mistake in their typing, but it
also enables somebody looking over the user's shoulders to see how many characters are in the password.

When you store the user's password on the computer,
concatenate a key and a salt, and encrypt the password with a
cryptographically secure one-way function. Never have programs store
passwords in plaintext form in files or databases. If this file is
compromised, all of the passwords need to be changed!

Traditionally, the easy way to store a password on a Unix system was
to use the Unix crypt() library function with a randomly generated salt. For example, the following bit of simple Perl code takes a password in the
$password variable, generates a random salt, and
places an encrypted password in the variable $encrypted_password:[20]

The primary benefit of using a cryptographic hash value is that it
takes whatever input the user types as the password, no matter how
long that value might be. This may encourage users to type longer
passwords or passphrases that will be more resistant to dictionary
attacks. You might also want to remind them of this practice when you
prompt them for new passwords.

Tips on Generating Random Numbers

Random numbers play an important role in
modern computer security. Many programs that use encryption need a
good source of random numbers for producing session keys. For
example, the PGP program uses random numbers for generating a random
key that is used to encrypt the contents of electronic mail messages;
the random key is then itself encrypted using the
recipient's public key.

Random numbers have other uses in computer security as well. A
variety of authentication protocols require that the computer create
a random number, encrypt it, and send it to the user. The user must
then decrypt the number, perform a mathematical operation on it,
re-encrypt the number, and send it back to the computer.

A great deal is known about random
numbers. Here are some general rules of thumb:

If a number is random, then each bit of that
number's binary representation should have an equal
probability of being a 0 or a 1.

If a number is random, then after each 0 bit in that
number's binary representation there should be an
equal probability that the following bit is a 0 or a 1. Likewise,
after each 1 there should be an equal probability that the following
bit is a 0 or a 1.

When examining a large set of random values, each with a large number
of bits, then roughly half of the bits should be 0s, and half of the
bits should be 1s.

For security-related purposes, a further requirement for random
numbers is unpredictability:

It should not be possible to predict the output of the random number
generator given previous outputs or other knowledge about the
computer generating the random numbers.

It should not be possible to determine the internal state of the
random number generator.

It should not be possible to replicate the initial state of the
random number generator, or to reseed the generator with the same
initial value.

One of the best ways of generating a stream of random numbers is to
make use of a random process, such as radioactive decay.
Unfortunately, most Unix computers are not equipped with Geiger
counters. Thus, they need to use something else. Often, they use
pseudorandom functions as random number generators.

A pseudorandom function is a function that yields a series of outputs
that appears to be unpredictable. In practice, these functions
maintain a large internal state from which the output is calculated.
Each time a new number is generated, the internal state is changed.
The function's initial state is referred to as its
seed.

If you need a series of random numbers that is repeatable, you need a
pseudorandom generator that takes a seed and keeps an internal state.
If you need a nonreproducible series of random numbers, you should
avoid pseudorandom generators. Thus, successfully picking random
numbers in the Unix environment depends on two things: picking the
right random number generator, and then picking a different seed each
time the program is run.