Skip to content

Commit

Permalink
chacha20poly1305: new package.
Browse files Browse the repository at this point in the history
This change adds a package, chacha20poly1305, which implements the
ChaCha20-Poly1305 AEAD from RFC 7539. This AEAD has several attractive
features:
   1. It's naturally constant time. AES-GCM needs either dedicated
      hardware or extreme effort to be fast and constant-time, while
      this design is easy to make constant-time.
   2. It's fast on modern processors: it runs at 1GB/s on my IvyBrige
      system.
   3. It's seeing significant use in TLS. (A change for crypto/tls is
      forthcoming.)

This change merges two CLs:
  https://go-review.googlesource.com/#/c/24717
  https://go-review.googlesource.com/#/c/26691

I took the amd64-optimised AEAD implementation from the former because
it was significantly faster. But the structure of the change is taken
from the latter.

This version will be checked into x/crypto. This package will then be
vendored into the stdlib so that it can be used from crypto/tls.

Change-Id: I5a60587958b7afeec81ca1091e603a7e8517000b
Reviewed-on: https://go-review.googlesource.com/30728
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
  • Loading branch information
agl committed Oct 11, 2016
1 parent a81735b commit 594708b
Show file tree
Hide file tree
Showing 9 changed files with 3,682 additions and 0 deletions.
83 changes: 83 additions & 0 deletions chacha20poly1305/chacha20poly1305.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD as specified in RFC 7539.
package chacha20poly1305

import (
"crypto/cipher"
"errors"
)

const (
// KeySize is the size of the key used by this AEAD, in bytes.
KeySize = 32
// NonceSize is the size of the nonce used with this AEAD, in bytes.
NonceSize = 12
)

type chacha20poly1305 struct {
key [32]byte
}

// New returns a ChaCha20-Poly1305 AEAD that uses the given, 256-bit key.
func New(key []byte) (cipher.AEAD, error) {
if len(key) != KeySize {
return nil, errors.New("chacha20poly1305: bad key length")
}
ret := new(chacha20poly1305)
copy(ret.key[:], key)
return ret, nil
}

func (c *chacha20poly1305) NonceSize() int {
return NonceSize
}

func (c *chacha20poly1305) Overhead() int {
return 16
}

func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != NonceSize {
panic("chacha20poly1305: bad nonce length passed to Seal")
}

if uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}

return c.seal(dst, nonce, plaintext, additionalData)
}

var errOpen = errors.New("chacha20poly1305: message authentication failed")

func (c *chacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != NonceSize {
panic("chacha20poly1305: bad nonce length passed to Open")
}
if len(ciphertext) < 16 {
return nil, errOpen
}
if uint64(len(ciphertext)) > (1<<38)-48 {
panic("chacha20poly1305: ciphertext too large")
}

return c.open(dst, nonce, ciphertext, additionalData)
}

// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
80 changes: 80 additions & 0 deletions chacha20poly1305/chacha20poly1305_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build amd64

package chacha20poly1305

import "encoding/binary"

//go:noescape
func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool

//go:noescape
func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)

//go:noescape
func haveSSSE3() bool

var canUseASM bool

func init() {
canUseASM = haveSSSE3()
}

// setupState writes a ChaCha20 input matrix to state. See
// https://tools.ietf.org/html/rfc7539#section-2.3.
func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
state[0] = 0x61707865
state[1] = 0x3320646e
state[2] = 0x79622d32
state[3] = 0x6b206574

state[4] = binary.LittleEndian.Uint32(key[:4])
state[5] = binary.LittleEndian.Uint32(key[4:8])
state[6] = binary.LittleEndian.Uint32(key[8:12])
state[7] = binary.LittleEndian.Uint32(key[12:16])
state[8] = binary.LittleEndian.Uint32(key[16:20])
state[9] = binary.LittleEndian.Uint32(key[20:24])
state[10] = binary.LittleEndian.Uint32(key[24:28])
state[11] = binary.LittleEndian.Uint32(key[28:32])

state[12] = 0
state[13] = binary.LittleEndian.Uint32(nonce[:4])
state[14] = binary.LittleEndian.Uint32(nonce[4:8])
state[15] = binary.LittleEndian.Uint32(nonce[8:12])
}

func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
if !canUseASM {
return c.sealGeneric(dst, nonce, plaintext, additionalData)
}

var state [16]uint32
setupState(&state, &c.key, nonce)

ret, out := sliceForAppend(dst, len(plaintext)+16)
chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
return ret
}

func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if !canUseASM {
return c.openGeneric(dst, nonce, ciphertext, additionalData)
}

var state [16]uint32
setupState(&state, &c.key, nonce)

ciphertext = ciphertext[:len(ciphertext)-16]
ret, out := sliceForAppend(dst, len(ciphertext))
if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
for i := range out {
out[i] = 0
}
return nil, errOpen
}

return ret, nil
}
Loading

6 comments on commit 594708b

@grittygrease
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the commit history of this code was lost, shouldn't the asm be attributed to @vkrasnov?

@agl
Copy link
Contributor Author

@agl agl commented on 594708b Oct 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message says:

This change merges two CLs:
  https://go-review.googlesource.com/#/c/24717
  https://go-review.googlesource.com/#/c/26691

and it certainly wasn't the intention not to credit anyone. If you would like a more explicit notice in the assembly file I can certainly do that.

@grittygrease
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No big deal, these things happen. An explicit notice in the assembly of the authorship (commit author or otherwise) would be nice.

@vkrasnov
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @agl, any mention is very much appreciated. I am just glad that you will be using this code. We have been using it for a while now.

I also have a similar implementation for arm64, but can't port it to goasm, due to lack of NEON instructions. Is there an ETA for NEON support?

@agl
Copy link
Contributor Author

@agl agl commented on 594708b Oct 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this change.

As for NEON in arm64, I'm not terribly familiar with Go on ARM but it appears that this is just something that hasn't been implemented yet.

(Although there would be a little more to it because ARM, unlike Intel, doesn't provide a CPUID instruction. Rather information about CPU support for instructions is provided in the aux vector, given by the kernel at process start. Thus some plumbing is probably required for that.)

@leonklingele
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome? Thanks @agl

Please sign in to comment.