-
Notifications
You must be signed in to change notification settings - Fork 988
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
Added SPAKE2, SPAKE2-EE, PAKE2+, and PAKE2+EE #273
Conversation
If anyone wants to run some tests, this is how you're suppose to use it. SPAKE2/SPAKE2-EE
PAKE2+/PAKE2+EE
Optional
|
This is a great contribution, will definitely go through but might take a while. |
Usually if javadoc is available it is added to the documentation after merge (here: http://bitwiseshiftleft.github.io/sjcl/) |
You need to include sjcl.js first and random.js last. I think all the others can be in any order.
P.S. Thanks @ggozad while looking at the dependencies I noticed a bug in random.js #274 |
Oh I think read that wrong... I thought you wanted to know what files to include :). Eh it's probably useful for someone. |
Is there a test suite that should be included with the PR? |
Added tests but there are no official test vectors or really how to generate pwKey1, pwKey2, and pwKey3; N and M per curve; or elligator edition N and M. So I just test to see if it generates the same keys with the same shared keys and different keys if the shared keys are different. |
Thank you Steve. I could not find official test vectors either. Having a look at https://github.com/warner/python-spake2/blob/master/spake2/test_spake2.py seems like @warner does the same thing for the python library. Can you please confirm the two libraries generate the same keys? |
The things that should be looked at:
|
That uses Ed25519... oh wow that's flawed. They use the same password key for "pwKey1" and "pwKey2". Also they went with
vs what I did
I did this to avoid collisions with IDs (ie aId = "asdf:asdf" bId = "asdf" and aId = "asdf" bId = "asdf:asdf"). Also note that aMsg, bMsg, abP, and pwKey2 are fixed length binary data. |
Is there a way to verify general correctness by comparing with some other implementation? I will be reviewing more next week, have some code comments but will do in a batch. |
Nice stuff! It'd be great to have interoperability between our libraries. Some notes (for reference) about my python library (which @ggozad referenced):
BTW, I wanted an asymmetric form for applications that don't have a good way to figure out "who's on first", like my magic-wormhole application when the two participants come up with a code-phase in person, then paste them into their computers later. I initially had both sides run two SPAKE2 protocols in parallel (each side doing one as Alice and a second as Bob), and then combining the results. Mike convinced me that it was safe to use M==N and reduce the traffic/computation in half. If that turns out to not be safe, I'll switch to adding a roundtrip to my protocol, so the two sides can negotiation who is whom before starting a single SPAKE2 process. |
I like The "pwKey1" vs "pwKey2" issue is that with PAKE2+ the server has pwKey1 (or precalculated pwKey1N and pwKey1M), pwKey2, and pwKey3P. Also for PAKE2+, "pw" is suppose to be salted and stretched to slow down offline attacks when the DB gets dumped. I just assumed that the only difference between SPAKE2 and PAKE2+ is that extra value "pwKey3b*P". SPAKE2:
PAKE2+:
The way I generated pwKey1, pwKey2, and pwKey3:
This might make more sense to do:
|
Huh I can't find anywhere that you need to generate two keys for SPAKE2 and three keys for PAKE2+. So I guess I'm wrong. SPAKE2:
PAKE2+:
|
@warner Should I just use SHA512 instead of HKDF. To generate a scalar I did "HKDF(..., cyclical group byte size + 16 bytes) % cyclical group" and for simplified Shallue-Woestijne-Ulas I did "HKDF(..., field prime byte size + 16 bytes) % field prime". The only "issue" is for P521 since 2**512-1 is less than both the field prime and cyclical group. SPAKE2:
SPAKE2-EE:
PAKE2+:
PAKE2+EE:
|
@warner So I just realized you do compressed points. So "A" and "B" in the hash will be different. Also I noticed "# try for compatibility with Boneh's JS version" in your code. Did Dan Boneh write SPAKE2 in JS? |
Added option to compress points to sjcl.ecc.point.toBits(). Replaced pake.js's use of HKDF with SHA512. Replaced pake.js's pwKey1, pwKey2, and pwKey3 with pwScalar, pw, and pw2Scalar. Changed SPAKE2's key output to H(H(aId) || H(bId) || A || B || a*b*G || pw) Changed PAKE2+'s key output to H(H(aId) || H(bId) || A || B || a*b*G || pw || pw2Scalar*b*G) New internal password keys/points: SPAKE2: pw = sharedKey pwScalar = SHA512(pw) SPAKE2-EE: pw = sharedKey pw_M = elligator(SHA512("M" || pw)) pw_N = elligator(SHA512("N" || pw)) PAKE2+: pw = SHA512("1" || sharedKey) pwScalar = pw pw2 = SHA512("2" || sharedKey) PAKE2+EE: pw = SHA512("1" || sharedKey) pw_M = elligator(SHA512("M" || pw)) pw_N = elligator(SHA512("N" || pw)) pw2 = SHA512("2" || sharedKey)
This should match @warner's when he changes his to "H(H(aId) || H(bId) || A || B || abP || pw)" except there's no Ed25519 in SJCL and there's no NIST curves in his. |
Just verified this matches @warner's when he changes his to "H(H(aId) || H(bId) || A || B || a_b_P || pw)". I added Ed25519 to SJCL, but it's not in this pull request because I think that will lower the chances of this being merged. If you want to test it: http://pastebin.com/dR9xTUP0 I still need to implement "sjcl.ecc.tEdCurve.deterministicRandomPoint()". |
Get warner/python-spake2#3 and add the following to the end of spake2.py and run
Get the current pull request and http://pastebin.com/dR9xTUP0 (ecc.js). In the dev console do:
Swap messages with spake2.py and in the dev console do:
|
I landed the hash-the-IDs PR.. thanks for the patch! |
👍
Ah is
Besides the key stretching stuff 👍
👍 |
Is What about elligator edition? My guess is you'd suggest this: SPAKE2-EE
SPAKE2+EE
|
Yeah, I think stretching should happen before SPAKE2+ even gets started.. that'll compose better. No point in baking a particular stretching algorithm into the PAKE layer. Especially since people usually layer additional stretching on top of their stored values, over time (pbkdf2(pw) today, bcrypt(pbkdf2(pw)) tomorrow, scrypt(bcrypt(pbkdf2(pw))) next thursday).. That layering would get really messy if it had to incorporate SPAKE2+'s hash-to-element function. I'm mildly in favor of including idA/idB in the function that converts I like your idea of using Yeah, len=256 is bits. So:
And then the server stores Yeah, for SPAKE2, I hesitate to say it, but since we seem to be having such a good time with HKDF, I wonder if we should use it for the final transcript too? Like
I'm -0 on that, but I can imagine an argument for consistency. It takes us further away from the published algorithm, though. |
The server only needs to store
I vote for
|
I just remembered I never said anything about this. It's easy to have deterministic test vectors for SJCL and should be added:
I already made the changes for the following. Should I wait just in case we want to change the output to use HKDF instead of just a hash? (P.S. SPAKE2
SPAKE2-EE
SPAKE2+
SPAKE2+EE
|
* 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
This changes password_to_scalar() and arbitrary_element() to a new derivation function, using HKDF and matching the proposed functions in bitwiseshiftleft/sjcl#273 . Users of this library from after this commit will not interoperate with those who use the library from before this commit: they will get "WrongPasswordError" all the time. closes #5 (rebased the original commits)
This modifies the finalization function (which hashes the transcript and shared group element into the final key) to match the proposal in bitwiseshiftleft/sjcl#273 . As with the previous compatibility-breaking patches, applications which use this revision of the SPAKE2 library will not be able to communicate with those using the previous version.
Sorry I've been so out of touch on this one. I just landed the patches to change python-spake2's final hash function to match this ( I also recently landed a patch that changes the password-to-scalar function to match the above proposal ( https://github.com/warner/python-spake2/blob/master/src/spake2/test/test_compat.py has test vectors that should let us check interoperability. For the finalization hash, the following should hold true (where the output is a bytestring, displayed here in hex):
and the password-to-scalar for the Ed25519 group should have this (note that the scalar is an integer, but displayed here in hex):
I've also added backwards-compatibility tests of the scalar-to-bytes conversion function (e.g. ed25519 scalars are serialized little-endian, in my codebase, inside a function that suspends the whole SPAKE2 conversation for later resumption), the overall SPAKE2 operation (using a deterministic RNG), and the "arbitrary element" function (which turns a seed like "N" or "M" into an un-discrete-loggable group element). I'm eager to build some tests that confirm interoperability of the overall SPAKE2 operation. Feel free to copy my tests, but the "overall" test is pretty dependent upon the particular way I built the deterministic RNG (basically SHA256 "CTR mode") and the exact way in which the "pick a random scalar" function uses the RNG. It might be easier to change the test to let you inject a pre-determined scalar into the SPAKE2 object, and include that "secret" scalar as part of the test vector. It'd be nice to get compatibility between our implementations for that arbitrary-element function too, so it's obvious in all codebases that the values they use were generated safely (i.e. it'd be great if both said I plan to make a new release of python-spake2 in the next few days. I'm aiming for a 1.0 release by the end of the month, to support a https://github.com/warner/magic-wormhole 1.0 release in the same timeframe. |
I've made a release (python-spake2==0.7), and I'm presenting magic-wormhole at PyCon this week, so I'm mostly committed to forwards-compatibility from here on out. (I'm holding off on python-spake2==1.0 until I land SPAKE2+ support too, but the SPAKE2 support will need to be the same as what's in 0.7). I think the next step will be for one of us (maybe @Sc00bz , maybe me in a few weeks when I'm done with the conference) to add some tests to the SJCL branch that use the same vectors as the ones from python-spake2. |
Replaced pake.js's use of SHA512 with HKDF. Changed SPAKE2's key output to H(H(pw) || H(aId) || H(bId) || A || B || a*b*G) Changed SPAKE2+'s key output to H(HKDF(pw, H(aId) || H(bId), "SPAKE2+ pw0 bytes") || A || B || a*b*G || pw2Scalar*b*G) Fixed defaults for compressPoints and littleEndian
I check and it matches the python-spake2 0.7 version. M and N changed to:
Or you can grab the updated ecc-ed25519.js |
@warner I'm going to work on tests and generating M and N from HKDF(...). I noticed that info is "SPAKE2 arbitrary element" which is 24 bytes you should try for at most 22 bytes so that it fits in one block, but it doesn't really matter. |
I just came across this and wanted to point out draft-irtf-cfrg-spake2-03, which specifies things like a specific format of the transcript hash data (with eight-byte little endian length prefixes for each field). It's still an early draft, so you can submit feedback on the CFRG mailing list. |
FWIW, @exarkun and I are working on a Haskell implementation of SPAKE2 (ignoring PAKE2+ etc. for the time being). It seems there a bunch of things that sit above the mathematical protocol (e.g. which hash algorithm to use, how group elements get mapped to bytes & vice versa, etc.) that need to be properly defined for full interoperability. I haven't fully digested all the discussion in this PR, but it seems that @Sc00bz and @warner have gone some way to identifying these things & making decisions. I would be grateful if they could be listed out here in bullet point form! I guess this is what the draft-irtf-cfrg-spake2-03 (linked from #273) is about, but I notice that it has expired. In any case, I'll keep plugging away, will give this PR a thorough read, and do my best to document what I'm doing so reviewing interoperability gets easier. Should I update here, or is there somewhere else more appropriate? |
Attempting to summarise interoperability decisions above. Restricting to SPAKE2 for now.
Thus, before exchanging, two nodes need to agree on the following, out-of-band:
I think that's it, but I could be wrong. Open questionsThis section just collects my questions, most of which are embedded in the description above.
Am I missing anything? Could someone have a go at answering my open questions? If not, I'll get there eventually. |
Much input derived from a PR to implement this for Javascript: bitwiseshiftleft/sjcl#273
Answers to my own questions:
It is the size of the elements of the group, in bytes.
Feelings. Oversizing the password by a certain number of bytes means the resulting scalar is more uniformly distributed over the group
expand and extract together
Groups must implement their own means of turning elements into bytes.
Not necessarily, but it's a desirable property for being able to readily agree on protocol instantiations (if that's the correct term) between implementations.
It must vary group-by-group.
Still no idea, but |
No description provided.