Commit cbe2f151 authored by ale's avatar ale
Browse files

bundle a subset of github.com/bdauvergne/python-oath

parent 26f169cd
from _totp import *
from _hotp import *
__version__ = VERSION = '1.2~ai-sso'
import hashlib
import hmac
import binascii
'''
HOTP implementation
To compute an HOTP one-time-password:
>>> hotp(key, counter)
where is the hotp is a key given as an hexadecimal string and counter is an
integer. The counter value must be kept synchronized on the server and the
client side.
See also http://tools.ietf.org/html/rfc4226
'''
__all__ = ( 'hotp', 'accept_hotp' )
def truncated_value(h):
bytes = map(ord, h)
offset = bytes[-1] & 0xf
v = (bytes[offset] & 0x7f) << 24 | (bytes[offset+1] & 0xff) << 16 | \
(bytes[offset+2] & 0xff) << 8 | (bytes[offset+3] & 0xff)
return v
def dec(h,p):
v = truncated_value(h)
v = v % (10**p)
return '%0*d' % (p, v)
def int2beint64(i):
hex_counter = hex(long(i))[2:-1]
hex_counter = '0' * (16 - len(hex_counter)) + hex_counter
bin_counter = binascii.unhexlify(hex_counter)
return bin_counter
def __hotp(key, counter, hash=hashlib.sha1):
bin_counter = int2beint64(counter)
bin_key = binascii.unhexlify(key)
return hmac.new(bin_key, bin_counter, hash).digest()
def hotp(key,counter,format='dec6',hash=hashlib.sha1):
'''
Compute a HOTP value as prescribed by RFC4226
:param key:
the HOTP secret key given as an hexadecimal string
:param counter:
the OTP generation counter
:param format:
the output format, can be:
- hex, for a variable length hexadecimal format,
- hex-notrunc, for a 40 characters hexadecimal non-truncated format,
- dec4, for a 4 characters decimal format,
- dec6,
- dec7, or
- dec8
it defaults to dec6.
:param hash:
the hash module (usually from the hashlib package) to use,
it defaults to hashlib.sha1.
:returns:
a string representation of the OTP value (as instructed by the format parameter).
Examples:
>>> hotp('343434', 2, format='dec6')
'791903'
'''
bin_hotp = __hotp(key, counter, hash)
if format == 'dec4':
return dec(bin_hotp, 4)
elif format == 'dec6':
return dec(bin_hotp, 6)
elif format == 'dec7':
return dec(bin_hotp, 7)
elif format == 'dec8':
return dec(bin_hotp, 8)
elif format == 'hex':
return hex(truncated_value(bin_hotp))[2:]
elif format == 'hex-notrunc':
return binascii.hexlify(bin_hotp)
elif format == 'bin':
return bin_hotp
elif format == 'dec':
return str(truncated_value(bin_hotp))
else:
raise ValueError('unknown format')
def accept_hotp(key, response, counter, format='dec6', hash=hashlib.sha1,
drift=3, backward_drift=0):
'''
Validate a HOTP value inside a window of
[counter-backward_drift:counter+forward_drift]
:param key:
the shared secret
:type key:
hexadecimal string of even length
:param response:
the OTP to check
:type response:
ASCII string
:param counter:
value of the counter running inside an HOTP token, usually it is
just the count of HOTP value accepted so far for a given shared
secret; see the specifications of HOTP for more details;
:param format:
the output format, can be:
- hex40, for a 40 characters hexadecimal format,
- dec4, for a 4 characters decimal format,
- dec6,
- dec7, or
- dec8
it defaults to dec6.
:param hash:
the hash module (usually from the hashlib package) to use,
it defaults to hashlib.sha1.
:param drift:
how far we can look forward from the current value of the counter
:param backward_drift:
how far we can look backward from the current counter value to
match the response, default to zero as it is usually a bad idea to
look backward as the counter is only advanced when a valid value is
checked (and so the counter on the token side should have been
incremented too)
:returns:
a pair of a boolean and an integer:
- first is True if the response is validated and False otherwise,
- second is the new value for the counter; it can be more than
counter + 1 if the drift window was used; you must store it if
the response was validated.
>>> accept_hotp('343434', '122323', 2, format='dec6')
(False, 2)
>>> hotp('343434', 2, format='dec6')
'791903'
>>> accept_hotp('343434', '791903', 2, format='dec6')
(True, 3)
>>> hotp('343434', 3, format='dec6')
'907279'
>>> accept_hotp('343434', '907279', 2, format='dec6')
(True, 4)
'''
for i in range(-backward_drift, drift+1):
if hotp(key, counter+i, format=format, hash=hash) == str(response):
return True, counter+i+1
return False,counter
import time
import hashlib
import datetime
import calendar
'''
:mod:`totp` -- RFC6238 - OATH TOTP implementation
=================================================
.. module:: parrot
:platform: any
:synosis: implement a time indexed one-time password algorithm based on a HMAC crypto function as specified in RFC6238
.. moduleauthor:: Benjamin Dauvergne <benjamin.dauvergne@gmail.com>
'''
from ._hotp import hotp
__all__ = ('totp', 'accept_totp')
def totp(key, format='dec6', period=30, t=None, hash=hashlib.sha1):
'''
Compute a TOTP value as prescribed by OATH specifications.
:param key:
the TOTP key given as an hexadecimal string
:param format:
the output format, can be:
- hex40, for a 40 characters hexadecimal format,
- dec4, for a 4 characters decimal format,
- dec6,
- dec7, or
- dec8
it default to dec6.
:param period:
a positive integer giving the period between changes of the OTP
value, as seconds, it defaults to 30.
:param t:
a positive integer giving the current time as seconds since EPOCH
(1st January 1970 at 00:00 GMT), if None we use time.time(); it
defaults to None;
:param hash:
the hash module (usually from the hashlib package) to use,
it defaults to hashlib.sha1.
:returns:
a string representation of the OTP value (as instructed by the format parameter).
:type: str
'''
if t is None:
t = int(time.time())
else:
if isinstance(t, datetime.datetime):
t = calendar.timegm(t.utctimetuple())
else:
t = int(t)
T = int(t/period)
return hotp(key, T, format=format, hash=hash)
def accept_totp(key, response, format='dec6', period=30, t=None,
hash=hashlib.sha1, forward_drift=1, backward_drift=1, drift=0):
'''
Validate a TOTP value inside a window of
[drift-bacward_drift:drift+forward_drift] of time steps.
Where drift is the drift obtained during the last call to accept_totp.
:param response:
a string representing the OTP to check, its format should correspond
to the format parameter (it's not mandatory, it is part of the
checks),
:param key:
the TOTP key given as an hexadecimal string
:param format:
the output format, can be:
- hex40, for a 40 characters hexadecimal format,
- dec4, for a 4 characters decimal format,
- dec6,
- dec7, or
- dec8
it default to dec6.
:param period:
a positive integer giving the period between changes of the OTP
value, as seconds, it defaults to 30.
:param t:
a positive integer giving the current time as seconds since EPOCH
(1st January 1970 at 00:00 GMT), if None we use time.time(); it
defaults to None;
:param hash:
the hash module (usually from the hashlib package) to use,
it defaults to hashlib.sha1.
:param forward_drift:
how much we accept the client clock to advance, as a number of
periods, i.e. if the period is 30 seconds, a forward_drift of 2,
allows at most a clock a drift of 90 seconds;
Schema:
.___ Current time
|
0 v + 30s +60s +90s
[ current_period | period+1 | period+2 [
it defaults to 1.
:param backward_drift:
how much we accept the client clock to backstep; it defaults to 1.
:param drift:
an absolute drift of the local clock to the client clock; use it to
keep track of an augmenting drift with a client without augmenting
the size of the window given by forward_drift and backward_dript; it
defaults to 0, you should usually give as value the last value
returned by accept_totp for this client (read further).
:returns:
a pair (v,d) where v is a boolean giving the result, and d the
needed drift to validate the value. The drift value should be saved
relative to the current client. This saved value SHOULD be used in
later calls to accept_totp in order to accept a slowly accumulating
drift in the client token clock; on the server side you should use
reliable source of time like an NTP server.
:rtype: a two element tuple
'''
if t is None:
t = int(time.time())
for i in range(max(-divmod(t, period)[0],-backward_drift),forward_drift+1):
d = (drift+i) * period
if totp(key, format=format, period=period, hash=hash, t=t+d) == str(response):
return True, drift+i
return False, 0
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment