def run(self, secret, salt, keylen): """ run scrypt kdf for specified secret, salt, and keylen .. note:: * time cost is ``O(n * r * p)`` * mem cost is ``O(n * r)`` """ # stretch salt into initial byte array via pbkdf2 iv_bytes = self.iv_bytes input = pbkdf2_hmac("sha256", secret, salt, rounds=1, keylen=iv_bytes) # split initial byte array into 'p' mflen-sized chunks, # and run each chunk through smix() to generate output chunk. smix = self.smix if self.p == 1: output = smix(input) else: # XXX: *could* use threading here, if really high p values encountered, # but would tradeoff for more memory usage. smix_bytes = self.smix_bytes output = b"".join( smix(input[offset:offset + smix_bytes]) for offset in range(0, iv_bytes, smix_bytes)) # stretch final byte array into output via pbkdf2 return pbkdf2_hmac("sha256", secret, output, rounds=1, keylen=keylen)
def derive_digest(cls, password, salt, rounds, alg): """helper to create SaltedPassword digest for SCRAM. This performs the step in the SCRAM protocol described as:: SaltedPassword := Hi(Normalize(password), salt, i) :type password: unicode or utf-8 bytes :arg password: password to run through digest :type salt: bytes :arg salt: raw salt data :type rounds: int :arg rounds: number of iterations. :type alg: str :arg alg: name of digest to use (e.g. ``"sha-1"``). :returns: raw bytes of ``SaltedPassword`` """ if isinstance(password, bytes): password = password.decode("utf-8") # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8, # and handle normalizing alg name. return pbkdf2_hmac(alg, saslprep(password), salt, rounds)
def test_known(self): """test reference vectors""" for row in self.pbkdf2_test_vectors: correct, secret, salt, rounds, keylen = row[:5] digest = row[5] if len(row) == 6 else "sha1" result = pbkdf2_hmac(digest, secret, salt, rounds, keylen) self.assertEqual(result, correct)
def derive_digest(cls, password, salt, rounds, alg): """helper to create SaltedPassword digest for SCRAM. This performs the step in the SCRAM protocol described as:: SaltedPassword := Hi(Normalize(password), salt, i) :type password: unicode or utf-8 bytes :arg password: password to run through digest :type salt: bytes :arg salt: raw salt data :type rounds: int :arg rounds: number of iterations. :type alg: str :arg alg: name of digest to use (e.g. ``"sha-1"``). :returns: raw bytes of ``SaltedPassword`` """ if isinstance(password, bytes): password = password.decode("utf-8") # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8, # and handle normalizing alg name. return pbkdf2_hmac(alg, saslprep(password), salt, rounds)
def test_known(self): """test reference vectors""" for row in self.pbkdf2_test_vectors: correct, secret, salt, rounds, keylen = row[:5] digest = row[5] if len(row) == 6 else "sha1" result = pbkdf2_hmac(digest, secret, salt, rounds, keylen) self.assertEqual(result, correct)
def generate_symmetric_key(self, server_component, client_component, options=None): """ Generate a composite key from a server and client component using a PBKDF2-based scheme. :param server_component: The component usually generated by privacyIDEA :type server_component: hex string :param client_component: The component usually generated by the client (e.g. smartphone) :type client_component: hex string :param options: :return: the new generated key as hex string :rtype: str """ # As /token/init has already been called before, self.hashlib # is already set. keysize = keylen[self.hashlib] rounds = int(self.get_tokeninfo('2step_difficulty')) decoded_client_component = binascii.unhexlify(client_component) expected_client_size = int(self.get_tokeninfo('2step_clientsize')) if expected_client_size != len(decoded_client_component): raise ParameterError('Client Secret Size is expected to be {}, but is {}'.format( expected_client_size, len(decoded_client_component) )) # Based on the two components, we generate a symmetric key using PBKDF2 # We pass the hex-encoded server component as the password and the # client component as the salt. secret = pbkdf2_hmac(digest='sha1', secret=server_component.lower(), salt=decoded_client_component, rounds=rounds, keylen=keysize) return hexlify_and_unicode(secret)
def decrypt_secret(b64str, pincode): # split the secret into components try: (scheme, salt, ciphertext) = b64str.split('$') salt = base64.b64decode(salt) ciphertext = base64.b64decode(ciphertext) except (ValueError, TypeError): raise totpcgi.UserSecretError('Failed to parse encrypted secret') key = pbkdf2_hmac('sha256', pincode, salt, KDF_ITER, KEY_SIZE * 2) aes_key = key[:KEY_SIZE] hmac_key = key[KEY_SIZE:] sig_size = hashlib.sha256().digest_size sig = ciphertext[-sig_size:] data = ciphertext[:-sig_size] # verify hmac sig first if hmac.new(hmac_key, data, hashlib.sha256).digest() != sig: raise totpcgi.UserSecretError('Failed to verify hmac!') iv_bytes = data[:AES_BLOCK_SIZE] data = data[AES_BLOCK_SIZE:] cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) data = cypher.decrypt(data) padlen = bytearray((data[-1], ))[0] secret = data[:-padlen].decode('utf-8') logger.debug('Decryption successful') return secret
def derive_key(xml, password): """ Derive the encryption key from the password with the parameters given in the XML soup. :param xml: The XML :param password: the password :return: The derived key, hexlified """ if not password: raise ImportException("The XML KeyContainer specifies a derived " "encryption key, but no password given!") keymeth = xml.keycontainer.encryptionkey.derivedkey.keyderivationmethod derivation_algo = keymeth["algorithm"].split("#")[-1] if derivation_algo.lower() != "pbkdf2": raise ImportException("We only support PBKDF2 as Key derivation " "function!") salt = keymeth.find("salt").text.strip() keylength = keymeth.find("keylength").text.strip() rounds = keymeth.find("iterationcount").text.strip() r = pbkdf2_hmac('sha1', to_utf8(password), base64.b64decode(salt), rounds=int(rounds), keylen=int(keylength)) return binascii.hexlify(r)
def encrypt_secret(strdata, pincode): data = strdata.encode('utf-8') salt = os.urandom(SALT_SIZE) # derive a twice-long key from pincode key = pbkdf2_hmac('sha256', pincode, salt, KDF_ITER, KEY_SIZE * 2) # split the key in two, one used for AES, another for HMAC aes_key = key[:KEY_SIZE] hmac_key = key[KEY_SIZE:] pad = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE data += pad * bytearray((pad, )) iv_bytes = os.urandom(AES_BLOCK_SIZE) cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) data = iv_bytes + cypher.encrypt(data) sig = hmac.new(hmac_key, data, hashlib.sha256).digest() # jab it all together in a base64-encrypted format b64str = ('aes256+hmac256$' + base64.b64encode(salt).decode('utf-8').replace('\n', '') + '$' + base64.b64encode(data + sig).decode('utf-8').replace('\n', '')) logger.debug('Encrypted secret: %s', b64str) return b64str
def create(password): salt = Random.get_random_bytes(saltSize) vek = Random.get_random_bytes(keysize) #print("VEK: " + str(binascii.hexlify(vek))) kek = digest.pbkdf2_hmac("sha256", password, salt, iterations, keysize); wrapped_key = aes_wrap_key(kek, vek) #print("WrappedKey: " + str(binascii.hexlify(wrapped_key))) return PasswordWrappedKeyBag(salt, iterations, keysize, wrapped_key)
def test_30_2step_otpkeyformat(self): serial = "2step3" db_token = Token(serial, tokentype="hotp") db_token.save() token = HotpTokenClass(db_token) token.update({ "2stepinit": "1", "2step_clientsize": "12", "hashlib": "sha512", }) self.assertEqual(token.token.rollout_state, "clientwait") self.assertEqual(token.get_tokeninfo("2step_clientsize"), "12") # fetch the server component for later tests server_component = binascii.unhexlify( token.token.get_otpkey().getKey()) # generate a 12-byte client component client_component = b'abcdefghijkl' checksum = hashlib.sha1(client_component).digest()[:4] # wrong checksum with warnings.catch_warnings(): warnings.simplefilter('ignore', category=DeprecationWarning) self.assertRaisesRegexp( ParameterError, "Incorrect checksum", token.update, { "otpkey": b32encode_and_unicode(b"\x37" + checksum[1:] + client_component).strip("="), "otpkeyformat": "base32check", }) # construct a secret token.update({ "otpkey": b32encode_and_unicode(checksum + client_component).strip("="), "otpkeyformat": "base32check", # the following values are ignored "2step_serversize": "23", "2step_difficulty": "666666", "2step_clientsize": "13" }) # check the generated secret secret = binascii.unhexlify(token.token.get_otpkey().getKey()) # check the correct lengths self.assertEqual(len(server_component), 64) # because of SHA-512 self.assertEqual(len(client_component), 12) self.assertEqual(len(secret), 64) # because of SHA-512 # check the secret has been generated according to the specification expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 10000, len(secret)) self.assertEqual(secret, expected_secret) self.assertTrue(token.token.active)
def testExtractWrappedKey(self): wrapped = binascii.unhexlify("5934f7d07e75f5ab55b9051ebd39331dbfba3c597589b203728043577bf93badeb9f07f528c8bd95") target_kek = binascii.unhexlify("9bc68a8c80a008d758de97cebc7ec39d6274512e3ddbdd5baf4eb8557ab7e58f") target_vek = binascii.unhexlify("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") g = rdflib.Graph() g.parse(data=keybagturtle, format="turtle") kb = keybag.PasswordWrappedKeyBag.load(g) key = "password" kek = digest.pbkdf2_hmac("sha256", key, kb.salt, kb.iterations, kb.keySizeBytes); self.assertEquals(target_kek, kek) vek = aes_unwrap_key(kek, kb.wrappedKey) self.assertEquals(target_vek, vek)
async def add_mattermost_server(self, host, team, password): curs = db.cursor() curs.execute("SELECT host, team, password FROM mattermost_accounts WHERE user=?", (self.internal_id, )) if curs.fetchall(): await self.reply('Sorry, no support for multiple servers yet') return key = pbkdf2_hmac('sha256', self.actual_password, self.nick, 10000) iv = Random.new().read(AES.block_size) cipher = AES.new(key, AES.MODE_CFB, iv) msg = iv + cipher.encrypt(password) curs.execute("INSERT INTO mattermost_accounts (user, host, team, password) VALUES (?,?,?,?)", (self.internal_id, host, team, base64.b64encode(msg))) db.commit() await self.reply('New server added') await self.connect_to_mattermost()
def raw(cls, secret, user): """encode password using msdcc v2 algorithm :type secret: unicode or utf-8 bytes :arg secret: secret :type user: str :arg user: username to use as salt :returns: returns string of raw bytes """ from passlib.crypto.digest import pbkdf2_hmac secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le") user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le") tmp = md4(md4(secret).digest() + user).digest() return pbkdf2_hmac("sha1", tmp, user, 10240, 16)
def testDecrypt(self): g = rdflib.Graph() g.parse(data=keybagturtle, format="turtle") kb = keybag.PasswordWrappedKeyBag.load(g) key = "password" kek = digest.pbkdf2_hmac("sha256", key, kb.salt, kb.iterations, kb.keySizeBytes); vek = aes_unwrap_key(kek, kb.wrappedKey) key1 = vek[0:16] key2 = vek[16:] tweak = codecs.decode('00', 'hex') cipher = python_AES.new((key1, key2), python_AES.MODE_XTS) text = cipher.decrypt(target_ciphertext, tweak) self.assertEqual(src[0:len(src)], text[0:len(src)])
def raw(cls, secret, user): """encode password using msdcc v2 algorithm :type secret: unicode or utf-8 bytes :arg secret: secret :type user: str :arg user: username to use as salt :returns: returns string of raw bytes """ from passlib.crypto.digest import pbkdf2_hmac secret = to_unicode(secret, "utf-8", param="secret").encode("utf-16-le") user = to_unicode(user, "utf-8", param="user").lower().encode("utf-16-le") tmp = md4(md4(secret).digest() + user).digest() return pbkdf2_hmac("sha1", tmp, user, 10240, 16)
async def connect_to_mattermost(self): curs = db.cursor() curs.execute("SELECT host, team, password FROM mattermost_accounts WHERE user=?", (self.internal_id, )) servers = curs.fetchall() if len(servers) == 0: await self.reply('No mattermost servers configured yet, not connecting to anything') elif len(servers) == 1: for host, team, pwd in servers: await self.reply('Connecting to server {0}'.format(host)) # Decrypt the password. Strart by un-b64ing it key = pbkdf2_hmac('sha256', self.actual_password, self.nick, 10000) msg = base64.b64decode(pwd) dec = AES.new(key, AES.MODE_CFB, msg[:16]) password = dec.decrypt(msg[16:]).decode('utf8') self.team_name = team asyncio.ensure_future(self.mattermost_handler(host, password)) else: await self.reply('XXX: Multiple servers not implemented yet')
async def add_mattermost_server(self, host, team, password): curs = db.cursor() curs.execute( "SELECT host, team, password FROM mattermost_accounts WHERE user=?", (self.internal_id, )) if curs.fetchall(): await self.reply('Sorry, no support for multiple servers yet') return key = pbkdf2_hmac('sha256', self.actual_password, self.nick, 10000) iv = Random.new().read(AES.block_size) cipher = AES.new(key, AES.MODE_CFB, iv) msg = iv + cipher.encrypt(password) curs.execute( "INSERT INTO mattermost_accounts (user, host, team, password) VALUES (?,?,?,?)", (self.internal_id, host, team, base64.b64encode(msg))) db.commit() await self.reply('New server added') await self.connect_to_mattermost()
def _check_pbkdf2sha256(self, password, hash): hashParts = hash.split("$") if len(hashParts) == 4 and hashParts[0] == "PBKDF2SHA256": iterations = int(hashParts[1]) saltBytes = bytes.fromhex(hashParts[2]) storedHash = bytes.fromhex(hashParts[3]) computedHash = pbkdf2_hmac( digest="sha256", secret=password, salt=saltBytes, rounds=iterations, ) return storedHash == computedHash else: self.logger.warning("Unknown seafile hash-format: %s", hash) return False
def test_29_2step_generation_custom(self): serial = "2step2" db_token = Token(serial, tokentype="hotp") db_token.save() token = HotpTokenClass(db_token) token.update({ "2stepinit": "1", "2step_serversize": "40", "2step_difficulty": "12345", "2step_clientsize": "12", "hashlib": "sha512", }) self.assertEqual(token.token.rollout_state, "clientwait") self.assertEqual(token.get_tokeninfo("2step_clientsize"), "12") self.assertEqual(token.get_tokeninfo("2step_difficulty"), "12345") # fetch the server component for later tests server_component = binascii.unhexlify( token.token.get_otpkey().getKey()) # too short self.assertRaises(ParameterError, token.update, {"otpkey": binascii.hexlify(b"=" * 8)}) # generate a 12-byte client component client_component = b'abcdefghijkl' # construct a secret token.update({ "otpkey": binascii.hexlify(client_component), # the following values are ignored "2step_serversize": "23", "2step_difficulty": "666666", "2step_clientsize": "13" }) # check the generated secret secret = binascii.unhexlify(token.token.get_otpkey().getKey()) # check the correct lengths self.assertEqual(len(server_component), 40) self.assertEqual(len(client_component), 12) self.assertEqual(len(secret), 64) # because of SHA-512 # check the secret has been generated according to the specification expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 12345, len(secret)) self.assertEqual(secret, expected_secret) self.assertTrue(token.token.active)
def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"): """pkcs#5 password-based key derivation v2.0 :arg secret: passphrase to use to generate key :arg salt: salt string to use when generating key :param rounds: number of rounds to use to generate key :arg keylen: number of bytes to generate. if set to ``None``, will use digest size of selected prf. :param prf: psuedo-random family to use for key strengthening. this must be a string starting with ``"hmac-"``, followed by the name of a known digest. this defaults to ``"hmac-sha1"`` (the only prf explicitly listed in the PBKDF2 specification) .. rst-class:: warning .. versionchanged 1.7: This argument no longer supports arbitrary PRF callables -- These were rarely / never used, and created too many unwanted codepaths. :returns: raw bytes of generated key .. deprecated:: 1.7 This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`, and will be removed in Passlib 2.0. *Note the call signature has changed.* """ if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)): raise NotImplementedError( "non-HMAC prfs are not supported as of Passlib 1.7") digest = prf[5:] return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
def pbkdf2(secret, salt, rounds, keylen=None, prf="hmac-sha1"): """pkcs#5 password-based key derivation v2.0 :arg secret: passphrase to use to generate key :arg salt: salt string to use when generating key :param rounds: number of rounds to use to generate key :arg keylen: number of bytes to generate. if set to ``None``, will use digest size of selected prf. :param prf: psuedo-random family to use for key strengthening. this must be a string starting with ``"hmac-"``, followed by the name of a known digest. this defaults to ``"hmac-sha1"`` (the only prf explicitly listed in the PBKDF2 specification) .. rst-class:: warning .. versionchanged 1.7: This argument no longer supports arbitrary PRF callables -- These were rarely / never used, and created too many unwanted codepaths. :returns: raw bytes of generated key .. deprecated:: 1.7 This has been deprecated in favor of :func:`passlib.crypto.digest.pbkdf2_hmac`, and will be removed in Passlib 2.0. *Note the call signature has changed.* """ if callable(prf) or (isinstance(prf, native_string_types) and not prf.startswith(_HMAC_PREFIXES)): raise NotImplementedError("non-HMAC prfs are not supported as of Passlib 1.7") digest = prf[5:] return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
def testWrap(self): keysize = 0x20 # in bytes key = "password" iterations = 147256 saltSize = 16 salt = binascii.unhexlify("000102030405060708090a0b0c0d0e0f") #hhh = hashlib.pbkdf2_hmac("sha256", key.encode(), salt, iterations, keysize); #print(len(hhh)) #print(binascii.hexlify(hhh)) kek = digest.pbkdf2_hmac("sha256", key, salt, iterations, keysize); print(binascii.hexlify(kek)) #h = pbkdf2_sha256.encrypt(key, rounds=iterations, salt_size=saltSize) # print(h) vek = binascii.unhexlify("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") print(len(vek)) wrapped_key= aes_wrap_key(kek, vek) print(binascii.hexlify(wrapped_key)) plaintext = src + b'\x00' * (512-len(src)) #msg = dict_xts_aes['msg%i' % i].decode('hex') #key = (dict_xts_aes['key1_%i' % i].decode('hex'), dict_xts_aes['key2_%i' % i].decode('hex')) key1 = vek[0:16] key2 = vek[16:] #cip = dict_xts_aes['cip%i' % i].decode('hex') #n = dict_xts_aes['n%i' % i].decode('hex') tweak = codecs.decode('00', 'hex') print(len(tweak)) cipher = python_AES.new((key1,key2), python_AES.MODE_XTS) ciphertext = cipher.encrypt(plaintext, tweak) print(len(ciphertext)) print(binascii.hexlify(ciphertext))
def test_28_2step_generation_default(self): serial = "2step" db_token = Token(serial, tokentype="hotp") db_token.save() token = HotpTokenClass(db_token) token.update({"2stepinit": "1"}) # fetch the server component for later tests server_component = binascii.unhexlify(token.token.get_otpkey().getKey()) # generate a 8-byte client component client_component = b'abcdefgh' # construct a secret token.update({"otpkey": binascii.hexlify(client_component)}) # check the generated secret secret = binascii.unhexlify(token.token.get_otpkey().getKey()) # check the correct lengths self.assertEqual(len(server_component), 20) self.assertEqual(len(client_component), 8) self.assertEqual(len(secret), 20) # check the secret has been generated according to the specification expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 10000, 20) self.assertEqual(secret, expected_secret)
async def connect_to_mattermost(self): curs = db.cursor() curs.execute( "SELECT host, team, password FROM mattermost_accounts WHERE user=?", (self.internal_id, )) servers = curs.fetchall() if len(servers) == 0: await self.reply( 'No mattermost servers configured yet, not connecting to anything' ) elif len(servers) == 1: for host, team, pwd in servers: await self.reply('Connecting to server {0}'.format(host)) # Decrypt the password. Strart by un-b64ing it key = pbkdf2_hmac('sha256', self.actual_password, self.nick, 10000) msg = base64.b64decode(pwd) dec = AES.new(key, AES.MODE_CFB, msg[:16]) password = dec.decrypt(msg[16:]).decode('utf8') self.team_name = team asyncio.ensure_future(self.mattermost_handler(host, password)) else: await self.reply('XXX: Multiple servers not implemented yet')
def derive_key(xml, password): """ Derive the encryption key from the password with the parameters given in the XML soup. :param xml: The XML :param password: the password :return: The derived key, hexlified """ if not password: raise ImportException("The XML KeyContainer specifies a derived " "encryption key, but no password given!") keymeth= xml.keycontainer.encryptionkey.derivedkey.keyderivationmethod derivation_algo = keymeth["algorithm"].split("#")[-1] if derivation_algo.lower() != "pbkdf2": raise ImportException("We only support PBKDF2 as Key derivation " "function!") salt = keymeth.find("salt").text.strip() keylength = keymeth.find("keylength").text.strip() rounds = keymeth.find("iterationcount").text.strip() r = pbkdf2_hmac('sha1', to_utf8(password), base64.b64decode(salt), rounds=int(rounds), keylen=int(keylength)) return binascii.hexlify(r)
def _calc_checksum(self, secret): # TODO: find out what crowd's policy is re: unicode # crowd seems to use a fixed number of rounds. # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha1", secret, self.salt, 10000, 32)
def _calc_checksum(self, secret): # TODO: find out what grub's policy is re: unicode # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha512", secret, self.salt, self.rounds, 64)
def test_06_force_totp_parameters(self): set_policy( name="force_2step", action=["totp_2step=force", "enrollTOTP=1", "delete"], scope=SCOPE.ADMIN, ) set_policy( name="2step_params", action=[ "totp_2step_difficulty=12345", "totp_2step_serversize=33", "totp_2step_clientsize=11" ], scope=SCOPE.ENROLL, ) with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "genkey": "1", "2stepinit": "0", # will be forced nevertheless "2step_serversize": "3", "2step_clientsize": "4", "2step_difficulty": "33333", "timeStep": "60", "hashlib": "sha512", "otplen": "8", }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) google_url = detail["googleurl"]["value"] self.assertIn('2step_difficulty=12345', google_url) self.assertIn('2step_salt=11', google_url) self.assertIn('2step_output=64', google_url) # Authentication does not work yet! wrong_otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate( key=server_component, counter=int(time.time() // 60)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": wrong_otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json self.assertTrue(result.get("result").get("status")) self.assertFalse(result.get("result").get("value")) self.assertEqual( result.get("detail").get("message"), u'matching 1 tokens, Token is disabled') client_component = b"wrongsize" # 9 bytes hex_client_component = binascii.hexlify(client_component) # Supply a client secret of incorrect size with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": hex_client_component, }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) result = res.json.get("result") self.assertFalse(result.get("status")) client_component = b"correctsize" # 11 bytes hex_client_component = binascii.hexlify(client_component) # Now doing the correct 2nd step with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": hex_client_component, "2step_serversize": "3", # will have no effect "2step_clientsize": "4", "2step_difficulty": "33333" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate( key=otpkey_bin, counter=int(time.time() // 60)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check serversize self.assertEqual(len(server_component), 33) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 12345, 64) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("force_2step") delete_policy("2step_params")
def test_05_init_totp_token(self): set_policy( name="allow_2step", action=["totp_2step=allow", "enrollTOTP=1", "delete"], scope=SCOPE.ADMIN, ) with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "genkey": "1", "2stepinit": "1" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) google_url = detail["googleurl"]["value"] self.assertIn('2step_difficulty=10000', google_url) self.assertIn('2step_salt=8', google_url) self.assertIn('2step_output=20', google_url) self.assertEqual(detail['2step_difficulty'], 10000) self.assertEqual(detail['2step_salt'], 8) self.assertEqual(detail['2step_output'], 20) client_component = b"VRYSECRT" checksum = hashlib.sha1(client_component).digest()[:4] base32check_client_component = base64.b32encode( checksum + client_component).strip(b"=") # Try to do a 2stepinit on a second step will raise an error with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "2stepinit": "1", "serial": serial, "otpkey": base32check_client_component, "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 400) result = res.json.get("result") self.assertIn( '2stepinit is only to be used in the first initialization step', result.get("error").get("message")) # Invalid base32check will raise an error with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "2stepinit": "1", "serial": serial, "otpkey": b"A" + base32check_client_component[1:], "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 400) result = res.json.get("result") self.assertIn('Malformed base32check data: Incorrect checksum', result.get("error").get("message")) # Authentication does not work yet! wrong_otp_value = HmacOtp().generate(key=server_component, counter=int(time.time() // 30)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": wrong_otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json self.assertTrue(result.get("result").get("status")) self.assertFalse(result.get("result").get("value")) self.assertEqual( result.get("detail").get("message"), u'matching 1 tokens, Token is disabled') # Now doing the correct 2nd step with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": base32check_client_component, "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] self.assertNotIn('2step', detail) # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp().generate(key=otpkey_bin, counter=int(time.time() // 30)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 10000, 20) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("allow_2step")
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha1", secret, self.salt, self.rounds, 20)
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"): return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using UTF8 return pbkdf2_hmac(self._digest, secret, self.salt, self.rounds, self.checksum_size)
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using UTF8 return pbkdf2_hmac(self._digest, secret, self.salt, self.rounds, self.checksum_size)
def _calc_checksum(self, secret): # TODO: find out what grub's policy is re: unicode # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha512", secret, self.salt, self.rounds, 64)
def _calc_checksum(self, secret): # TODO: find out what crowd's policy is re: unicode # crowd seems to use a fixed number of rounds. # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha1", secret, self.salt, 10000, 32)
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 salt = self._get_config() result = pbkdf2_hmac("sha1", secret, salt, self.rounds, 24) return ab64_encode(result).decode("ascii")
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 salt = self._get_config() result = pbkdf2_hmac("sha1", secret, salt, self.rounds, 24) return ab64_encode(result).decode("ascii")
def _calc_checksum(self, secret): # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8 return pbkdf2_hmac("sha1", secret, self.salt, self.rounds, 20)
def helper(): result = hexlify( pbkdf2_hmac("sha1", "abracadabra", "open sesame", 10240, 20)) assert result == 'e45ce658e79b16107a418ad4634836f5f0601ad1', result
def _calc_checksum(self, secret): # NOTE: secret & salt will be encoded using UTF-8 by pbkdf2_hmac() hash = pbkdf2_hmac(self._digest, secret, self.salt, self.rounds) return b64encode(hash).rstrip().decode("ascii")
def helper(): result = hexlify( pbkdf2_hmac("sha256", "abracadabra", "open sesame", 10240, 32)) assert result == 'fadef97054306c93c55213cd57111d6c0791735dcdde8ac32f9f934b49c5af1e', result
def test_03_custom_parameters(self): set_policy( name="enrollhotp", action=["enrollHOTP=1", "delete", "hotp_2step=allow"], scope=SCOPE.ADMIN, ) with self.app.test_request_context( '/token/init', method='POST', data={ "type": "hotp", "genkey": "1", "2stepinit": "1", "2step_serversize": "5", "2step_clientsize": "16", "2step_difficulty": "17898", "hashlib": "sha512", # force 64-byte secret "otplen": "8", }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) client_component = b"wrongsize0" # 10 bytes hex_client_component = binascii.hexlify(client_component) # Supply a client secret of incorrect size with self.app.test_request_context('/token/init', method='POST', data={ "type": "hotp", "serial": serial, "otpkey": hex_client_component, }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) result = res.json.get("result") self.assertFalse(result.get("status")) client_component = b"correctsizeABCDE" # 16 bytes hex_client_component = binascii.hexlify(client_component) # Now doing the correct 2nd step with self.app.test_request_context( '/token/init', method='POST', data={ "type": "hotp", "serial": serial, "otpkey": hex_client_component, "2step_serversize": "3", # will have no effect "2step_clientsize": "4", "2step_difficulty": "33333" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate(key=otpkey_bin, counter=1) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check serversize self.assertEqual(len(server_component), 5) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 17898, 64) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("enrollhotp")
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"): return pbkdf2_hmac(digest, secret, salt, rounds, keylen)