Project1: SecLogin: Securing authentication using case-based password hardening CS 6238: Secure Computer Systems Due Date: 2/16/2010 - I. Alan Carroll Daniel Komaromy Initialization Routine During the initialization routine, we setup a configuration file that holds the parameters passed and can be loaded in subsequent executions. This size of the history file is chosen during our program’s initialization phase. This is important to satisfy the constant encrypted history file size; consequently, also the reason the max password length is required, since (num. of distinguishing features == number of characters in password). [root@linbox cs6238]# ./main.py $help Printing options init [--len_history|-l] len [--dir|-d] dir [--min|-m] min_len [--max|-M] max_len initialize the system, with history_file size h(integer) and base directory d (string: fully qualified path) and minimum password lengt min_len and maximum password length max_len load [--dir|-d] d: initialize the system by loading it from the config file, found at the base directory d (string: fully qualified path) add [--user|-u] username: add a new user with login name 'username' . login [--user|-u] username: authenticate the user with login name 'username' . exit: quit the program. help: print this help menu. $init -l 10 -d config -m 8 -M 20 Init a new system. Save config file. SecLogin home created. $ We are selecting a 160-bit prime number (q) during initialization and a random hpwd value to statisty the Hpwd < q requirement. def _generate_prime(self, len): … while(1): rand_bytes = os.urandom(len / 8) value = number.bytes_to_long(rand_bytes) # Ensure high bit is set: multiply (=bitwise or) it by 2^(len-1) value |= 2L ** (len-1) count = 0 while (not number.isPrime(value)): value = value + 2 count = count + 1 if (count == 1000): break if (number.isPrime(value)): break else: pass return value This shows we are searching for an acceptable prime number and utilizing the number class from our Crypto.Cipher module (from Crypto.Util import number). We are calculating hpwd: hpwd = SUM(lgr_coeff_i * y_i) mod q using python’s map/reduce functional programming def _compute_hpwd(self, lgr_coeffs, points): sum_terms = map(lambda x,y: (x*y) % self.mod, lgr_coeffs, points) return reduce(lambda x,y: (x+y) % self.mod, sum_terms) We are selecting a random polynominal (f) of degree n-1 and then using this to create our instruction table def _generate_polynomial(self, hpwd, degree): coeffs = [self._generate_param(self.len) % self.mod for i in range(degree)] coeffs.append(hpwd) return numpy.poly1d(coeffs) so that all our initial and values are valid. For this, we used the formula in the paper to complete this creations. Finally, the history table is encrypted with Hpwd. def _compute_instruction_table(self, pwd, y_pairs): instruction_table_rows = [] for i in range(len(y_pairs)): # i goes from 0 to m-1, but it is 1 to m according to paper, so +1 alpha = (y_pairs[i][0] + self._hash(pwd.lower(), 2*(i+1))) % self.mod beta = (y_pairs[i][1] + self._hash(pwd.lower(), 2*(i+1) + 1)) % self.mod instruction_table_rows.append(tuple([alpha, beta])) return instruction_table_rows II. Login Our login routine first works by selecting our appropriate i or I from our instruction table (setup during initialization) based on the feature values (i) for feature based on the case of the typed character. insTable = InstructionTable.InstructionTable(pwd, path, False) We decrypt the value from the instruction table using the pwd supplied and calculate the xi, yi coordinates based on the i or I values selected from the instruction table using the formula given in the paper. Our decryption and computation of coordinates is shown below. def _decrypt(self, pwd, parameter): #return number.bytes_to_long(parameter) hashed_pwd = SHA256.new(pwd).digest() #use 32 byte value as AES key cryptObj = AES.new(hashed_pwd) #decrypt parameter which is a bytestream ciphertext and turn it into a long plaintext = cryptObj.decrypt(parameter) pad_length = plaintext[len(plaintext) - 1] if (ord(pad_length) == 0): cutoff = len(plaintext) - cryptObj.block_size else: cutoff = len(plaintext) - ord(pad_length) plaintext = plaintext[:cutoff] return number.bytes_to_long(plaintext) points = self._compute_points(pwd, insTable) # _compute_points shown below """ Computes the points on the polynomial pol from pwd, alpha, beta. Parameters: pwd: the users password, used in G() and to determine which column to use from the instruction table. table: the instruction table used here to recover points: the actual InstructionTable object Note: in x calculation, we use i+1 instead of i because i value runs from 1 to m, not 0 to m-1. Every other usage of i is as index into the list (not the actual value), and internally everything is indexed from 0 to m-1. Output: the list of computed (x,y) coordinates of the point on the polynomial """ def _compute_points(self, pwd, table): points = [] #use as lowercase for encryption/decryption pwd_encrypt = pwd.lower() for i in range(len(pwd)): if (self._get_case(pwd[i]) == 0): x = 2*(i+1) alpha = table.get_alpha(i, pwd_encrypt) if (alpha == None): utility.debug('Alpha recovery failed!') return None y = (alpha - self._hash(pwd_encrypt, 2*(i+1))) % self.mod else: x = (2*(i+1) + 1) beta = table.get_beta(i, pwd_encrypt) if (beta == None): utility.debug('Beta recovery failed!') return None y = (beta - self._hash(pwd_encrypt, 2*(i+1) + 1)) % self.mod points.append(tuple([x,y])) return points Once we calculate Hpwd using the retrieved coordinates using polynomial interpolation, we can use Hpwd to decrypt our history file. Our history file, once decrypted, is in a native python double-ended queue for speedy removal of the oldest feature values and addition to the newest feature value. This acts as a true FIFO data structure. hFile = HistoryFile.HistoryFile(len(pwd), self.history_length, path, hpwd) # Creates a new AES object from the Crypto.Cipher module using the hpwd as the key for decrypting later on. This is stored in-memory – never on disk. self.cryptObj = AES.new((SHA256.new(number.long_to_bytes(hpwd))).digest()) … # self.contents stores the decrypted history file contents in-memory. Decryption will fail if the improper key was given to the AES (cryptObj) object. self.contents = self.cryptObj.decrypt(content) If decryption is successful, the Boolean value representing the login status is passed back to SecLogin.py and the user is officially authenticated and welcomed to the system; however, the program will continue to add a new feature vector for the current login in the history file and encrypt the file once again using hpwd. SecLogin will: generate a new random polynomial -> new y pairs -> new alpha, beta -> and then update our Instruction Table. III. Class Descriptions Our system implements the following classes. Note, that each class implementation includes a unit test that can be run to verify functionality. Alternatively, the main.py script can be executed to instantiate the program with a command line interface (as shown in the walk through section of this document). HistoryFile - This class implements a History File object. It handles both the in memory representation and the serialization/deserialization of the file. It is instantiated by SecLogin (see below) each time a user interacts with the system (new user or user logging in). SecLogin calls its deserialize() function to read the History File from the disk. This function determines whether the supplied hpwd was correct or not, by testing the redundancy of the decrypted file. To provide this, we attach its hash value to the serialized bytestream version of the file before it is ecnrypted and dumped from memory. HistoryFile's serialize() function in turn writes out the file from memory, update() updates it with a new feature vector, and get_feature_vector() returns the acceptable case (lower/upper) for each position based on the current history file contents. InstructionTable - This class implements an Instruction Table object. Similarly to HistoryFile, it handles both the in memory representation and the serialization/deserialization of the file. The same as HistoryFile, it is instantiated by SecLogin (see below) each time a user interacts with the system (new user or user logging in). This class only handles alpha and beta values and takes care of their encryption/decryption, lower lever functionality needed to get alpha, beta values from points on the polynomial or vice versa is handled by SecLogin (see below). Its public functions are serialize(), deserialize(), update_rows() and get_alpha(), get_beta(). The first three provide similar functionality to the corresponding functions of HistoryFile (with the exception that update_rows() completely replaces the contents of the table), while the latter two are used to query an instance of the class for the alpha or beta value in a given row of the table. In order to get a correct plaintext blocksize from the alpha, beta values that can be used with AES, we apply padding bytes to the values once there are converted to a bytestream. LoginServer - This class is one of the two example interface classes that we have created for our system. It implements a socket server that listens for connection requests and handles them in a multithreaded way. It can accept user creation and login attempts and returns a response to the client that indicates success or failure. MyOptionParser - This is a helper class, used to overwrite the default functionality of the optparse module. In optparse, parsing errors are sent to stderr. This class traps them to allow execution within our own command line interface, for example PromptInterface. PromptInterface - This is the other interface class. It implements a command line interface to run the hardened password authentication program. Its functionality can be seen by invoking the command 'help'. Note, that some python interpreters do not handle the password input functionality provided by the getpass module, in which case a warning message is displayed to notify the user that typed passwords are echoed. IDLE on Linux is an example of such an interpreter, while the default bash on Linux is an example that hides typed passwords properly. SecLogin - This class drives the mathematical operations to create and recover the hardened passwords, as described in the paper. It saves its configuration parameters into a file during initialization, to make sure that it can be reloaded (i.e. user credentials do not get last after a reboot). It's two public functions are new_user and login_user. The former creates a user, the later validates its login. SecLogin handles Instruction Tables and History Files by calling the public functions for the classes that implement them. It creates one instance of each class for each login attempt, for example. SecLogin is designed to be threadsafe for adding and authenticating users (see LoginServer). Note: If Seclogin’s Unit Tests are run twice in a row, it WILL fail second time. This is the expected behavior. The first run of logins fills up the history file with good passwords in such a way that the same passwords are not acceptable anymore. Utility - This is another helper class. It contains various printing functions. The three currently implemented are utility.message, utility.verbose_message and utility.debug. Change these properly for more or less verbose input. By default utility.message sends its argument to the stdout, while utility.debug silently discards it. Note, that for the multithreaded server to function properly on all platforms (e.g. the IDLE environment on Linux), all stdout messages have to be muted – This is due to a limitation in IDLE not being able to interpret passed stdout pointers. IV. Additional Information We are using the following external modules/classes for programmatic support: Scipy + numpy Crypto o Cipher o Hash o Util cPickle For numpy (specifically, poly1d, a one-dimensional polynomial class) we use this during SecLogin for encapsulating “natural” operations on polynomials so that the operations can take on their customary form in code. We use various functions from the Crypto (PyCrypto) module that allow us to instantiate an AES object, symmetrical-key algorithm using a 32-byte key and a 16-byte block size. We also utilize the SHA256 oneway hash functions for various parts in the code and the Util module offer a number class that allow convenient helped functions (e.g. long_to_bytes(...)). cPickle is an extremely fast implementation of the pickle class for python, which provides a powerful algorithm for serializing and de-serializing a Python object structure (In our case, the history file, has for redundancy verification, and our instruction table). “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream is converted back into an object hierarchy. V. Example Below is a sample output of the program’s execution from init, to login failure, and a successful login. The output is shown for clarification. # ./main.py $help Printing options init [--len_history|-l] len [--dir|-d] dir [--min|-m] min_len [--max|-M] max_len initialize the system, with history_file size h(integer) and base directory d (string: fully qualified path) and minimum password lengt min_len and maximum password length max_len load [--dir|-d] d: initialize the system by loading it from the config file, found at the base directory d (string: fully qualified path) add [--user|-u] username: add a new user with login name 'username' . login [--user|-u] username: authenticate the user with login name 'username' . exit: quit the program. help: print this help menu. $init -l 10 -d config -m 6 -M 20 Init a new system. Save config file. SecLogin home created. $add -u mustaque Enter new password for user: Renter new password for user: Passwords did not match. Enter new password for user: Renter new password for user: SecLogin: adding new user. Initialized Instruction Table. SecLogin: originally calculated pwd is 369815079354300349149468514366220179101352579562 Successfully added new user. User successfuly added. $login -u mustaque Enter password for user: SecLogin: log user in. Initialized Instruction Table from file. The unpickled object is invalid. Authentication Failed. Login Failed. Try Again. Enter password for user: SecLogin: log user in. Initialized Instruction Table from file. The unpickled object is invalid. Authentication Failed. Login Failed. Try Again. Enter password for user: SecLogin: log user in. Initialized Instruction Table from file. The unpickled object is invalid. Authentication Failed. Login Failed. Try Again. Maximum number of attempts exceeded. Try again later. Login Failed $login -u mustaque Enter password for user: SecLogin: log user in. Initialized Instruction Table from file. SecLogin: calculated pwd is 369815079354300349149468514366220179101352579562 Signature correct. History File initialized and deserialized. SecLogin: login successful. Welcome! Login Successful. $exit Thank you for using SecLogin!