Skip to content

change password-to-scalar and seed-to-arbitrary-element functions #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 12, 2016
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ python:
- "3.4"
- "3.5"
- "pypy"
- "pypy3"
install:
- pip install -U tox virtualenv coverage python-coveralls
script:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

* License: MIT
* Dependencies: none (pure-python)
* Compatible With: Python 2.6, 2.7, 3.3, 3.4, 3.5, pypy2, pypy3
* Compatible With: Python 2.6, 2.7, 3.3, 3.4, 3.5, pypy2
* [![Build Status](https://travis-ci.org/warner/python-spake2.png?branch=master)](https://travis-ci.org/warner/python-spake2) [![Coverage Status](https://coveralls.io/repos/warner/python-spake2/badge.svg)](https://coveralls.io/r/warner/python-spake2)

This library implements the SPAKE2 password-authenticated key exchange
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ def abbrev(t):
"Programming Language :: Python :: 3.5",
"Topic :: Security :: Cryptography",
],
install_requires=["hkdf"],
)
8 changes: 6 additions & 2 deletions src/spake2/ed25519_basic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import binascii, hashlib, itertools
from .groups import expand_arbitrary_element_seed

Q = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493
Expand Down Expand Up @@ -267,8 +268,11 @@ def subtract(self, other):


def arbitrary_element(seed): # unknown DL
# TODO: if we don't need uniformity, maybe use just sha256 here?
hseed = hashlib.sha512(seed).digest()
# We don't strictly need the uniformity provided by hashing to an
# oversized string (128 bits more than the field size), then reducing
# down to Q. But it's comforting, and it's the same technique we use for
# converting passwords/seeds to scalars (which *does* need uniformity).
hseed = expand_arbitrary_element_seed(seed, (256/8)+16)
y = int(binascii.hexlify(hseed), 16) % Q

# we try successive Y values until we find a valid point
Expand Down
3 changes: 2 additions & 1 deletion src/spake2/ed25519_group.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import ed25519_basic
from .groups import password_to_scalar

class _Ed25519Group:
def random_scalar(self, entropy_f):
Expand All @@ -8,7 +9,7 @@ def scalar_to_bytes(self, i):
def bytes_to_scalar(self, b):
return ed25519_basic.bytes_to_scalar(b)
def password_to_scalar(self, pw):
return ed25519_basic.password_to_scalar(pw)
return password_to_scalar(pw, self.scalar_size_bytes, self.order())
def arbitrary_element(self, seed):
return ed25519_basic.arbitrary_element(seed)
def bytes_to_element(self, b):
Expand Down
57 changes: 26 additions & 31 deletions src/spake2/groups.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import division
import hashlib
from hkdf import Hkdf
from .six import integer_types
from .util import (size_bits, size_bytes, unbiased_randrange,
bytes_to_number, number_to_bytes)
Expand Down Expand Up @@ -61,6 +62,24 @@
"""


def expand_password(data, num_bytes):
h = Hkdf(salt=b"", input_key_material=data, hash=hashlib.sha256)
info = b"SPAKE2 pw"
return h.expand(info, num_bytes)

def password_to_scalar(pw, scalar_size_bytes, q):
assert isinstance(pw, bytes)
# the oversized hash reduces bias in the result, so
# uniformly-random passwords give nearly-uniform scalars
oversized = expand_password(pw, scalar_size_bytes+16)
assert len(oversized) >= scalar_size_bytes
i = bytes_to_number(oversized)
return i % q

def expand_arbitrary_element_seed(data, num_bytes):
h = Hkdf(salt=b"", input_key_material=data, hash=hashlib.sha256)
info = b"SPAKE2 arbitrary element"
return h.expand(info, num_bytes)

class _Element:
def __init__(self, group, e):
Expand All @@ -76,7 +95,7 @@ def to_bytes(self):
return self._group._element_to_bytes(self)

class IntegerGroup:
def __init__(self, p, q, g, element_hasher):
def __init__(self, p, q, g):
self.q = q # the subgroup order, used for scalars
self.scalar_size_bytes = size_bytes(self.q)
_s = self.scalar_to_bytes(self.password_to_scalar(b""))
Expand All @@ -89,10 +108,6 @@ def __init__(self, p, q, g, element_hasher):
self.p = p # the field size
self.element_size_bits = size_bits(self.p)
self.element_size_bytes = size_bytes(self.p)
_e = element_hasher(b"")
assert isinstance(_e, bytes)
assert len(_e) >= self.element_size_bytes
self.element_hasher = element_hasher

# double-check that the generator has the right order
assert pow(g, self.q, self.p) == 1
Expand All @@ -119,19 +134,14 @@ def bytes_to_scalar(self, b):
return i

def password_to_scalar(self, pw):
assert isinstance(pw, bytes)
# the oversized hash reduces bias in the result, so
# uniformly-random passwords give nearly-uniform scalars
oversized = hashlib.sha512(pw).digest()
assert len(oversized) >= self.scalar_size_bytes
i = bytes_to_number(oversized)
return i % self.q
return password_to_scalar(pw, self.scalar_size_bytes, self.q)


def arbitrary_element(self, seed):
# we do *not* know the discrete log of this one. Nobody should.
assert isinstance(seed, bytes)
processed_seed = self.element_hasher(seed)[:self.element_size_bytes]
processed_seed = expand_arbitrary_element_seed(seed,
self.element_size_bytes)
assert isinstance(processed_seed, bytes)
assert len(processed_seed) == self.element_size_bytes
# The larger (non-prime-order) group (Zp*) we're using has order
Expand Down Expand Up @@ -192,21 +202,6 @@ def _add(self, e1, e2):
return _Element(self, (e1._e * e2._e) % self.p)


def sha256(b):
return hashlib.sha256(b).digest()
def sha512(b):
return hashlib.sha512(b).digest()
def hash1024(b):
return b"".join([sha512(b"0:"+b), sha512(b"1:"+b)])
def hash2048(b):
return b"".join([sha512(b"0:"+b), sha512(b"1:"+b),
sha512(b"2:"+b), sha512(b"3:"+b)])
def hash3072(b):
return b"".join([sha512(b"0:"+b), sha512(b"1:"+b),
sha512(b"2:"+b), sha512(b"3:"+b),
sha512(b"4:"+b), sha512(b"5:"+b)])


# This 1024-bit group originally came from the J-PAKE demo code,
# http://haofeng66.googlepages.com/JPAKEDemo.java . That java code
# recommended these 2048 and 3072 bit groups from this NIST document:
Expand All @@ -217,18 +212,18 @@ def hash3072(b):
p=0xE0A67598CD1B763BC98C8ABB333E5DDA0CD3AA0E5E1FB5BA8A7B4EABC10BA338FAE06DD4B90FDA70D7CF0CB0C638BE3341BEC0AF8A7330A3307DED2299A0EE606DF035177A239C34A912C202AA5F83B9C4A7CF0235B5316BFC6EFB9A248411258B30B839AF172440F32563056CB67A861158DDD90E6A894C72A5BBEF9E286C6B,
q=0xE950511EAB424B9A19A2AEB4E159B7844C589C4F,
g=0xD29D5121B0423C2769AB21843E5A3240FF19CACC792264E3BB6BE4F78EDD1B15C4DFF7F1D905431F0AB16790E1F773B5CE01C804E509066A9919F5195F4ABC58189FD9FF987389CB5BEDF21B4DAB4F8B76A055FFE2770988FE2EC2DE11AD92219F0B351869AC24DA3D7BA87011A701CE8EE7BFE49486ED4527B7186CA4610A75,
element_hasher = hash1024)
)

# L=2048, N=224
I2048 = IntegerGroup(
p=0xC196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE428782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BFFAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83,
q=0x90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D,
g=0xA59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D65444FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E5048B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDFD049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D73980B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085,
element_hasher=hash2048)
)

# L=3072, N=256
I3072 = IntegerGroup(
p=0x90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA129F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D15939487E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773EBE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941DAD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504FB0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328EC22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73,
q=0xCFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D,
g=0x5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B39AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B,
element_hasher=hash3072)
)
166 changes: 166 additions & 0 deletions src/spake2/test/myhkdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from hashlib import sha256, sha1
from binascii import unhexlify
import hmac

def HKDF(IKM, dkLen, salt=None, info=b"", digest=sha256,
_test_expected_PRK=None):
assert isinstance(IKM, bytes)
assert isinstance(salt, (bytes,type(None)))
assert isinstance(info, bytes)
hlen = len(digest(b"").digest())
assert dkLen <= hlen*255
if salt is None:
salt = b"\x00"*hlen
# extract
PRK = hmac.new(salt, IKM, digest).digest()
if _test_expected_PRK and _test_expected_PRK != PRK:
raise ValueError("test failed")
# expand
blocks = []
counter = 1
t = b""
while hlen*len(blocks) < dkLen:
t = hmac.new(PRK, t+info+unhexlify("%02x"%counter), digest).digest()
blocks.append(t)
counter += 1
return b"".join(blocks)[:dkLen]

def power_on_self_test():
from binascii import hexlify, unhexlify

def _test(IKM, salt, info, L, PRK, OKM, digest=sha256):
def remove_prefix(prefix, s):
assert s.startswith(prefix)
return s[len(prefix):]
ikm = unhexlify(remove_prefix("0x", IKM))
salt = unhexlify(remove_prefix("0x", salt))
info = unhexlify(remove_prefix("0x", info))
prk = unhexlify(remove_prefix("0x", PRK))
okm = unhexlify(remove_prefix("0x", OKM))
assert isinstance(ikm, bytes)
assert isinstance(salt, bytes)
assert isinstance(info, bytes)
assert isinstance(prk, bytes)
assert isinstance(okm, bytes)
if digest is None:
out = HKDF(ikm, L, salt, info, _test_expected_PRK=prk)
else:
out = HKDF(ikm, L, salt, info, digest=digest,
_test_expected_PRK=prk)
if okm != out:
raise ValueError("got %s, expected %s" % (hexlify(out), hexlify(okm)))

# test vectors from RFC5869
_test(IKM="0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
salt="0x000102030405060708090a0b0c",
info="0xf0f1f2f3f4f5f6f7f8f9",
L=42,
PRK=("0x077709362c2e32df0ddc3f0dc47bba63"
"90b6c73bb50f9c3122ec844ad7c2b3e5"),
OKM=("0x3cb25f25faacd57a90434f64d0362f2a"
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
"34007208d5b887185865"))

_test(IKM=("0x000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"),
salt=("0x606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"),
info=("0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"),
L=82,
PRK=("0x06a6b88c5853361a06104c9ceb35b45c"
"ef760014904671014a193f40c15fc244"),
OKM=("0xb11e398dc80327a1c8e7f78c596a4934"
"4f012eda2d4efad8a050cc4c19afa97c"
"59045a99cac7827271cb41c65e590e09"
"da3275600c2f09b8367793a9aca3db71"
"cc30c58179ec3e87c14c01d5c1f3434f"
"1d87"))

_test(IKM="0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
salt="0x",
info="0x",
L=42,
PRK=("0x19ef24a32c717b167f33a91d6f648bdf"
"96596776afdb6377ac434c1c293ccb04"),
OKM=("0x8da4e775a563c18f715f802a063c5a31"
"b8a11f5c5ee1879ec3454e5f3c738d2d"
"9d201395faa4b61a96c8"))

_test(digest=sha1,
IKM="0x0b0b0b0b0b0b0b0b0b0b0b",
salt="0x000102030405060708090a0b0c",
info="0xf0f1f2f3f4f5f6f7f8f9",
L=42,
PRK="0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243",
OKM=("0x085a01ea1b10f36933068b56efa5ad81"
"a4f14b822f5b091568a9cdd4f155fda2"
"c22e422478d305f3f896"))
_test(digest=sha1,
IKM=("0x000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"),
salt=("0x606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"),
info=("0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"),
L=82,
PRK="0x8adae09a2a307059478d309b26c4115a224cfaf6",
OKM=("0x0bd770a74d1160f7c9f12cd5912a06eb"
"ff6adcae899d92191fe4305673ba2ffe"
"8fa3f1a4e5ad79f3f334b3b202b2173c"
"486ea37ce3d397ed034c7f9dfeb15c5e"
"927336d0441f4c4300e2cff0d0900b52"
"d3b4"))
_test(digest=sha1,
IKM="0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
salt="0x",
info="0x",
L=42,
PRK="0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01",
OKM=("0x0ac1af7002b3d761d1e55298da9d0506"
"b9ae52057220a306e07b6b87e8df21d0"
"ea00033de03984d34918"))

_test(digest=sha1,
IKM="0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
salt="0x",
info="0x",
L=42,
PRK="0x2adccada18779e7c2077ad2eb19d3f3e731385dd",
OKM=("0x2c91117204d745f3500d636a62f64f0a"
"b3bae548aa53d423b0d1f27ebba6f5e5"
"673a081d70cce7acfc48"))

# finally test that HKDF() without a digest= uses SHA256

_test(digest=None,
IKM="0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
salt="0x",
info="0x",
L=42,
PRK=("0x19ef24a32c717b167f33a91d6f648bdf"
"96596776afdb6377ac434c1c293ccb04"),
OKM=("0x8da4e775a563c18f715f802a063c5a31"
"b8a11f5c5ee1879ec3454e5f3c738d2d"
"9d201395faa4b61a96c8"))
#print "all test passed"

power_on_self_test()
Loading