-
Notifications
You must be signed in to change notification settings - Fork 7
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
README and test vectors should reflect new 32 byte hex key expectations from branca-js #37
Comments
I think these are all good points, and I agree these are useful to make clear. I, however, do not agree that this repository is the right place to do so. The changes listed are all related to a single implementation of this spec. A spec, that to my knowledge, does not, and IMO shold not, track changes in a specific implementation. That belongs to the changelog of that specific library. I see nowhere that the spec requires that secret key to be an ASCII string, which IIRC was the issue brought up with branca-js. Perhaps, I'm overlooking something? There are other implementations that accept raw byte-arrays, for secret keys, for instance. Perhaps, it's worth looking in to how the secret key is currently described in the spec instead? When you mention the test vectors should be modified, do you mean the ones in this repository or that of branca-js? |
Hi @brycx and thanks for the thoughtful comments. For those who may not be aware, I filed an issue and a PR was accepted to resolve what I saw as a serious security flaw in the implied and tested methods of providing security keys to Branca clients. This is not specific to a single implementation. I'll provide some additional info to support that assertion. The original PR and some of my rationale can be found here: Fundamentally, this comes down to the unsafe use of non-cryptographically random values for encryption keys. This flaw is inherent in the spec and the test vectors that the spec provides, and that all implementations are expected to adhere to. 32 character strings are not safe encryption keysA 32 character string has far less The main website, and the spec vectors, all make use of plain text "passwords" which are not suitable as encryption keys. Libsodium, the underlying encryption library for Branca is clear on what makes an acceptable key: tuupola/branca-js#12 (comment) It is never recommended in the cryptography community to use plain text strings as encryption keys. In cases where user memorability of the key might be a desirable feature the use of key stretching algorithms like PBKDF2 or Argon2 are required to derive a strong encryption key of at least 32 bytes. These are intentionally slow hashing algorithms, and by performing this "key spreading" they help prevent against brute force attacks. Branca libraries/spec that use passwords as keys (and possibly user memorable passwords?) are much more susceptible to brute force attacks. The spec is silent on how implementations should handle keys, but the vectors are notWhile the text of the spec itself seems to be mostly silent on how to handle encryption keys, the test vectors are not. To my knowledge the vectors require conformant libraries to be able to accept a 32 character string, which they pass directly to the encryption library as a key. Even if one passed in a hex key (even if output from a cryptographic secure random number generator), it could only be equivalent to a 16 byte random key. Since, to my knowledge, all of the library implementations follow this spec and its vectors, they are all flawed in this regard. Clarifying that libraries should require 32 byte hex strings (or 32 byte byte arrays) as keys is a breaking changeIf libraries do as they should, and require 32 byte keys, this represents a breaking change to the spec and potentially all conformant libraries. Elixir:32 character string keys. https://github.com/tuupola/branca-elixir/blob/master/lib/branca.ex#L14 Go:32 character string keys. https://github.com/hako/branca/blob/master/branca.go#L42 JS:32 Byte (64 char) hex string, or 32 Byte Buffer. Now fixed, but now also a breaking change with keys generated from older version of lib, and now incompatible with other implementations. New version was not published with SemVer major release bump. https://github.com/tuupola/branca-js PHP:32 Byte (64 char) hex string, or 32 Byte Array. I believe @tuupola has also just modified this lib to work the way that the JS lib works now. Python:Hybrid Seems to straddle the line. If bytes are passed as a key they will be used, if not a string conversion to bytes will be done. https://github.com/tuupola/pybranca/blob/master/branca.py#L42 The test cases for the Python lib only ever exercises the string password (test vector) case. https://github.com/tuupola/pybranca/blob/master/branca_test.py Rust:The same. Uses the provided 32 character string directly. https://github.com/return/branca/blob/master/src/lib.rs#L181 Summary
This is hard. I understand that. If standard practice had been adhered to in the creation of the spec, and in the later development of reference and community clients, this would have been a non issue. I also think that fixing it the right way is the right thing to do. Some inspiration might be taken from how Paseto, a project with similar goals, handles these issues. They support the current version of the spec, and the one prior. If a new version comes out, support for the oldest drops. This can also be a time to handle any other interoperability issues if they exist. Cheers. |
@grempe Thanks for taking the time to improve Branca. I do have some additional comments.
Indeed they are not. I don't believe the intention is to demonstrate otherwise either. This tells me that the website and examples, should use randomly generated keys, instead of strings, in order to correctly convey what sort of source we expect the keys to come from.
Yes, this is true. That is why I previously mentioned looking into how the secret key is described in the spec right now. E.g:
The part with "user provided" can have two meanings. I believe the user referred to here is the user of a Branca implementation. I see how this could be misinterpreted. Branca does not handle password-stretching. Indeed, if the source of the secret key is a user-password in form of something directly from a keyboard, then this key should be stretched. This is not within the scope of Branca to support though, so the user of a Branca library must ensure the key used is sufficiently strong. Nonetheless, it seems something worth mentioning in the spec.
The keys used in the test vectors in this repository do not require one to pass them as strings directly. Converting the key-strings to byte-arrays yield arrays of length 32 (unless it's a too-(short/long) key test).
If one did use hex keys to test, that person would have to convert the hex to byte-arrays first, and then pass it, unless the library provided a method to do so automatically. What if the the secret keys used in the test vectors were hex-encoded? Would that make it clearer, that one should be able to pass raw bytes? (I haven't looked at the other implementations listed, since I'm not very familiar with them)
I'm not sure what is meant here.
I think adding a section to the spec about what the key should be is a good idea. There we can clarify that an implementing library must at least accept raw bytes. Interoperability across ecosystems is important.
I can get behind this. As mentioned above, let examples be using CSPRNG and mention this in spec along with references to KDFs.
I don't agree here. They are currently conforming to the spec, arguably due to its vagueness of what the key should be. Libraries accepting raw bytes can produce tokens with string-only passwords too. Libraries generating tokens, without accepting raw bytes, are simply not able to re-produce tokens made by implementations that use e.g. CSPRNG generated-keys. I still think that, ultimately, the libraries only accepting strings are erroneous in this case. Though, again, the spec can be improved by defining the secret key clearer.
So convert the current keys in test vectors to hex-encoded?
Indeed PASETO has some versioning support defined. That aside, I don't see any documentation on PASETO about expectations of the secret key. The only difference with their test vectors, compared to Branca, is that they have a hard-coded hex string used as key, which is decoded, instead of a string. @tuupola, please correct me if I make any false assumptions. |
This spec is only about the token format. This spec is not about which data format implementation should use nor how people should implement the spec.
Spec has never said that password style secret keys should be used. Using the However like I have explained to you before ASCII secret key has never been a requirement of the spec. Also with the said implementations it has always been possible to use random byte keys.
Not true. Spec only says key should be 32 bytes. That said spec could better describe how generate a random byte key. #38 takes care of this already. Spec itself is not broken. This is a documentation issue.
Not true. The key just happens to be a string in the test vector file. This does not imply spec requires cleartext keys. To avoid confusion the test vector file should be changed to use Spec itself does not change. |
You mean there is a library which cannot consume token generated by another library. Which library combination? |
Nope, I just changed the code examples in README. Library itself was not changed. It was a documentation issue. |
Just tested and I am able consume tokens generated with JavaScript version with PHP version and vice versa. I do not know which implementation you found to be incompatible but could you open a bug report in the corresponding repository.
There is no need to do a major version bump when breaking BC before 1.0.0. |
Thanks @tuupola for engaging in the conversation. My only intention is to try to remedy a problem I see. I will take a moment to digest your comments and your commits. I did want to clarify two things though: KDFI don't mean to imply that Branca, in spec or implementations, should make mention of or encourage use of a KDF to stretch passwords into suitable key material. I would like to suggest that it be recommended that any key provided to Branca be the output of a suitable CSPRNG as 32 fully random bytes in hex or InteropOn the topic of interoperability. I have not done any testing of the interop between libraries and my code links were only meant to demonstrate how what was published in the spec/vectors heavily influenced the implementations. I did do some testing of what developers will experience when they upgrade from JS https://runkit.com/grempe/60d355d6a6c6ff001ac5ecdd# The key piece of knowledge is to know that you must convert your Buffer.from(keyString, 'utf8').toString('hex') I think the documented goal should be that a 32 byte hex string (derived from a CSPRNG), or the conversion of that string to the equivalent of a JS As an aside, I ran into this article this morning which is on topic. The author also ran into issues related to key management with Branca. Moving to this new method universally might help resolve these types of issues:
https://quanttype.net/posts/2020-10-04-branca-and-yak-shaving.html |
This makes little sense to me. Any other serialization is just as applicable, since the keys should be passed as raw bytes in the end. Also, still not in scope IMO. |
I think #37 and #39 do help to clarify. I am glad to see the Rust lib passing with the new vector keys.
I agree that getting the raw bytes of the key is what matters most. Requiring hex ser/deser was not my intent, only an approach that would perhaps be shown in the example docs since handling string serialized keys is obviously more convenient in most cases. If that is the case though, and the spec is agnostic about ser/deser, then perhaps the libraries should be encouraged in the spec to require acceptance of the platform specific method of passing in a byte array as the key (e.g. One idea would be to handle some of this in the spec in the form of a section related to e.g. (with a few extra just to provide an example) As a library implementer your library should follow suggested interface guidelines:
These types of guidelines (not requirements) make it much easier for library developers to remain conformant IMHO. Since the API for Branca is generally pretty simple (init, encode, decode) there would not be many of these suggestions. |
No, this is not known to me. The libraries listed are not vetted before being put there, and some have much more serious issues.
The spec now states the key is a sequence of 32 arbitrary bytes (was also the case before, though not as clearly stated). Which means, like before, to adhere to the spec, you'd have to be able to create tokens with any byte-sequence. Payload is already defined as arbitrary in the spec, so the same applies here as with the key. I just tried to write alternative guidelines (e.g without "MUST" which seems out-of-place in guidelines compared to requirements), but since everything is listed as arbitrary bytes, I'm having a hard time putting something together that doesn't seem obvious. To me it's obvious that you can't make assumptions on what these parameters contain, except for bytes. The problem seems to me that branca-js didn't accept raw bytes, when it should have. The rest of the guidelines you've listed are mostly on API design and usability, which I don't believe belong here. @grempe Do you have an example for where such guidelines, or similar ones, are used in other similar projects? |
This has nothing to do with the spec. It is an API change in the JavaScript implementation.
While hex is a convenient and probably most universal way to serialise it should not be a requirement. This spec is only about token format, not how to implement a library.
Yes, this is obvious and this is why the test vectors exist.
It they would it would be against the spec and the unit tests would fail.
Which should be done in the library documentation. More docs is always a good idea.
You said there are interoperability issues which is why I asked. |
Actually it did. You could pass the key either as an instance of a |
Apologies, that's my misunderstanding then. |
I have too been thinking about a secondary doc (not the spec itself) or like you said guidelines which would contain tidbits which do not belong to the directly to the spec. Stuff like the fact that Branca tokens are sortable by their creation date. Also comment about timestamp is not part of the spec, instead it is a guideline. Will think about this a bit. |
It did. But it was undocumented, untested, and this capability was not referenced in the docs. It was also not intuitive to figure out even if you read the code since you would be parsing a
I'd suggest this is an area of improvement for anywhere you link to external projects. If the lib doesn't demonstrate conformance with the spec it should probably be noted how it is, or is not, conformant. An example of this would be on the Paseto project. In their case they are indicating conformance with the various versions of the spec.
I think I have made any points I wanted to make. I look forward to seeing any additions or clarifications you may make. Cheers. |
The Javascript reference implementation has recently changed to enforce the use of more secure 32 byte hex keys, or a 32 byte Buffer. This change is to require the use of cryptographic keys of the proper length and construction as expected by the underlying encryption algorithm.
https://github.com/tuupola/branca-js
The spec should be modified to:
Other downstream implementations should also be encouraged to adopt the new spec to maintain cross library interoperability.
The text was updated successfully, but these errors were encountered: