Skip to content

Commit 6cde392

Browse files
committed
change scalar/element hashing
* split expandstring() into two functions: * expand_password (for password_to_scalar) * expand_arbitrary_element_seed (for arbitrary_element) * change HKDF context_info= for both * This should match the proposed SJCL changes, in bitwiseshiftleft/sjcl#273 * remove element_hasher= from Group constructor * change Ed25519 to use the same scheme
1 parent c8ee85b commit 6cde392

File tree

4 files changed

+33
-24
lines changed

4 files changed

+33
-24
lines changed

src/spake2/ed25519_basic.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import binascii, hashlib, itertools
2+
from .groups import expand_arbitrary_element_seed
23

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

268269

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

274278
# we try successive Y values until we find a valid point

src/spake2/ed25519_group.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from . import ed25519_basic
2+
from .groups import password_to_scalar
23

34
class _Ed25519Group:
45
def random_scalar(self, entropy_f):
@@ -8,7 +9,7 @@ def scalar_to_bytes(self, i):
89
def bytes_to_scalar(self, b):
910
return ed25519_basic.bytes_to_scalar(b)
1011
def password_to_scalar(self, pw):
11-
return ed25519_basic.password_to_scalar(pw)
12+
return password_to_scalar(pw, self.scalar_size_bytes, self.order())
1213
def arbitrary_element(self, seed):
1314
return ed25519_basic.arbitrary_element(seed)
1415
def bytes_to_element(self, b):

src/spake2/groups.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,24 @@
6262
"""
6363

6464

65-
def expandstring(kind, data, bits):
65+
def expand_password(data, num_bytes):
6666
h = Hkdf(salt=b"", input_key_material=data, hash=hashlib.sha256)
67-
info = b"spake2-group-expand-" + kind + b"-" + ("%d" % bits).encode("ascii")
68-
return h.expand(info, bits / 8)
67+
info = b"SPAKE2 pw"
68+
return h.expand(info, num_bytes)
69+
70+
def password_to_scalar(pw, scalar_size_bytes, q):
71+
assert isinstance(pw, bytes)
72+
# the oversized hash reduces bias in the result, so
73+
# uniformly-random passwords give nearly-uniform scalars
74+
oversized = expand_password(pw, scalar_size_bytes+16)
75+
assert len(oversized) >= scalar_size_bytes
76+
i = bytes_to_number(oversized)
77+
return i % q
78+
79+
def expand_arbitrary_element_seed(data, num_bytes):
80+
h = Hkdf(salt=b"", input_key_material=data, hash=hashlib.sha256)
81+
info = b"SPAKE2 arbitrary element"
82+
return h.expand(info, num_bytes)
6983

7084
class _Element:
7185
def __init__(self, group, e):
@@ -81,7 +95,7 @@ def to_bytes(self):
8195
return self._group._element_to_bytes(self)
8296

8397
class IntegerGroup:
84-
def __init__(self, p, q, g, element_hasher):
98+
def __init__(self, p, q, g):
8599
self.q = q # the subgroup order, used for scalars
86100
self.scalar_size_bytes = size_bytes(self.q)
87101
_s = self.scalar_to_bytes(self.password_to_scalar(b""))
@@ -94,10 +108,6 @@ def __init__(self, p, q, g, element_hasher):
94108
self.p = p # the field size
95109
self.element_size_bits = size_bits(self.p)
96110
self.element_size_bytes = size_bytes(self.p)
97-
_e = element_hasher(b"")
98-
assert isinstance(_e, bytes)
99-
assert len(_e) >= self.element_size_bytes
100-
self.element_hasher = element_hasher
101111

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

126136
def password_to_scalar(self, pw):
127-
assert isinstance(pw, bytes)
128-
# the oversized hash reduces bias in the result, so
129-
# uniformly-random passwords give nearly-uniform scalars
130-
oversized = expandstring(b"password", pw, 512)
131-
assert len(oversized) >= self.scalar_size_bytes
132-
i = bytes_to_number(oversized)
133-
return i % self.q
137+
return password_to_scalar(pw, self.scalar_size_bytes, self.q)
134138

135139

136140
def arbitrary_element(self, seed):
137141
# we do *not* know the discrete log of this one. Nobody should.
138142
assert isinstance(seed, bytes)
139-
processed_seed = self.element_hasher(seed)[:self.element_size_bytes]
143+
processed_seed = expand_arbitrary_element_seed(seed,
144+
self.element_size_bytes)
140145
assert isinstance(processed_seed, bytes)
141146
assert len(processed_seed) == self.element_size_bytes
142147
# The larger (non-prime-order) group (Zp*) we're using has order
@@ -207,18 +212,18 @@ def _add(self, e1, e2):
207212
p=0xE0A67598CD1B763BC98C8ABB333E5DDA0CD3AA0E5E1FB5BA8A7B4EABC10BA338FAE06DD4B90FDA70D7CF0CB0C638BE3341BEC0AF8A7330A3307DED2299A0EE606DF035177A239C34A912C202AA5F83B9C4A7CF0235B5316BFC6EFB9A248411258B30B839AF172440F32563056CB67A861158DDD90E6A894C72A5BBEF9E286C6B,
208213
q=0xE950511EAB424B9A19A2AEB4E159B7844C589C4F,
209214
g=0xD29D5121B0423C2769AB21843E5A3240FF19CACC792264E3BB6BE4F78EDD1B15C4DFF7F1D905431F0AB16790E1F773B5CE01C804E509066A9919F5195F4ABC58189FD9FF987389CB5BEDF21B4DAB4F8B76A055FFE2770988FE2EC2DE11AD92219F0B351869AC24DA3D7BA87011A701CE8EE7BFE49486ED4527B7186CA4610A75,
210-
element_hasher = lambda b: expandstring(b"element", b, 1024))
215+
)
211216

212217
# L=2048, N=224
213218
I2048 = IntegerGroup(
214219
p=0xC196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE428782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BFFAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83,
215220
q=0x90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D,
216221
g=0xA59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D65444FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E5048B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDFD049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D73980B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085,
217-
element_hasher = lambda b: expandstring(b"element", b, 2048))
222+
)
218223

219224
# L=3072, N=256
220225
I3072 = IntegerGroup(
221226
p=0x90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA129F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D15939487E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773EBE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941DAD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504FB0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328EC22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73,
222227
q=0xCFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D,
223228
g=0x5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B39AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B,
224-
element_hasher = lambda b: expandstring(b"element", b, 3072))
229+
)

src/spake2/test/test_group.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ def test_math_trivial(self):
136136
self.assertElementsEqual(e1.add(e2), e2.add(e1))
137137
self.assertElementsEqual(e2.add(e3), e1.add(e4))
138138

139-
I23 = groups.IntegerGroup(p=23, q=11, g=2,
140-
element_hasher=lambda b: groups.expandstring(b"element", b, 256))
139+
I23 = groups.IntegerGroup(p=23, q=11, g=2)
141140

142141
class Parameters(unittest.TestCase):
143142
def test_params(self):

0 commit comments

Comments
 (0)