nacl/box: support anonymous seal/open
This adds SealAnonymous and OpenAnonymous functions that implement the libsodium "sealed box" functionality. Fixes golang/go#35346 Change-Id: I22455f1b83595ec8a68d1861e635bd6cb0573f44 GitHub-Last-Rev: 7d334cf861942ec63ad613b7f28fb6dd7a1f9992 GitHub-Pull-Request: golang/crypto#107 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/205241 Reviewed-by: Filippo Valsorda <filippo@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
committed by
Filippo Valsorda
parent
e9b2fee464
commit
becbf705a9
+81
-2
@@ -31,19 +31,30 @@ Thus large amounts of data should be chunked so that each message is small.
|
||||
chunk size.
|
||||
|
||||
This package is interoperable with NaCl: https://nacl.cr.yp.to/box.html.
|
||||
Anonymous sealing/opening is an extension of NaCl defined by and interoperable
|
||||
with libsodium:
|
||||
https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes.
|
||||
*/
|
||||
package box // import "golang.org/x/crypto/nacl/box"
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"golang.org/x/crypto/salsa20/salsa"
|
||||
)
|
||||
|
||||
// Overhead is the number of bytes of overhead when boxing a message.
|
||||
const Overhead = secretbox.Overhead
|
||||
const (
|
||||
// Overhead is the number of bytes of overhead when boxing a message.
|
||||
Overhead = secretbox.Overhead
|
||||
|
||||
// AnonymousOverhead is the number of bytes of overhead when using anonymous
|
||||
// sealed boxes.
|
||||
AnonymousOverhead = Overhead + 32
|
||||
)
|
||||
|
||||
// GenerateKey generates a new public/private key pair suitable for use with
|
||||
// Seal and Open.
|
||||
@@ -101,3 +112,71 @@ func Open(out, box []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte
|
||||
func OpenAfterPrecomputation(out, box []byte, nonce *[24]byte, sharedKey *[32]byte) ([]byte, bool) {
|
||||
return secretbox.Open(out, box, nonce, sharedKey)
|
||||
}
|
||||
|
||||
// SealAnonymous appends an encrypted and authenticated copy of message to out,
|
||||
// which will be AnonymousOverhead bytes longer than the original and must not
|
||||
// overlap it. This differs from Seal in that the sender is not required to
|
||||
// provide a private key.
|
||||
func SealAnonymous(out, message []byte, recipient *[32]byte, rand io.Reader) ([]byte, error) {
|
||||
if rand == nil {
|
||||
rand = cryptorand.Reader
|
||||
}
|
||||
ephemeralPub, ephemeralPriv, err := GenerateKey(rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nonce [24]byte
|
||||
if err := sealNonce(ephemeralPub, recipient, &nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if total := len(out) + AnonymousOverhead + len(message); cap(out) < total {
|
||||
original := out
|
||||
out = make([]byte, 0, total)
|
||||
out = append(out, original...)
|
||||
}
|
||||
out = append(out, ephemeralPub[:]...)
|
||||
|
||||
return Seal(out, message, &nonce, recipient, ephemeralPriv), nil
|
||||
}
|
||||
|
||||
// OpenAnonymous authenticates and decrypts a box produced by SealAnonymous and
|
||||
// appends the message to out, which must not overlap box. The output will be
|
||||
// AnonymousOverhead bytes smaller than box.
|
||||
func OpenAnonymous(out, box []byte, publicKey, privateKey *[32]byte) (message []byte, ok bool) {
|
||||
if len(box) < AnonymousOverhead {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var ephemeralPub [32]byte
|
||||
copy(ephemeralPub[:], box[:32])
|
||||
|
||||
var nonce [24]byte
|
||||
if err := sealNonce(&ephemeralPub, publicKey, &nonce); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return Open(out, box[32:], &nonce, &ephemeralPub, privateKey)
|
||||
}
|
||||
|
||||
// sealNonce generates a 24 byte nonce that is a blake2b digest of the
|
||||
// ephemeral public key and the receiver's public key.
|
||||
func sealNonce(ephemeralPub, peersPublicKey *[32]byte, nonce *[24]byte) error {
|
||||
h, err := blake2b.New(24, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = h.Write(ephemeralPub[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = h.Write(peersPublicKey[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Sum(nonce[:0])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -76,3 +76,106 @@ func TestBox(t *testing.T) {
|
||||
t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealOpenAnonymous(t *testing.T) {
|
||||
publicKey, privateKey, _ := GenerateKey(rand.Reader)
|
||||
message := []byte("test message")
|
||||
|
||||
box, err := SealAnonymous(nil, message, publicKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error sealing %v", err)
|
||||
}
|
||||
opened, ok := OpenAnonymous(nil, box, publicKey, privateKey)
|
||||
if !ok {
|
||||
t.Fatalf("failed to open box")
|
||||
}
|
||||
|
||||
if !bytes.Equal(opened, message) {
|
||||
t.Fatalf("got %x, want %x", opened, message)
|
||||
}
|
||||
|
||||
for i := range box {
|
||||
box[i] ^= 0x40
|
||||
_, ok := OpenAnonymous(nil, box, publicKey, privateKey)
|
||||
if ok {
|
||||
t.Fatalf("opened box with byte %d corrupted", i)
|
||||
}
|
||||
box[i] ^= 0x40
|
||||
}
|
||||
|
||||
// allocates new slice if out isn't long enough
|
||||
out := []byte("hello")
|
||||
orig := append([]byte(nil), out...)
|
||||
box, err = SealAnonymous(out, message, publicKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error sealing %v", err)
|
||||
}
|
||||
if !bytes.Equal(out, orig) {
|
||||
t.Fatal("expected out to be unchanged")
|
||||
}
|
||||
if !bytes.HasPrefix(box, orig) {
|
||||
t.Fatal("expected out to be coppied to returned slice")
|
||||
}
|
||||
_, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
|
||||
if !ok {
|
||||
t.Fatalf("failed to open box")
|
||||
}
|
||||
|
||||
// uses provided slice if it's long enough
|
||||
out = append(make([]byte, 0, 1000), []byte("hello")...)
|
||||
orig = append([]byte(nil), out...)
|
||||
box, err = SealAnonymous(out, message, publicKey, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error sealing %v", err)
|
||||
}
|
||||
if !bytes.Equal(out, orig) {
|
||||
t.Fatal("expected out to be unchanged")
|
||||
}
|
||||
if &out[0] != &box[0] {
|
||||
t.Fatal("expected box to point to out")
|
||||
}
|
||||
_, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
|
||||
if !ok {
|
||||
t.Fatalf("failed to open box")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSealedBox(t *testing.T) {
|
||||
var privateKey [32]byte
|
||||
for i := range privateKey[:] {
|
||||
privateKey[i] = 1
|
||||
}
|
||||
|
||||
var publicKey [32]byte
|
||||
curve25519.ScalarBaseMult(&publicKey, &privateKey)
|
||||
var message [64]byte
|
||||
for i := range message[:] {
|
||||
message[i] = 3
|
||||
}
|
||||
|
||||
fakeRand := bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5})
|
||||
box, err := SealAnonymous(nil, message[:], &publicKey, fakeRand)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error sealing %v", err)
|
||||
}
|
||||
|
||||
// expected was generated using the C implementation of libsodium with a
|
||||
// random implementation that always returns 5.
|
||||
// https://gist.github.com/mastahyeti/942ec3f175448d68fed25018adbce5a7
|
||||
expected, _ := hex.DecodeString("50a61409b1ddd0325e9b16b700e719e9772c07000b1bd7786e907c653d20495d2af1697137a53b1b1dfc9befc49b6eeb38f86be720e155eb2be61976d2efb34d67ecd44a6ad634625eb9c288bfc883431a84ab0f5557dfe673aa6f74c19f033e648a947358cfcc606397fa1747d5219a")
|
||||
|
||||
if !bytes.Equal(box, expected) {
|
||||
t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
|
||||
}
|
||||
|
||||
// box was generated using the C implementation of libsodium.
|
||||
// https://gist.github.com/mastahyeti/942ec3f175448d68fed25018adbce5a7
|
||||
box, _ = hex.DecodeString("3462e0640728247a6f581e3812850d6edc3dcad1ea5d8184c072f62fb65cb357e27ffa8b76f41656bc66a0882c4d359568410665746d27462a700f01e314f382edd7aae9064879b0f8ba7b88866f88f5e4fbd7649c850541877f9f33ebd25d46d9cbcce09b69a9ba07f0eb1d105d4264")
|
||||
result, ok := OpenAnonymous(nil, box, &publicKey, &privateKey)
|
||||
if !ok {
|
||||
t.Fatalf("failed to open box")
|
||||
}
|
||||
if !bytes.Equal(result, message[:]) {
|
||||
t.Fatalf("message didn't match, got\n%x\n, expected\n%x", result, message[:])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user