Navigation Menu

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

Proposal: crypto/elliptic support x25519 #29128

Closed
MetalBreaker opened this issue Dec 6, 2018 · 13 comments
Closed

Proposal: crypto/elliptic support x25519 #29128

MetalBreaker opened this issue Dec 6, 2018 · 13 comments

Comments

@MetalBreaker
Copy link

MetalBreaker commented Dec 6, 2018

Currently, there's no way to use the x25519 curve in Golang outside of TLS.
I believe implementing it in crypto/elliptic would help a lot of people using the curve for non-TLS purposes (i.e. with an ECIES library), including me.

Thanks in advance.

@gopherbot gopherbot added this to the Proposal milestone Dec 6, 2018
@bcmills
Copy link
Contributor

bcmills commented Dec 6, 2018

CC @FiloSottile @agl

@agl
Copy link
Contributor

agl commented Dec 7, 2018

https://godoc.org/golang.org/x/crypto/curve25519 is intended to handle this. Does that work for you?

@MetalBreaker
Copy link
Author

MetalBreaker commented Dec 7, 2018 via email

@agl
Copy link
Contributor

agl commented Dec 7, 2018

I don't think this is a proposal so much as a question then. (An elliptic.Curve for X25519 doesn't really make sense because it's implemented as a Montgomery ladder, so Add and Double don't fit. You can work on Ed25519 in that fashion, but then you would need to to the isogeny mapping.)

Basically, ECIES just means Diffie–Hellman with an AEAD. So, to do that with X25519 you:

  1. Generate a random private key.
  2. Generate the public key from the private key with ScalarBaseMult.
  3. To encrypt to another public key, calculate the shared key by hashing the ScalarMult of your private key and their public key, and use that to key an AEAD like AES-GCM. If you always generate a fresh private key then you can set the AEAD nonce to zero. Send your public key and the AEAD ciphertext.
  4. To decrypt a message, calculate the ScalarMult of your private key with their public key and open the AEAD ciphertext.

But really you want to avoid having to worry about the above. https://godoc.org/golang.org/x/crypto/nacl/box handles this for you in a standard format that interoperates with NaCl, libsodium, and several other libraries.

@MetalBreaker
Copy link
Author

MetalBreaker commented Dec 7, 2018 via email

@agl
Copy link
Contributor

agl commented Dec 8, 2018

I just want to confirm that I can use NaCl crypto box for hybrid encryption, as I need to encrypt potentially very long messages.

See the package comment about long messages. Roughly: don't have long messages. Rather, put a random key and a total length in the box. Then chunk the message into ~16KiB chunks and use secretbox on each one with an incrementing nonce. When decrypting, check that you got as many bytes as expected to prevent truncation at a chunk boundary.

@MetalBreaker
Copy link
Author

MetalBreaker commented Dec 8, 2018

@agl
Sorry, I'm still quite new to this;
I thought that for asymmetric key encryption, you only needed a public key, and for asymmetric key decryption, you only needed a private key.
However, I need to pass a private and public key to both the Open and Seal functions. Is that intended? (some research shows it is indeed intended) Doesn't libsodium's cryptobox seal implementation only require a public key? https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes

To be clear, I want to do something like this:
Step 1. User sends sensitive data to a server, server encrypts it with the public key and stores it in the database.
Step 2. When a person authorized to view that data wants to access it, the private key is fetched from secure storage and the data sent by the user is decrypted.

I cannot currently do this, as the app doesn't know the private key in step 1, and nacl/box wants it for encryption.
Go-ethereum seems to be able to do it (seems to be using a shared key behind the scenes as well), but I can't quite understand the code: https://github.com/ethereum/go-ethereum/blob/master/crypto/ecies/ecies.go

UPDATE: Okay, I seem to have found a solution, but please tell me if this is insecure/your general opinion on this solution.

var dummyPrivateKey [32]byte
var dummyPublicKey [32]byte
// Derive public key from empty (zero) private key
curve25519.ScalarBaseMult(&dummyPublicKey, &dummyPrivateKey)

...

encrypted := box.Seal(..., ..., ..., realPublicKey, &dummyPrivateKey)
decrypted, _ := box.Open(..., ..., ..., &dummyPublicKey, realPrivateKey)

Thanks in advance.

@agl
Copy link
Contributor

agl commented Dec 12, 2018

X25519 is still a good way to do what you want, but it's a Diffie–Hellman scheme, not a public-key encryption scheme or a KEM. (Some people find this paint analogy to be useful.)

A DH scheme provides a function the generates a shared key between a private key and a public key. Therefore to make a public-key encryption scheme one generally generates a random, ephemeral private key for each encryption operation, encrypts the data with the resulting shared key, and stores the ephemeral public key along with the data.

The documentation for libsodium's “sealed boxes” says that is what it's doing under the covers. (libsodium's version of x/crypto's box API is here).

In your update you suggest using a zero private-key for all operations. But zero isn't very random so it would be trivial for anyone to decrypt that. I would suggest mirroring what libsodium does here.

@MetalBreaker
Copy link
Author

Ah! I missed that. Thank you so much!
I've actually managed to get it to work thanks to your help!
(In case anyone's interested:

Click to expand )

func Encrypt(pubKey *[32]byte, msg []byte) []byte {
	var nonce [24]byte
	epk, esk, _ := box.GenerateKey(rand.Reader)
	nonceWriter, _ := blake2b.New(24, nil)
	nonceSlice := nonceWriter.Sum(append(epk[:], pubKey[:]...))
	copy(nonce[:], nonceSlice)

	return box.Seal(epk[:], msg, &nonce, pubKey, esk)
}

func Decrypt(privKey, pubKey *[32]byte, encrypted []byte) []byte {
	var epk [32]byte
	var nonce [24]byte
	copy(epk[:], encrypted[:32])

	nonceWriter, _ := blake2b.New(24, nil)
	nonceSlice := nonceWriter.Sum(append(epk[:], pubKey[:]...))
	copy(nonce[:], nonceSlice)

	decrypted, ok := box.Open(nil, encrypted[32:], &nonce, &epk, privKey)
	if !ok {
		panic("Decryption error.")
	}
	return decrypted
}

Should I submit a pull request that implements this functionality?

@baha-ai
Copy link

baha-ai commented Aug 22, 2019

@MetalBreaker thanks for the code snippet above,

I was trying to find the equivalent of blake2b in go and was surprised it was actually part or the crypto library. It's good to know since libsodium's crypto_box_seal() takes no nonce as argument but its internal implementation uses black2b() to create the nonce.

Your above example shows how the nonce is created.

@MetalBreaker
Copy link
Author

MetalBreaker commented Aug 22, 2019

@Baha-sk Haha, this was a long time ago. I remember spending quite some time deciphering the documentation and original C (or C++? Can't remember, doesn't matter) libsodium code and writing a Go equivalent, so I figured it'd be nice to share that code snippet.

@baha-ai
Copy link

baha-ai commented Aug 22, 2019

@MetalBreaker much appreciated!!!

You definitely saved me the time of researching this nonce creation :)

yeah, libsodium is C code

Thanks so much!

@MetalBreaker
Copy link
Author

MetalBreaker commented Aug 22, 2019 via email

@golang golang locked and limited conversation to collaborators Aug 21, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants