def verify_totp_code(to_verify: str, my_secret: str, length: int, interval: int, hash_type: str) -> bool: """ Verify given time-based one time password using library passlib :param to_verify: given time-based one time password :type to_verify: str :param my_secret: my local stored secret :type my_secret: str :param length: time-based one time password length Caution Due to a limitation of the HOTP algorithm, the 10th digit can only take on values 0 .. 2, and thus offers very little extra security. Please use maxim lenght of 9 instead 10 limitation see here https://passlib.readthedocs.io/en/stable/lib/passlib.totp.html#totptoken :type length: int :param interval: totp interval in sec :type interval: int :param hash_type: hash code type to be used sha1, sha256 or sha512 :type hash_type: str :return: result of totp matches :rtype: bool """ logger.debug("verify totp with library passlib") totp = TOTP(key=my_secret, digits=length, period=interval, alg=hash_type) if totp.generate().token == str(to_verify): return True else: return False
def generate_totp_code(my_secret: str, length: int, interval: int, hash_type: str) -> bool: """ Generate a new time-based one time password using library passlib :param my_secret: my local stored secret :type my_secret: str :param length: time-based one time password length Caution Due to a limitation of the HOTP algorithm, the 10th digit can only take on values 0 .. 2, and thus offers very little extra security. Please use maxim lenght of 9 instead 10 limitation see here: https://passlib.readthedocs.io/en/stable/lib/passlib.totp.html#totptoken :type length: int :param interval: totp interval in sec :type interval: int :param hash_type: hash code type to be used sha1, sha256 or sha512 :type hash_type: str :return: totp code :rtype: int """ logger.debug("generate new totp code with library passlib") totp = TOTP(key=my_secret, digits=length, period=interval, alg=hash_type) return totp.generate().token
def totp_hook(self, secret=None): nonlocal totp if totp is None: totp = TOTP(secret) if secret: return totp.generate().token else: # on check, take advantage of window because previous token has been # "burned" so we can't generate the same, but tour is so fast # we're pretty certainly within the same 30s return totp.generate(time.time() + 30).token
def new_user(self): self.username = input('Enter username: '******'Enter password: '******'Two factor auth (y, n)?: ') if totp.lower() == 'y': self.totp_enabled = True totp_factory = TOTP.using(secrets_path='totp_sec', issuer='sec.thelonelylands.com') totp = totp_factory.new() self.totp = json.dumps(totp.to_json()) uri = totp.to_uri(label=self.username) input('Please enlarge your screen and press enter') print(pyqrcode.create(uri).terminal(quiet_zone=1)) print("Alternatively: " + totp.pretty_key()) token = input('Enter token to verify: ') verification = self.auth_verify(token) if verification: print('Successfully created user') return self else: print('User creation failed') return else: print('Successfully created user') return self
def verifyRegistration(request): registrationId = request.args.get('registrationId') otp1 = request.args.get('otp1') otp2 = request.args.get('otp2') TotpFactory = TOTP.using(secrets_path=PATH_TO_SECRET,issuer=ISSUER) querystr = AccountAdministratorRegistration \ .query \ .filter_by(registrationId=registrationId) \ .all() if querystr: registrationObj = querystr[0] secretToken = regObj.secretToken totp = TotpFactory.from_json(secretToken) try: if not registrationObj.expired and totp.verify(otp1,secretToken) and totp.verify(otp2,secretToken, window=60) and __verifyQR__(): registrationObj.expired = True registrationObj.expiryDate = datetime.now() db.session.add(registrationObj) db.session.flush() return {"Success": True,"SuccessResponse": [{"message": "Valid Registration"}]}, 200 except: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "Invalid OTP/Registration"}]}, 404 else: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "No Such Id"}]}, 404
def initialize(self): self.name = 'user' self.ensureIndices( ['login', 'email', 'groupInvites.groupId', 'size', 'created']) self.prefixSearchFields = ('login', ('firstName', 'i'), ('lastName', 'i')) self.ensureTextIndex({ 'login': 1, 'firstName': 1, 'lastName': 1 }, language='none') self.exposeFields(level=AccessType.READ, fields=('_id', 'login', 'public', 'firstName', 'lastName', 'admin', 'created')) self.exposeFields(level=AccessType.ADMIN, fields=('size', 'email', 'groups', 'groupInvites', 'status', 'emailVerified')) # To ensure compatibility with authenticator apps, other defaults shouldn't be changed self._TotpFactory = TOTP.using( # An application secret could be set here, if it existed wallet=None) events.bind('model.user.save.created', CoreEventHandler.USER_SELF_ACCESS, self._grantSelfAccess) events.bind('model.user.save.created', CoreEventHandler.USER_DEFAULT_FOLDERS, self._addDefaultFolders)
def totp_factory(core_settings): authc_config = core_settings.AUTHC_CONFIG totp_settings = authc_config.get('totp') totp_context = totp_settings.get('context') totp_secrets = totp_context.get('secrets') return TOTP.using(secrets=totp_secrets, issuer="testing")
def create_totp_factory(env_var=None, file_path=None, authc_settings=None): if not authc_settings: yosai_settings = LazySettings(env_var=env_var, file_path=file_path) authc_settings = AuthenticationSettings(yosai_settings) totp_context = authc_settings.totp_context return TOTP.using(**totp_context)
def generateOTP(request): adminId = request.args.get('adminId') # Generate OTP TotpFactory = TOTP.using(secrets_path=PATH_TO_SECRET,issuer=ISSUER) totp = TotpFactory.new(digits=8) d = datetime.now() # Insert OR Update querystr = OneTimePassword \ .query \ .filter_by(adminId=adminId) \ .all() token_obj = totp.generate() if querystr: updateqstr = querystr[0] updateqstr.adminOTP = totp.to_json() updateqstr.adminOTPCreation = d updateqstr.adminOTPValidity = token_obj.expire_time else: # Create New Record totpDBObj = OneTimePassword(adminId=adminId, adminOTP = totp.to_json(), adminOTPCreation = d, adminOTPValidity = token_obj.expire_time) print(totpDBObj) db.session.add(totpDBObj) try: safeCommit() return {"Success": True,"SuccessResponse": [{"otp": token_obj.token}]}, 200 except: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "Could not Generate OTP"}]}, 404
def verify_credentials(self, authc_token, authc_info): submitted = authc_token.credentials stored = self.get_stored_credentials(authc_token, authc_info) service = self.cc_token_resolver[authc_token.__class__] try: if isinstance(authc_token, UsernamePasswordToken): result = service.verify(submitted, stored) if not result: raise IncorrectCredentialsException else: totp = TOTP(key=stored) totp.verify(submitted) except (ValueError, TokenError): raise IncorrectCredentialsException
def initialize(self): self.name = 'user' self.ensureIndices(['login', 'email', 'groupInvites.groupId', 'size', 'created']) self.prefixSearchFields = ( 'login', ('firstName', 'i'), ('lastName', 'i')) self.ensureTextIndex({ 'login': 1, 'firstName': 1, 'lastName': 1 }, language='none') self.exposeFields(level=AccessType.READ, fields=( '_id', 'login', 'public', 'firstName', 'lastName', 'admin', 'created')) self.exposeFields(level=AccessType.ADMIN, fields=( 'size', 'email', 'groups', 'groupInvites', 'status', 'emailVerified')) # To ensure compatibility with authenticator apps, other defaults shouldn't be changed self._TotpFactory = TOTP.using( # An application secret could be set here, if it existed wallet=None ) events.bind('model.user.save.created', CoreEventHandler.USER_SELF_ACCESS, self._grantSelfAccess) events.bind('model.user.save.created', CoreEventHandler.USER_DEFAULT_FOLDERS, self._addDefaultFolders)
def __init__(self, secrets, issuer): """ Initialize a totp factory. secrets are used to encrypt the per-user totp_secret on disk. """ # This should be a dict with at least one entry if not isinstance(secrets, dict) or len(secrets) < 1: raise ValueError("secrets needs to be a dict with at least one entry") self._totp = TOTP.using(issuer=issuer, secrets=secrets)
def gen_totp_qr(self): totp_factory = TOTP.using(secrets_path='totp_sec', issuer='sec.thelonelylands.com') totp = totp_factory.from_source(json.loads(self.totp)) uri = totp.to_uri(label=self.username) input('Please enlarge your screen and press enter.') print(pyqrcode.create(uri).terminal(quiet_zone=1)) print("Alternatively: " + totp.pretty_key())
def generateQRImage(request): registrationId = request.args.get('registrationId') TotpFactory = TOTP.using(secrets_path=PATH_TO_SECRET,issuer=ISSUER) totp = TotpFactory.new() uri = totp.to_uri(label=registrationId) qrurl = pyqrcode.create(uri) outputBuffer = BytesIO() qrurl.png(outputBuffer) outputBuffer.seek(0) return send_file(outputBuffer, mimetype="image/png")
def remembered_valid_thedude_totp_token(thedude_totp_key, cache_handler): keys = cache_handler.keys('*authentication*') for key in keys: cache_handler.cache_region.delete(key) token = int(TOTP(key=thedude_totp_key, digits=6).generate().token) yield TOTPToken(token, remember_me=True) keys = cache_handler.keys('*authentication*') for key in keys: cache_handler.cache_region.delete(key)
def auth_verify(self, token): try: int(token) except ValueError: return False else: token = int(token) totp_factory = TOTP.using(secrets_path='totp_sec', issuer='sec.thelonelylands.com') source = totp_factory.from_source(json.loads(self.totp)) try: verify = TOTP.verify(token=token, source=source, last_counter=self.totp_counter, window=10) except (passlib_errors.UsedTokenError, passlib_errors.InvalidTokenError, passlib_errors.MalformedTokenError): return False else: self.totp_counter = verify.counter return True
def _tokenFromTotpUri(totpUri, valid=True): # Create an external TOTP instance from passlib.totp import TOTP totp = TOTP.from_uri(totpUri) # Generate a valid token otpToken = totp.generate().token if not valid: # Increment the token by 1 to invalidate it otpToken = '%06d' % ((int(otpToken) + 1) % int(1e6)) return otpToken
def tf_setup(app): """ Initialize a totp factory. The TWO_FACTOR_SECRET is used to encrypt the per-user totp_secret on disk. """ secrets = config_value("TWO_FACTOR_SECRET", app=app) # This should be a dict with at least one entry if not isinstance(secrets, dict) or len(secrets) < 1: raise ValueError( "TWO_FACTOR_SECRET needs to be a dict with at least one" "entry") return TOTP.using(issuer=config_value("TWO_FACTOR_URI_SERVICE_NAME", app=app), secrets=secrets)
def test_user_get_login_no_secret(self): User = Pool().get('res.user') user = User(name='totp', login='******') user.save() with self.assertRaises(LoginException) as cm: User.get_login('totp', {}) self.assertEqual(cm.exception.name, 'totp_code') self.assertEqual(cm.exception.type, 'char') totp_code = TOTP(key=TOTP_SECRET_KEY).generate().token self.assertFalse(User.get_login('totp', { 'totp_code': totp_code, }))
def invalid_thedude_totp_token(cache_handler, yosai, monkeypatch): keys = cache_handler.keys('*authentication*') for key in keys: cache_handler.cache_region.delete(key) token = int(TOTP(key='AYAGB3C5RPYX5375L5VY2ULKZXMXWLZF', digits=6).generate().token) yield TOTPToken(totp_token=token) keys = cache_handler.keys('*authentication*') for key in keys: cache_handler.cache_region.delete(key) with Yosai.context(yosai): new_subject = Yosai.get_current_subject() da = new_subject.security_manager.authenticator monkeypatch.setattr(da.authc_settings, 'account_lock_threshold', 3) da.init_locking() da.locking_realm.unlock_account('thedude')
def initialize(self): self.name = 'protoUser' self.ensureIndices(['email', 'groupInvites.groupId']) self.prefixSearchFields = ('email') self.ensureTextIndex({ 'email': 1, }, language='none') self.exposeFields(level=AccessType.READ, fields=('_id', 'public', 'email', 'created')) self.exposeFields(level=AccessType.ADMIN, fields=('groupInvites', 'status')) # To ensure compatibility with authenticator apps, other defaults shouldn't be changed self._TotpFactory = TOTP.using( # An application secret could be set here, if it existed wallet=None) self._cryptContext = CryptContext(schemes=['bcrypt'])
def test_totp_check(self): pool = Pool() TOTPLogin = pool.get('res.user.login.totp') User = pool.get('res.user') user = User(name='totp', login='******', totp_secret=TOTP_SECRET_KEY) user.save() time = current_timestamp() totp_code = TOTP(key=TOTP_SECRET_KEY).generate(time=time).token totp_login, = TOTPLogin.create([{'user_id': user.id}]) self.assertFalse(totp_login.check('0', _time=time)) self.assertFalse(totp_login.check('invalid_code', _time=time)) self.assertFalse(totp_login.check('000000', _time=time)) self.assertTrue(totp_login.check(totp_code, _time=time)) # Second check raises warning about replay attack with self.assertRaises(LoginException) as cm: totp_login.check(totp_code, _time=time) self.assertEqual(cm.exception.name, 'totp_code') self.assertRegex(cm.exception.message, r'(?i)warning.*already.*used')
def handleRegistration(request): email = request.args.get('email') otp = request.args.get('otp') if __getAdminIdByEmail__(email): adminId = __getAdminIdByEmail__(email) else: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "Invalid Email"}]}, 404 TotpFactory = TOTP.using(secrets_path=PATH_TO_SECRET,issuer=ISSUER) querystr = OneTimePassword \ .query \ .filter_by(adminId=adminId) \ .all() if querystr: currentAdminOTPjson = querystr[0] adminOTPjson = currentAdminOTPjson.adminOTP totp = TotpFactory.from_json(adminOTPjson) try: totp.verify(otp,adminOTPjson) except: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "Invalid OTP"}]}, 404 currentAdminOTPjson.adminOTPValidity = 0 totp = TotpFactory.new(digits=8) registrationObj = AccountAdministratorRegistration(adminId=adminId,secretToken=totp.to_json()) db.session.add(registrationObj) db.session.flush() return {"Success": True,"SuccessResponse": [{"registrationId": registrationObj.registrationId}]}, 200 else: return {"Success": False,"ErrorResponse": [{"code": "12", "message": "No Id Found for the email provided"}]}, 404
## import pymongo, hashlib, uuid, hashlib, pyotp, base64,qrcode, pyqrcode from pymongo import MongoClient from bottle import post, get, route, run, template, request from passlib.totp import TOTP, generate_secret from passlib.exc import TokenError, MalformedTokenError mongoclient = MongoClient() db = mongoclient.giw collection = db['usuarios'] pimienta = "c6y]s4*u#L3r?tZ{3LYM95'vLq%DfmrF{'gjv[vs:B%!_FP3L)r$-r^;~swKcUabrcapata89" secret = generate_secret() TotpFactory = TOTP.using(secrets={"1":secret}) totpCounter = 0 ############## # APARTADO 1 # ############## # MECANISMO DE PROTECCIÓN DE CONTRASEÑAS: # # Almacenar las contraseñas en la BBDD de Mongo sin cifrado expone a los usuarios. # Para almacenar la contraseña de manera segura en la BBDD guardo ésta de la siguiente manera: # El hash de la contraseña (con SHA512) + sal (generación de cadena aleatoria) + pimienta (cadena de texto estática) # Además, quiero evitar Brute Force, y para ello uso un algoritmo de ralentizado (PBKDF2) # La función de hashlib se encarga de añadir la sal #.
def credentials(self, credentials): self._credentials = TOTP.normalize_token(credentials)
def test_two_factor_flag(app, client): # trying to verify code without going through two-factor # first login function wrong_code = b"000000" response = client.post( "/tf-validate", data=dict(code=wrong_code), follow_redirects=True ) message = b"You currently do not have permissions to access this page" assert message in response.data # Test login using invalid email data = dict(email="*****@*****.**", password="******") response = client.post("/login", data=data, follow_redirects=True) assert b"Specified user does not exist" in response.data response = client.post( "/login", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) assert b"Specified user does not exist" in response.data # Test login using valid email and invalid password data = dict(email="*****@*****.**", password="******") response = client.post("/login", data=data, follow_redirects=True) assert b"Invalid password" in response.data response = client.post( "/login", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) assert b"Invalid password" in response.data # Test two-factor authentication first login data = dict(email="*****@*****.**", password="******") response = client.post("/login", data=data, follow_redirects=True) message = b"Two-factor authentication adds an extra layer of security" assert message in response.data response = client.post( "/tf-setup", data=dict(setup="not_a_method"), follow_redirects=True ) assert b"Marked method is not valid" in response.data session = get_session(response) assert session["tf_state"] == "setup_from_login" # try non-existing setup on setup page (using json) data = dict(setup="not_a_method") response = client.post( "/tf-setup", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) assert response.status_code == 400 assert ( response.json["response"]["errors"]["setup"][0] == "Marked method is not valid" ) data = dict(setup="email") response = client.post( "/tf-setup", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) # Test for sms in process of valid login sms_sender = SmsSenderFactory.createSender("test") data = dict(email="*****@*****.**", password="******") response = client.post( "/login", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) assert b'"code": 200' in response.data assert sms_sender.get_count() == 1 session = get_session(response) assert session["tf_state"] == "ready" code = sms_sender.messages[0].split()[-1] # submit bad token to two_factor_token_validation response = client.post("/tf-validate", data=dict(code=wrong_code)) assert b"Invalid Token" in response.data # sumbit right token and show appropriate response response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True) assert b"Your token has been confirmed" in response.data # Upon completion, session cookie shouldnt have any two factor stuff in it. assert not tf_in_session(get_session(response)) # Test change two_factor view to from sms to mail with app.mail.record_messages() as outbox: setup_data = dict(setup="email") response = client.post("/tf-setup", data=setup_data, follow_redirects=True) msg = b"To complete logging in, please enter the code sent to your mail" assert msg in response.data # Fetch token validate form response = client.get("/tf-validate") assert response.status_code == 200 assert b'name="code"' in response.data code = outbox[0].body.split()[-1] # sumbit right token and show appropriate response response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True) assert b"You successfully changed your two-factor method" in response.data # Test change two_factor password confirmation view to authenticator # Setup authenticator setup_data = dict(setup="authenticator") response = client.post("/tf-setup", data=setup_data, follow_redirects=True) assert b"Open an authenticator app on your device" in response.data # verify png QRcode is present assert b"data:image/svg+xml;base64," in response.data # parse out key rd = response.data.decode("utf-8") matcher = re.match(r".*((?:\S{4}-){7}\S{4}).*", rd, re.DOTALL) totp_secret = matcher.group(1) # Generate token from passed totp_secret and confirm setup totp = TOTP(totp_secret) code = totp.generate().token response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True) assert b"You successfully changed your two-factor method" in response.data logout(client) # Test login with remember_token assert "remember_token" not in [c.name for c in client.cookie_jar] data = dict(email="*****@*****.**", password="******", remember=True) response = client.post( "/login", json=data, headers={"Content-Type": "application/json"}, follow_redirects=True, ) # Generate token from passed totp_secret code = totp.generate().token response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True) assert b"Your token has been confirmed" in response.data # Verify that the remember token is properly set found = False for cookie in client.cookie_jar: if cookie.name == "remember_token": found = True assert cookie.path == "/" assert found response = logout(client) # Verify that logout clears session info assert not tf_in_session(get_session(response)) # Test two-factor authentication first login data = dict(email="*****@*****.**", password="******") response = client.post("/login", data=data, follow_redirects=True) message = b"Two-factor authentication adds an extra layer of security" assert message in response.data # check availability of qrcode when this option is not picked assert b"data:image/png;base64," not in response.data # check availability of qrcode page when this option is picked setup_data = dict(setup="authenticator") response = client.post("/tf-setup", data=setup_data, follow_redirects=True) assert b"Open an authenticator app on your device" in response.data assert b"data:image/svg+xml;base64," in response.data # check appearence of setup page when sms picked and phone number entered sms_sender = SmsSenderFactory.createSender("test") data = dict(setup="sms", phone="+442083661177") response = client.post("/tf-setup", data=data, follow_redirects=True) assert b"To Which Phone Number Should We Send Code To" in response.data assert sms_sender.get_count() == 1 code = sms_sender.messages[0].split()[-1] response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True) assert b"Your token has been confirmed" in response.data assert not tf_in_session(get_session(response)) logout(client) # check when two_factor_rescue function should not appear rescue_data_json = dict(help_setup="lost_device") response = client.post( "/tf-rescue", json=rescue_data_json, headers={"Content-Type": "application/json"}, ) assert b'"code": 400' in response.data # check when two_factor_rescue function should appear data = dict(email="*****@*****.**", password="******") response = client.post("/login", data=data, follow_redirects=True) assert b"Please enter your authentication code" in response.data rescue_data = dict(help_setup="lost_device") response = client.post("/tf-rescue", data=rescue_data, follow_redirects=True) message = b"The code for authentication was sent to your email address" assert message in response.data rescue_data = dict(help_setup="no_mail_access") response = client.post("/tf-rescue", data=rescue_data, follow_redirects=True) message = b"A mail was sent to us in order to reset your application account" assert message in response.data
from trytond.i18n import gettext from trytond.model import ModelSQL, ModelView, fields from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, PYSONEncoder from trytond.transaction import Transaction from .exception import ( TOTPAccessCodeReuseError, TOTPInvalidSecretError, TOTPKeyTooShortError, TOTPLoginException) _totp_issuer = config.get( 'authentication_totp', 'issuer', default='{company} Tryton') _totp_key_length = config.get( 'authentication_totp', 'key_length', default=160) _TOTPFactory = TOTP.using(secrets_path=config.get( 'authentication_totp', 'application_secrets_file', default=None)) class User(metaclass=PoolMeta): __name__ = 'res.user' totp_key = fields.Char("TOTP Key") totp_secret = fields.Function(fields.Char( "TOTP Secret", help="Secret key used for time-based one-time password (TOTP) " "user authentication."), 'get_totp_secret', setter='set_totp_secret') totp_qrcode = fields.Function(fields.Binary( "TOTP QR Code", states={ 'invisible': ~Eval('totp_secret', '') | (not QRCode),