Skip to content
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

crypto/ed25519: Implement Ed25519ph #31804

Open
titanous opened this issue May 2, 2019 · 11 comments
Open

crypto/ed25519: Implement Ed25519ph #31804

titanous opened this issue May 2, 2019 · 11 comments
Assignees
Milestone

Comments

@titanous
Copy link
Member

@titanous titanous commented May 2, 2019

The Ed25519ph variant specified in RFC 8032 allows signing/verifying a message that has already been hashed with SHA-512 without risking the collision-resistant properties of "PureEdDSA" when using the same keys for messages signed using both schemes.

This is useful in at least two scenarios:

  1. When the private key is isolated to another piece of hardware and passing the entire message to be signed is not possible, for example when using a HSM and signing messages larger than a few KB.
  2. When working with large messages that are too large to be reasonably buffered for the current one-shot API.

This variant can be implemented minimally using the existing crypto.Signer API plus an additional verification function, without encouraging unsafe use by providing easy access to an API that takes an io.Reader or io.Writer.

Due to the additional internal hash initialization, there is no way to implement this without forking the package or upstreaming an implementation patch.

I will send a CL with a proposed implementation.

Relevant: #31727

/cc @zx2c4 @FiloSottile

@titanous titanous added this to the Unreleased milestone May 2, 2019
@gopherbot

This comment has been minimized.

Copy link

@gopherbot gopherbot commented May 2, 2019

Change https://golang.org/cl/174941 mentions this issue: ed25519: Implement Ed25519ph

@x30n

This comment has been minimized.

Copy link

@x30n x30n commented Jul 22, 2019

+1

@Hades32

This comment has been minimized.

Copy link

@Hades32 Hades32 commented Jul 26, 2019

Thanks @titanous , this is just what we needed. Confirmed to behave as the reference implementation (libsodium). 👍

Not sure if this is still in time for 1.13... @FiloSottile

@FiloSottile FiloSottile modified the milestones: Unreleased, Go1.14 Jul 26, 2019
@FiloSottile FiloSottile changed the title x/crypto/ed25519: Implement Ed25519ph crypto/ed25519: Implement Ed25519ph Jul 26, 2019
@FiloSottile

This comment has been minimized.

Copy link
Member

@FiloSottile FiloSottile commented Jul 26, 2019

Too late for Go 1.13, targeting Go 1.14. (crypto/ed25519 is now in the standard library.)

@Yawning

This comment has been minimized.

Copy link

@Yawning Yawning commented Sep 19, 2019

While I understand that the crypto.Signer interface is fixed in stone and can't be changed, if this is going to happen, it would be nice if it supported the full Ed25519ph algorithm as described in the RFC.

As it stands right now, the proposed implementation does not support a domain separation context.

Since there already is a Sign method in the package, and the PR adds VerifyHashed, this could be done by adding SignHashed(privateKey PrivateKey, context, message []byte) []byte and changing the proposed VerifyHashed to take another byte slice.

@FiloSottile

This comment has been minimized.

Copy link
Member

@FiloSottile FiloSottile commented Oct 1, 2019

RFC 8032 defined context independently of pre-hashing, so to support the whole spec we'd also have to support pure Ed25519 with custom context.

I am really not a fan of extending the API surface of a standard library package that should be what we point users to for basic public key signatures. How about this alternative API, which is more extensible and still makes it very opt-in to select the variants?

// Options can be used with PrivateKey.Sign or VerifyWithOptions
// to select Ed25519 variants.
type Options struct {
    // Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
    Hash    crypto.Hash
    Context string
}

func (*Options) HashFunc() crypto.Hash

func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) bool
@Yawning

This comment has been minimized.

Copy link

@Yawning Yawning commented Oct 1, 2019

If this proposed API is accepted, then VerifyHashed as proposed in the PR will go away right? I would be in favor of this, since it seems like a cleaner way to support the functionality.

Nitpicking: Is there any particular reason why Context is a string over []byte? I understand they are fairly interchangeable (and string may be more const friendly). I personally would make it a []byte to make it clear that it is an arbitrary "octet string of at most 255 octets" (and include the size limit in a doc string comment).

@FiloSottile

This comment has been minimized.

Copy link
Member

@FiloSottile FiloSottile commented Oct 1, 2019

If this proposed API is accepted, then VerifyHashed as proposed in the PR will go away right? I would be in favor of this, since it seems like a cleaner way to support the functionality.

Yep.

Nitpicking: Is there any particular reason why Context is a string over []byte? I understand they are fairly interchangeable (and string may be more const friendly). I personally would make it a []byte to make it clear that it is an arbitrary "octet string of at most 255 octets" (and include the size limit in a doc string comment).

Mostly consistency, we've used string for contexts elsewhere, in some cases to make it a different type from the message itself (which is not relevant here). I personally think it fits better the nature of the value, because it's usually fixed or at least immutable, often human-readable, and as you say can be a const.

We should definitely document the max length, thank you.

@Yawning

This comment has been minimized.

Copy link

@Yawning Yawning commented Oct 3, 2019

I went and implemented this in a package I maintain for dayjob (because dayjob needs ph-with-context support), and have more feedback.

What should happen when Hash is crypto.Hash(0) and Context is ""?

  1. Ed25519ctx with a 0 octet context ("The context input SHOULD NOT be empty.").
  2. Ed25519pure

I went with option 2 as option 1 is somewhat nonsensical and recommended against, though I will happlily change the package to match what the runtime library does. If Context were a byte, this could be disambiguated by nil vs []byte{}, but it's not clear to me if that justifies the loss of consistency and const friendliness, just for the sake of completeness.

Minor: I used opts crypto.SignerOpts for VerifyWithOptions so that it is possible to pass crypto.SHA512 when the context is not required (following PublicKey.Sign). The type naming is somewhat unfortunate, but the ease of use for what I suspect is a common case probably wins out.

@FiloSottile

This comment has been minimized.

Copy link
Member

@FiloSottile FiloSottile commented Oct 3, 2019

What should happen when Hash is crypto.Hash(0) and Context is ""?

  1. Ed25519ctx with a 0 octet context ("The context input SHOULD NOT be empty.").
  2. Ed25519pure

I went with option 2 as option 1 is somewhat nonsensical and recommended against, though I will happlily change the package to match what the runtime library does. If Context were a byte, this could be disambiguated by nil vs []byte{}, but it's not clear to me if that justifies the loss of consistency and const friendliness, just for the sake of completeness.

Yeah, definitely pure if Context is "", the alternative is confusing and not something people should do anyway, but we should document it. I am also not a fan of semantic differences between nil and []byte{} in general.

Minor: I used opts crypto.SignerOpts for VerifyWithOptions so that it is possible to pass crypto.SHA512 when the context is not required (following PublicKey.Sign). The type naming is somewhat unfortunate, but the ease of use for what I suspect is a common case probably wins out.

I thought about that, but I think I'd rather use the concrete type for a few reasons:

  • you can't actually semantically use any other crypto.SignerOpts here, the reason Sign has it is to match an interface which needs the abstraction, VerifyWithOptions has no semantic justification
  • Ed25519ph is not something we need to encourage and make easy, so having to explicitly use Options seems fine
  • interface boxing still causes heap escapes, I think
@rsc rsc modified the milestones: Go1.14, Backlog Oct 9, 2019
@smasher164 smasher164 modified the milestones: Backlog, Go1.14 Oct 11, 2019
@Yawning Yawning mentioned this issue Oct 28, 2019
2 of 3 tasks complete
@odeke-em

This comment has been minimized.

Copy link
Member

@odeke-em odeke-em commented Nov 29, 2019

Thank you for mailing CL 174941 @titanous, unfortunately that didn't make it into the cut before the Go1.14 freeze, but please rebase from master and we'll hopefully get this in for Go1.15. Apologies for lack of eyes on it during Go1.14.

@odeke-em odeke-em modified the milestones: Go1.14, Backlog Nov 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.