Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ the [research paper](https://eprint.iacr.org/2016/027.pdf) or their [website](ht

Works on Python 3.

Check test vectors with `python -m unittest`.

## Background

Balloon Hashing is a new hashing function that, according to the paper, is:
Expand Down
56 changes: 56 additions & 0 deletions balloon.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import concurrent.futures
import hashlib

hash_functions = {
Expand Down Expand Up @@ -146,3 +147,58 @@ def balloon_hash(password, salt):
time_cost = 20
space_cost = 16
return balloon(password, salt, space_cost, time_cost, delta=delta).hex()

def balloon_m(password, salt, space_cost, time_cost, parallel_cost, delta=3) -> bytes:
"""M-core variant of the Balloon hashing algorithm. Note the result
is returned as bytes, for a more friendly function with default
values and returning a hex string see the function balloon_m_hash

Args:
password (str): The main string to hash
salt (str): A user defined random value for security
space_cost (int): The size of the buffer
time_cost (int): Number of rounds to mix
parallel_cost (int): Number of concurrent instances
delta (int): Number of random blocks to mix with.

Returns:
str: A series of bytes, the hash.

"""
output = b''

with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []

for p in range(parallel_cost):
parallel_salt = b'' + salt.encode('utf-8') + (p + 1).to_bytes(8, "little")
futures.append(executor.submit(balloon, password, parallel_salt, space_cost, time_cost, delta=delta))
for future in concurrent.futures.as_completed(futures):
result = future.result()

if len(output) == 0:
output = result
else:
output = bytes([_a ^ _b for _a, _b in zip(output, result)])

return hash_func(password, salt, output)


def balloon_m_hash(password, salt):
"""A more friendly client function that just takes
a password and a salt and computes outputs the hash in hex.
This uses the M-core variant of the Balloon hashing algorithm.

Args:
password (str): The main string to hash
salt (str): A user defined random value for security

Returns:
str: The hash as hex.

"""
delta = 4
time_cost = 20
space_cost = 16
parallel_cost = 4
return balloon_m(password, salt, space_cost, time_cost, parallel_cost, delta=delta).hex()
122 changes: 122 additions & 0 deletions test_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from balloon import balloon,balloon_m
import unittest

class TestBalloon(unittest.TestCase):

def test_vectors(self):
test_vectors = [
{
"password": "hunter42",
"salt": "examplesalt",
"s_cost": 1024,
"t_cost": 3,
"output": "716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb",
},
{
"password": "",
"salt": "salt",
"s_cost": 3,
"t_cost": 3,
"output": "5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378",
},
{
"password": "password",
"salt": "",
"s_cost": 3,
"t_cost": 3,
"output": "20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc",
},
{
"password": "\0",
"salt": "\0",
"s_cost": 3,
"t_cost": 3,
"output": "4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4",
},
{
"password": "password",
"salt": "salt",
"s_cost": 1,
"t_cost": 1,
"output": "eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545",
},
]

for test_vector in test_vectors:
self.assertEqual(balloon(test_vector["password"], test_vector["salt"], test_vector["s_cost"], test_vector["t_cost"]).hex(), test_vector["output"])

class TestBalloon(unittest.TestCase):

def test_vectors(self):
test_vectors = [
{
"password": "hunter42",
"salt": "examplesalt",
"s_cost": 1024,
"t_cost": 3,
"p_cost": 4,
"output": "1832bd8e5cbeba1cb174a13838095e7e66508e9bf04c40178990adbc8ba9eb6f",
},
{
"password": "",
"salt": "salt",
"s_cost": 3,
"t_cost": 3,
"p_cost": 2,
"output": "f8767fe04059cef67b4427cda99bf8bcdd983959dbd399a5e63ea04523716c23",
},
{
"password": "password",
"salt": "",
"s_cost": 3,
"t_cost": 3,
"p_cost": 3,
"output": "bcad257eff3d1090b50276514857e60db5d0ec484129013ef3c88f7d36e438d6",
},
{
"password": "password",
"salt": "",
"s_cost": 3,
"t_cost": 3,
"p_cost": 1,
"output": "498344ee9d31baf82cc93ebb3874fe0b76e164302c1cefa1b63a90a69afb9b4d",
},
{
"password": "\000",
"salt": "\000",
"s_cost": 3,
"t_cost": 3,
"p_cost": 4,
"output": "8a665611e40710ba1fd78c181549c750f17c12e423c11930ce997f04c7153e0c",
},
{
"password": "\000",
"salt": "\000",
"s_cost": 3,
"t_cost": 3,
"p_cost": 1,
"output": "d9e33c683451b21fb3720afbd78bf12518c1d4401fa39f054b052a145c968bb1",
},
{
"password": "password",
"salt": "salt",
"s_cost": 1,
"t_cost": 1,
"p_cost": 16,
"output": "a67b383bb88a282aef595d98697f90820adf64582a4b3627c76b7da3d8bae915",
},
{
"password": "password",
"salt": "salt",
"s_cost": 1,
"t_cost": 1,
"p_cost": 1,
"output": "97a11df9382a788c781929831d409d3599e0b67ab452ef834718114efdcd1c6d",
},
]

for test_vector in test_vectors:
self.assertEqual(balloon_m(test_vector["password"], test_vector["salt"], test_vector["s_cost"], test_vector["t_cost"], test_vector["p_cost"]).hex(), test_vector["output"])

if __name__ == '__main__':
unittest.main()