python-otp

What is python-otp?

python-otp is a module which implements support for all requirements, recommendations, and optional features described in RFC2289. This RFC defines a standard for the implementation of OTP - one-time passwords.

What is OTP?

One form of attack on networked computing systems is eavesdropping on network connections to obtain authentication information such as the login IDs and passwords of legitimate users. Once this information is captured, it can be used at a later time to gain access to the system. One-time password systems are designed to counter this type of attack, called a "replay attack".

The authentication system defined in RFC2289, and known as OTP - one-time password, uses a secret pass-phrase to generate a sequence of one-time (single use) passwords. With this system, the user's secret pass-phrase never needs to cross the network at any time such as during authentication or during pass-phrase changes. Thus, it is not vulnerable to replay attacks. Added security is provided by the property that no secret information need be stored on any system, including the server being protected.

The OTP system protects against external passive attacks against the authentication subsystem. It does not prevent a network eavesdropper from gaining access to private information and does not provide protection against either "social engineering" or active attacks [9].

{i} This information was obtained from RFC2289.

OTP basic theory

There are two entities in the operation of the OTP one-time password system. The generator must produce the appropriate one-time password from the user's secret pass-phrase and from information provided in the challenge from the server. The server must send a challenge that includes the appropriate generation parameters to the generator, must verify the one-time password received, must store the last valid one-time password it received, and must store the corresponding one- time password sequence number. The server must also facilitate the changing of the user's secret pass-phrase in a secure manner.

The OTP system generator passes the user's secret pass-phrase, along with a seed received from the server as part of the challenge, through multiple iterations of a secure hash function to produce a one-time password. After each successful authentication, the number of secure hash function iterations is reduced by one. Thus, a unique sequence of passwords is generated. The server verifies the one-time password received from the generator by computing the secure hash function once and comparing the result with the previously accepted one-time password. This technique was first suggested by Leslie Lamport.

{i} This information was obtained from RFC2289.

python-otp module features

The python-otp module offers the following features:

[1] Requires mhash or pycrypto python modules

Output formats

Several output formats are supported by the python-otp module. Output formats control how the generated keys are returned. Below are presented the builtin formats supported, with sample output.

"long"
Generates output like 15097546000631008081L, widely used in internal operations.
"hex"
Generates output like "d1854218ebbb0b51".
"hex1"
Generates output like "D1854218EBBB0B51".
"hex2"
Generates output like "D1854218 EBBB0B51".
"hex4"
Generates output like "D185 4218 EBBB 0B51".
"hex8"
Generates output like "D1 85 42 18 EB BB 0B 51".
"words"
Generates output like "ROME MUG FRED SCAN LIVE LACE".

Notice that all generated formats above represent exactly the same key, and can be used anywhere a key is accepted in the API. Also notice that even though these are the currently supported builtin formats, you can easily plug a custom format in the system.

Hash algorithms

RFC2289 presents three hashing algorithms to be used in one-time passwords: md4, md5, and sha1. From these, md5 is defined as required, sha1 is defined as recommended, and md4 is defined as optional. The python-otp module implements all of them. OTOH, since python has no md4 support in the standard library, this hash algorithm requires some external module support to work. Currently, mhash and pycrypto python modules are supported. If one of them is available, md4 will be supported without further intervention.

API documentation

OTP class

Most of the functionality of the python-otp module is offered by the OTP class. Here is presented a detailed documentation about how to use it, including examples.

OTP.__init__()

The OTP class constructor takes the following keyword arguments:

format
String defining the default output format (default is "hex").
hash
String defining the default hash algorithm (default is "md5")

OTP.generate()

The OTP.generate() method creates one-time passwords with the provided information. It has the following prototype:

OTP.generate(self, passwd, sequence, seed, **keywd) -> [key1, ...] 

Additionally, it accepts the following keyword arguments:

length
Number of keys generated, starting at sequence.
format
String defining the key output format.
hash
String defining the hash algorithm used.
force
Ignore password and seed validation.

Example:

otp = OTP() 
keylist = otp.generate("my password string", 99, "aValidSeed") 

OTP.reformat()

The OTP.reformat() method translates a key format into another format. Unless the fromformat keyword argument is used, the original format is automatically detected. If no format is able to parse the given key, None is returned. This method has the following prototype:

OTP.reformat(oldkey) -> newkey 

Additionally, it accepts the following keyword arguments:

format
String defining the key output format.
hash
String defining the hash algorithm used.
fromformat
Disable automatic format detection and use the given format.

Example:

otp = OTP() 
newkey = otp.reformat("D185 4218 EBBB 0B51", format="words") 

OTP.parse_challenge()

The OTP.parse_challenge() method extracts a dictionary containing the hash, the sequence, and the seed contained in an OTP challenge. This method has the following prototype:

OTP.parse_challenge(challenge) -> {} 

Additionally, it accepts the following keyword arguments:

force
Ignore seed validation.

Example:

otp = OTP() 
dict = otp.parse_challenge("otp-md5 99 aValidSeed") 
print "Hash:",     dict["hash"] 
print "Sequence:", dict["sequence"] 
print "Seed:",     dict["seed"] 

OTP.generate_seed()

The OTP.generate_seed() method will generate a valid seed. This method has the following prototype:

OTP.generate_seed() -> newseed 

Additionally, it accepts the following keyword arguments:

length

Seed length, defaults to 10 (1 <= length <= 16).

Example:

otp = OTP() 
newseed = otp.generate_seed(length=16) 

OTP.change_seed()

This is a wrapper over OTP.generate_seed() which ensures that the seed has changed, following RFC recommendations. The function has the following prototype:

OTP.change_seed(oldseed) -> newseed 

It accepts the same keyword arguments as the wrapped method.

Example:

otp = OTP() 
newseed = otp.change_seed("aValidSeed", length=16) 

OTP.generate_challenge()

This is a simple method which generates a challenge string. It has the follownig prototype:

OTP.generate_challenge(sequence, seed=None) -> challenge 

If seed is not provided, it is automatically generated calling OTP.generate_seed(). Additionally, it accepts following keyword arguments:

length
Generated seed length, if not provided.

Example:

otp = OTP() 
challenge = otp.generate_challenge(99, "aValidSeed") 

OTP.check_with_passwd()

The OTP.check_with_passwd() method checks if a key is valid using the password and additional information. This information can be either a hash/sequence/seed term, or a challenge. When using the term, if hash is omitted, the default one is used instead. This method has the following prototype:

OTP.check_with_passwd(passwd, key) -> rawkey 

This method will either return None, or the valid key generated with the information provided. If necessary, the key can be stored and used to check the next provided key, according to RFC recommendations.

Additionally, it accepts the following keyword arguments:

challenge
Use this challenge to retrieve information about the key to be tested. If provided, no other keywords are necessary.
hash
Use this hash while generating the key to be checked against.
sequence
Use this sequence while generating the key to be checked against.
seed
Use this seed while generating the key to be checked against.

Example:

otp = OTP() 
rawkey = otp.check_with_passwd("my password string", "852de27667d2daae", 
                               challenge="otp-md5 99 aValidSeed") 
if rawkey: 
        print "Valid key!" 

OTP.check_with_nexthash()

This method is similar to OTP.check_with_passwd(), but instead of using the password, it uses the next key in the sequence. This method has the following prototype:

OTP.check_with_nexthash(nexthash, key) -> rawkey 

This method will either return None, or the valid key generated with the information provided. If necessary, the key can be stored and used to check the next provided key, accordingly to RFC recommendations.

Example:

otp = OTP() 
rawkey = otp.check_with_passwd("my password string", "852de27667d2daae", 
                               challenge="otp-md5 99 aValidSeed") 
#... 
dict = otp.parse_challenge("otp-md5 98 aValidSeed") 
rawkey = otp.check_with_nexthash(rawkey, "72b5dfe79e26a2d7", 
                                 hash=dict["hash"]) 

OTP.compare()

The OTP.compare() method will compare two keys, with arbitrary formats, returning a true value if they are equal. This method has the following prototype:

OTP.compare(oldkey) -> bool 

Additionally, it accepts the following keyword arguments:

format
String defining the key output format.
hash
String defining the hash algorithm used.
fromformat
Disable automatic format detection and use the given format.

Example:

otp = OTP() 
newkey = otp.reformat("D185 4218 EBBB 0B51", format="words") 

OTP.detect_format()

This method will detect the provided key format. It has the following prototype:

OTP.detect_format(key) -> formatname 

Additionally, it accepts the following keyword arguments:

hash
String defining the hash algorithm used. This is necessary to correctly detect alternative word dictionaries.

Example:

otp = OTP() 
format = otp.detect_format("D185 4218 EBBB 0B51") 

OTPDictGenerator class

python-otp offers an alternative dictionary generator through the OTPDictGenerator class. Using it you can easily build alternative dictionaries as defined in Appendix B of RFC2289, and plug into the system as a new output format.

Notice that accordingly to the RFC documentation, an alternative dictionary is always related to a specific hashing algorithm, so the OTPDictGenerator class interface will reflect this.

OTPDictGenerator.__init__()

The class constructor takes the following keyword arguments:

hashes
This is a list of the hashes that the processed words will be

included into. It defaults to ["md5"].

maxsize

Maximum size of accepted words. Defaults to 6.

Example:

otpgen = OTPDictGenerator(hashes=["md5", "sha1"]) 

OTPDictGenerator.push_string()

This method will push a string into the generator, splitting it in spaces, and doing the proper transformations and validations. The method prototype is as follows:

OTPDictGenerator.push_string(s) -> None

It accepts no keyword arguments.

Example:

otpgen = OTPDictGenerator() 
otpgen.push_string("Hello, my friend!") 

OTPDictGenerator.push_file()

This method will pass every line of the given file to the push_string() method. It has the following prototype:

OTPDictGenerator.push_file(file) -> None 

The file argument can be either a filename, or a file-like object. This method accepts no keyword arguments.

Example:

otpgen = OTPDictGenerator() 
otpgen.push_file("mywords.txt") 

OTPDictGenerator.write()

This method writes the current generator state into a file like this:

[md5] 
1 = word1 word2 word3 
2 = 
3 = word4 
... 

In this example, the first position of the dictionary is currently taken by word1, but could be safely replaced by word2 or word3. The second position has no candidate for now, and the third is occupied only by word4.

This format is useful to maintain a dictionary in a readable and maintainable form, until all positions are filled, and the best words are chosen from the available options.

The write() method has the following prototype:

OTPDictGenerator.write(file) 

The file argument can be either a filename, or a file-like object (with a write() method). This method accepts no keyword arguments.

Example:

otpgen = OTPDictGenerator() 
otpgen.push_file("mywords.txt") 
otpgen.write("mywords.dict") 

OTPDictGenerator.read()

This method will merge files in the format created by the write() method into the current generator state. Since it merges instead of replacing, multiple files can be merged into a single state. This method has the following prototype:

OTPDictGenerator.read(file) 

The file argument can be either a filename, or a file-like object (with a readlines() method). This method accepts no keyword arguments.

Example:

otpgen = OTPDictGenerator() 
otpgen.read("mywords.dict") 

OTPDictGenerator.export()

The export() method will generate a python module with the generated dictionary. This dictionary can then be plugged into the system as a new output format. Here is the method prototype:

OTPDictGenerator.export(file) 

The file argument can be either a filename, or a file-like object. Additionally, it accepts the following keyword arguments:

dictname
Python variable name of the generated dictionary. Defaults

to "DICT".

Example:

otpgen = OTPDictGenerator() 
otpgen.read("mywords.dict") 
otpgen.export("mywords.py", dictname="MYWORDS") 
 
import mywords 
print mywords.MYWORDS 

OTPDictGenerator.export_dict()

This method is similar to the export() method, presented above. But instead of generating a python module, it returns the generated dictionary immediately. This is the method prototype:

OTPDictGenerator.export_dict() 

This method accepts no keyword arguments.

Example:

otpgen = OTPDictGenerator() 
otpgen.read("mywords.dict") 
MYWORDS = otpgen.export_dict() 

Plugging an alternative dictionary

Here is an example of how to plug an alternative generated with the OTPDictGenerator into the python-otp module.

import otp 
import mywords 
otp.register_format("my-words", format.FormatWords, 
                    {"dict": MYWORDS}}, before="words") 
keylist = otp.generate("my password string", 99, "aValidSeed", 
                       format="my-words") 

Notice the usage of the before keyword. This is necessary for correctly detecting the format of your dictionary in OTP.detect_format().

Downloads

License

python-otp is licensed under the GPL.

Author

Gustavo Niemeyer <gustavo@niemeyer.net>


CategoryProject

python-otp (last edited 2008-03-03 03:14:34 by GustavoNiemeyer)