Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

Password Files


JAN96: Password Files

Trevor is a software engineer specializing in real-time embedded systems. He can be contacted at 38 Somerset Road, Kensington, 2094, South Africa or at [email protected].


Recently, while developing an access-control method for an embedded system, I implemented one-way encryption of passwords using the MD5 Message Digest algorithm. The MD5 Message Digest Algorithm was invented by Ron Rivest of RSA Data Security (inventors of the patented RSA public-key cryptography algorithm). The MD5 algorithm calculates a practically unique 128-bit message digest of a file. It is claimed that the odds of a different file producing the same digest are quite small--one in 264. Thus, the digest protects a file, because any alteration will show up as a changed digest. The digest can be used for one-way encryption so that data can be verified without being disclosed. This is how the passwords in the method described here are protected.

The approach I implemented uses a password file that contains the user records in plaintext, together with the user digests. The entire file is protected using a digest to detect unauthorized alterations to the file. Under UNIX, password files can be read by users, but are protected from tampering by file-access restrictions. Since this wasn't possible in my embedded system, I used a file digest to protect the password file from tampering.

Each user record contains the user's name and other personal details, as well as the record's access rights and digest. The digest is calculated on the visible contents of the record as well as the user's password, to produce a unique, 128-bit key. The length of the key (and the way it is calculated) makes it unlikely that the password can be guessed.

The Password File

The password file (an ASCII file that can be read with a text editor) contains a header and a number of user entries. The header contains the file digest, a string containing the machine name, and the number of password entries in the file. The digest is calculated on the whole file so that if the file is altered in any way, this will be detected. If the digest is incorrect, an attempt may have been made to alter the file, and the file is rejected.

Each entry provides for one user. The plaintext part contains the user's name, employee number, and access rights. The digest is constructed using the plaintext part and the user-entered password. The resulting 128-bit key is stored as a string of 32 hex characters at the end of the user record. To be validated, the user supplies a username and password. The username is used to locate the user record in the password file. The plaintext part of the record, together with the user-supplied password, is used to calculate the digest. If the calculated digest is the same as the digest stored in the record, the user is validated. The user's password remains secret: It is not stored separately in the file, and it is computationally infeasible to guess it.

Because the MD5 algorithm is well known, it would be simple to change the file and recalculate the digest. One way to prevent this is to implement a keyed hash function. My approach, however, was to add encryption and scrambling to the digest; this is a key feature of my password file. There are a number of ways to scramble the digest. Any change in the order of the data will affect the digest, so even simply reordering the file as the digest is calculated would complicate things for a cracker. As long as this encryption remains secret, the password file will be impregnable.

The Code

The code that implements the technique I used consists of the password module, a command-line-driven test module, and the MD5 digest-algorithm code. The code is written for ANSI C and has been compiled and tested using the GNU C compiler (gcc) on a Sun under Solaris 1.1 (4.3 BSD) and Solaris 2.3 (basically UNIX System V), and under Linux. I obtained the MD5 digest package via FTP from info.cert.org in /pub/tools/md5 and used it as is. (Thanks to Ron Rivest and RSA Data Security for placing this code into the public domain.)

The password module consists of password.c, password.h (the public interfaces), and passwrdc.h (the public data definitions). These files, along with the MD5 modules used (as distributed by RSA Data Security), are available electronically; see "Availability," page 3. Listing One is passtest.c, a program that tests the operation of the password package. The code can be compiled and linked with gcc as follows:

gcc -O -g -Wall -static -I. -I/usr/5include -I/usr/include -o passtest passtest.c Password.c md5c.c -L/usr/5lib.

The -g flag includes debug information; this should be omitted from the final version. Figure 1 is sample output from the test program.

Running the resulting executable allows the various functions in password.c to be exercised by entering the first letter out of the list of options provided. You could start off by making a new password file and answering the questions. The resulting password file contains 20 entries--the first is a user with supervisor rights, as per the questions you answered. The remaining 19 entries are marked as unused. The single user needs supervisor rights to add and delete other users.

Once you have a valid password file, you test whether or not it is okay by pressing "I" (initialize), whereupon the file is read into memory and validated by checking the digest. Once a password file has been validated, you can perform other actions: validate, add and delete users, or change passwords. Each time a user is added/deleted, or the user changes his or her password, the password file is updated and written with the new digest. You can also display the password file in memory and generate a list of users suitable for display to a user.

The code enforces several common-sense rules:

  • Only a user with a supervisor privilege can add and delete users.
  • Supervisors cannot delete themselves.
  • Only a user can change his or her password, and only by entering the previous password first. If the user forgets the password, a supervisor must log on and delete and add the user with a new initial password. The user can alter the initial password to a secure password known only to the user at any later time.
  • A new password must pass an obviousness test to be accepted.
  • There can be more than one supervisor. If the only supervisor forgets his password, a new password file must be made from scratch and copied over the old.

Security

To keep the system secure, the facility to create a new password file from scratch must be guarded. Depending on the actual implementation, there are a number of ways to attack a system using the code I present here. Among the strategies an attacker could adopt are:

  • Cracking individual passwords. Nonobvious passwords that are long enough are computationally infeasible to crack. The user must choose a password that is suitably randomized and long enough.
  • Adding entries to the password file. If this can be done, then the new entry could be used to compromise the system. Once the attack is over, the old password file can be restored to allow the attacker to cover his tracks. This will only succeed if the password file can successfully be altered with a new, valid digest. Proper encryption of the file digest will make the attacker's task extremely difficult.
  • Intercepting passwords. This will depend very much on the architecture of the target system. If a serial terminal is used, a serial communications monitor can be used to reveal the password. If an X terminal is used, network sniffers can reveal it. On an X terminal, another application could simply grab the keyboard while the user types in the password.
  • Accessing the source code. This will allow the attacker to analyze the algorithms used. This is unimportant except for the part used to encrypt the file digest; still, this needs to be guarded carefully.
  • Using a debugger. Under UNIX, it is possible to attach a debugger to a process and then set a break point just after the user enters a password. On other systems, this may not be possible, particularly if the code is running out of ROM. If possible, control access to the system via telnet, rlogin, and the like to prevent this sort of attack. To make this type of attack more difficult, the password field is cleared as soon as the user digest has been calculated. Don't distribute the executable with debugging information included!

For More Information

RFC1321: The MD5 Message-Digest Algorithm. Ron Rivest, RSA Data Security Inc. April 1992. Available from info.cert.org in /pub/tools/md5.

Schneier, Bruce. "One-Way Hash Functions." Dr. Dobb's Journal (September 1991).

Stallings, William. "SHA: The Secure Hash Algorithm." Dr. Dobb's Journal (April 1994).

Figure 1: A starting password file with one entry with supervisor privileges. This was created using the New function in the test software. One user is defined and has supervisor privileges (logon username Trevor, password Pope). Other user records are marked as unused.

efa00b25cc4b3fb5aa29d507f9d2ad38 1
MachineName
Trevor  0123456789  s   0c47cdd73a4e8f70c67671f968eae636
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #
Unused  Nobody  *   #

Listing One

/*************************************************************************/ 
/* File: passtest.c                              */ 
/* Password package - test program                       */ 
/* Simple test program to check the operation of the password package    */ 
/* under unix.                               */ 
/* Copyright (C) 1994-5, Trevor J. Pope -- All rights reserved.      */ 
/* License to copy and use this software is granted provided that this   */ 
/* copyright notice is retained in any copies of any part of this    */ 
/* software.                                 */ 
/* No representations are made concerning either the merchantability     */ 
/* of this software or the suitability of this software for any          */ 
/* particular purpose. It is provided "as is" without express or     */ 
/* implied warranty of any kind.                     */ 
/*************************************************************************/ 
 
/* Common library includes */ 
#include <stdio.h>  /* for file I/O */ 
#include <time.h>   /* for time functions */ 
#include <string.h> /* for strcpy */ 
#include <errno.h>  /* for errno etc */ 
 
/* Pull in the standard include for the package */ 
#include "Password.h" 
 
/*************************************************************************/ 
/* main() -- Entry point for the test program                */ 
/* Passed  : command line parameter list                 */ 
/*************************************************************************/ 
int main( int argc, char * argv[], char * env[] ) 
{ 
   int result; 
   char option, optionString[80]; 
   char userName[USER_NAME_LENGTH+1]; 
   char userNumber[EMPLOYEE_NUMBER_LENGTH+1]; 
   char accessString[ACCESS_RIGHTS_LENGTH+1]; 
   char password[PASSWORD_LENGTH+1]; 
   char superUserName[USER_NAME_LENGTH+1]; 
   char superPassword[PASSWORD_LENGTH+1]; 
   char newPassword[PASSWORD_LENGTH+1]; 
   char passwordFileName[51]; 
   char machineName[MACHINE_NAME_LENGTH+1]; 
   char userDetails[USER_DETAILS_LENGTH]; 
 
   /* Loop getting options and executing them until a quit is selected */ 
   do { 
      /* Show menu */ 
      printf("\n\ 
Select:  (i)nitialise    (v)alidate (c)hange  (a)add   (d)elete\n\ 
         (n)ew  (u)sers  (s)how     (h)elp    (q)uit   : "); 
     scanf( "%s", &optionString[0] ); 
     option = optionString[0]; 
     /* Act on option ... */ 
     switch( (int)option ) 
     { 
     case 'i': 
     { 
       printf("\n initPassword - enter password file name :"); 
       scanf("%s", passwordFileName ); 
       result = initPassword( passwordFileName ); 
       printf("\n initPassword() returned %s \n", 
       &passReturnCodeStrings[result][0] ); 
       break; 
     } 
     case 'v': 
     { 
     printf("\n validateUser - enter userName :"); 
     scanf("%s", userName ); 
     printf("\n validateUser - enter pass word :"); 
     scanf("%s", password ); 
     result = validateUser( userName, password, accessString ); 
     if( result == PASS_OK ) 
     { 
          printf("validateUser() returned %s (Access level is %s )\n", 
          &passReturnCodeStrings[result][0], accessString ); 
      } else { 
          printf("validateUser() returned %s \n", 
            &passReturnCodeStrings[result][0] ); 
      } 
      break; 
    } 
    case 'c': 
    { 
       printf("\n changePassword - enter userName :"); 
       scanf("%s", userName ); 
       printf("\n changePassword - enter old pass word :"); 
       scanf("%s", password ); 
       printf("\n changePassword - enter new pass word :"); 
       scanf("%s", newPassword ); 
       result = changePassword( userName, password, newPassword ); 
       printf("changePassword() returned %s \n", 
             &passReturnCodeStrings[result][0] ); 
       break; 
      } 
      case 'a': 
      { 
      printf("\n addUser - enter supervisor userName :"); 
      scanf("%s", superUserName ); 
      printf("\n addUser - enter supervisor password :"); 
      scanf("%s", superPassword ); 
      printf("\n addUser - enter new userName :"); 
      scanf("%s", userName ); 
      printf("\n addUser - enter new user number:"); 
      scanf("%s", userNumber ); 
      printf("\n addUser - enter new user rights :"); 
      scanf("%s", accessString ); 
      printf("\n addUser - enter new user password :"); 
      scanf("%s", password ); 
      result = addUser( superUserName, superPassword, 
                  userName, userNumber, accessString, password ); 
      printf("addUser() returned %s \n", 
        &passReturnCodeStrings[result][0] ); 
      break; 
      } 
      case 'd': 
      { 
     printf("\n deleteUser - enter supervisor userName :"); 
     scanf("%s", superUserName ); 
     printf("\n deleteUser - enter supervisor password :"); 
     scanf("%s", superPassword ); 
     printf("\n addUser - enter userName to delete :"); 
     scanf("%s", userName ); 
     result = deleteUser( superUserName, superPassword, userName ); 
 
     printf("deleteUser() returned %s \n", 
        &passReturnCodeStrings[result][0] ); 
     break; 
      } 
      case 'n': 
      { 
    printf("\n newPasswordFile - enter full pathname : "); 
    scanf("%s", passwordFileName ); 
    printf("\n newPasswordFile - enter machine name :"); 
    scanf("%s", machineName ); 
    printf("\n newPasswordFile - enter supervisor name :"); 
    scanf("%s", userName ); 
    printf("\n newPasswordFile - enter supervisor number:"); 
    scanf("%s", userNumber ); 
    printf("\n newPasswordFile - enter supervisor password :"); 
    scanf("%s", password ); 
    result = newPasswordFile( passwordFileName, machineName, 
                  userName, userNumber, password ); 
    printf("newPasswordFile() returned %s \n", 
              &passReturnCodeStrings[result][0] ); 
    break; 
      } 
      case 'u': 
      { 
    showUserDetails( userDetails ); 
    printf("showUserDetails() :\n%s", userDetails ); 
 
    /* Check current user details as recorded internally */ 
    result = currentUserDetails( userName, userNumber, accessString ); 
    if( result == PASS_OK ) 
    { 
        printf("currentUserDetails() returned a valid user:\n\ 
userName: %s\n\ 
userNumber: %s\n\ 
accessString: %s\n", userName, userNumber, accessString ); 
    } else { 
         printf("currentUserDetails() returned NO valid user:\n"); 
    } 
    break; 
      } 
      case 'h': 
      { 
    printf("\n\n%s\n\n", passwordHelp() ); 
    break; 
      } 
      case 's': 
      { 
      displayPasswordStruct(); 
      break; 
      } 
      case 'q': 
      { 
      printf("\n quitting ... \n"); 
      break; 
      } 
      default: 
      { 
      printf("\n unrecognised option (%x)", (int)option ); 
      break; 
      } 
      } /* switch */ 
 
    } while( option != 'q' ); 
     return( 0 ); 
} /


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.