Browse Source

Initial import.

Yawning Angel 3 years ago
commit
ddb8f42a3f
3 changed files with 424 additions and 0 deletions
  1. 122 0
      LICENSE
  2. 209 0
      aead.go
  3. 93 0
      aead_test.go

+ 122 - 0
LICENSE

@@ -0,0 +1,122 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
+

+ 209 - 0
aead.go

@@ -0,0 +1,209 @@
+// aead.go - An AEAD_CHACHA20_POLY1305 implementation.
+//
+// To the extent possible under law, Yawning Angel has waived all copyright
+// and related or neighboring rights to chacha20poly1305, using the Creative
+// Commons "CC0" public domain dedication. See LICENSE or
+// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
+
+// Package chacha20poly1305 implemnets the RFC 7539 AEAD_CHACHA20_POLY1305
+// construct.  It depends on my ChaCha20 and Poly1305 libraries (and not
+// golang.org/x/crypto for the latter), and attempts to be correct and easy to
+// read over fast.
+//
+// When the golang.org/x/crypto maintainers feel like providing a sane
+// interface to the Poly1305 code, this will switch to using that, but not
+// before then.
+package chacha20poly1305
+
+import (
+	"crypto/cipher"
+	"crypto/subtle"
+	"encoding/binary"
+	"errors"
+
+	"git.schwanenlied.me/yawning/chacha20.git"
+	"git.schwanenlied.me/yawning/poly1305.git"
+)
+
+const (
+	// KeySize is the key length in bytes (32 bytes, 256 bits).
+	KeySize = chacha20.KeySize
+
+	// NonceSize is the nonce (IV) length in bytes (12 bytes, 96 bits).
+	NonceSize = chacha20.INonceSize
+
+	// Overhead is the tag length in bytes (16 bytes, 128 bits).
+	Overhead = poly1305.Size
+)
+
+var (
+	// ErrOpen is the error returned when an Open fails.
+	ErrOpen = errors.New("chacha20poly1305: message authentication failed")
+
+	paddingBytes [16]byte
+)
+
+// ChaCha20Poly1305 is an AEAD_CHACHA20_POLY1305 instance.
+type ChaCha20Poly1305 struct {
+	key [KeySize]byte
+}
+
+// NonceSize returns the size of the nonce that must be passed to Seal
+// and Open.
+func (a *ChaCha20Poly1305) NonceSize() int {
+	return NonceSize
+}
+
+// Overhead returns the maximum difference between the lengths of a
+// plaintext and its ciphertext.
+func (a *ChaCha20Poly1305) Overhead() int {
+	return Overhead
+}
+
+func (a *ChaCha20Poly1305) init(nonce []byte) (*chacha20.Cipher, *poly1305.Poly1305) {
+	if len(nonce) != a.NonceSize() {
+		panic("chacha20poly1305: len(nonce) != NonceSize()")
+	}
+
+	// First, a Poly1305 one-time key is generated from the 256-bit key
+	// and nonce using the procedure described in Section 2.6.
+	var polyKey [32]byte
+	defer memwipe(polyKey[:])
+
+	c, err := chacha20.NewCipher(a.key[:], nonce)
+	if err != nil {
+		panic("chacha20poly1305: failed to initialize chacha20: " + err.Error())
+	}
+	c.KeyStream(polyKey[:])
+	c.Seek(1) // Set the initial counter to 1 in preparation for payload.
+
+	m, err := poly1305.New(polyKey[:])
+	if err != nil {
+		panic("chacha20poly1305: failed to initialize poly1305: " + err.Error())
+	}
+
+	return c, m
+}
+
+// Seal encrypts and authenticates plaintext, authenticates the
+// additional data and appends the result to dst, returning the updated
+// slice. The nonce must be NonceSize() bytes long and unique for all
+// time, for a given key.
+func (a *ChaCha20Poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+	c, m := a.init(nonce)
+	defer c.Reset()
+	defer m.Clear()
+
+	// Next, the ChaCha20 encryption function is called to encrypt the
+	// plaintext, using the same key and nonce, and with the initial
+	// counter set to 1.
+	retLen := len(plaintext) + Overhead
+	ret := make([]byte, len(plaintext), retLen)
+	c.XORKeyStream(ret, plaintext)
+
+	// Finally, the Poly1305 function is called with the Poly1305 key
+	// calculated above, and a message constructed as a concatenation of
+	// the following:
+
+	// The AAD
+	m.Write(additionalData)
+
+	// padding1 -- the padding is up to 15 zero bytes, and it brings
+	//  the total length so far to an integral multiple of 16.  If the
+	//  length of the AAD was already an integral multiple of 16 bytes,
+	//  this field is zero-length.
+	padding1 := (16 - (len(additionalData) & 0x0f)) & 0x0f
+	m.Write(paddingBytes[:padding1])
+
+	// The ciphertext
+	m.Write(ret)
+
+	// padding2 -- the padding is up to 15 zero bytes, and it brings
+	//  the total length so far to an integral multiple of 16.  If the
+	//  length of the ciphertext was already an integral multiple of 16
+	//  bytes, this field is zero-length.
+	padding2 := (16 - (len(plaintext) & 0x0f)) & 0x0f
+	m.Write(paddingBytes[:padding2])
+
+	// The length of the additional data in octets (as a 64-bit
+	//  little-endian integer).
+	var lenBuf [8]byte
+	binary.LittleEndian.PutUint64(lenBuf[0:], uint64(len(additionalData)))
+	m.Write(lenBuf[:])
+
+	// The length of the ciphertext in octets (as a 64-bit little-
+	//  endian integer).
+	binary.LittleEndian.PutUint64(lenBuf[0:], uint64(len(plaintext)))
+	m.Write(lenBuf[:])
+
+	// Return `dst | ciphertext | tag.
+	ret = m.Sum(ret)
+	return append(dst, ret...)
+}
+
+// Open decrypts and authenticates ciphertext, authenticates the
+// additional data and, if successful, appends the resulting plaintext
+// to dst, returning the updated slice. The nonce must be NonceSize()
+// bytes long and both it and the additional data must match the
+// value passed to Seal.
+//
+// Even if the function fails, the contents of dst, up to its capacity,
+// may be overwritten.
+func (a *ChaCha20Poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+	if len(ciphertext) < Overhead {
+		return nil, ErrOpen
+	}
+	ctLen := len(ciphertext) - Overhead
+
+	c, m := a.init(nonce)
+	defer c.Reset()
+	defer m.Clear()
+
+	// Derive the tag based on the data received, and validate.
+	m.Write(additionalData)
+	padding1 := (16 - (len(additionalData) & 0x0f)) & 0x0f
+	m.Write(paddingBytes[:padding1])
+	m.Write(ciphertext[:ctLen])
+	padding2 := (16 - (ctLen & 0x0f)) & 0x0f
+	m.Write(paddingBytes[:padding2])
+	var lenBuf [8]byte
+	binary.LittleEndian.PutUint64(lenBuf[0:], uint64(len(additionalData)))
+	m.Write(lenBuf[:])
+	binary.LittleEndian.PutUint64(lenBuf[0:], uint64(ctLen))
+	m.Write(lenBuf[:])
+	derivedTag := m.Sum(nil)
+	if subtle.ConstantTimeCompare(ciphertext[ctLen:], derivedTag[:]) != 1 {
+		memwipe(dst)
+		return nil, ErrOpen
+	}
+
+	// Decrypt and return.
+	ret := make([]byte, ctLen)
+	c.XORKeyStream(ret, ciphertext[:ctLen])
+	return append(dst, ret...), nil
+}
+
+// Reset clears all sensitive cryptographic material from a given instance
+// so that it is no longer resident in memory.
+func (a *ChaCha20Poly1305) Reset() {
+	memwipe(a.key[:])
+}
+
+// New returns a new ChaCha20Poly1305 instance, keyed with a given key.
+func New(key []byte) (*ChaCha20Poly1305, error) {
+	if len(key) != KeySize {
+		return nil, chacha20.ErrInvalidKey
+	}
+
+	a := &ChaCha20Poly1305{}
+	copy(a.key[:], key)
+	return a, nil
+}
+
+func memwipe(buf []byte) {
+	for i := range buf {
+		buf[i] = 0
+	}
+}
+
+var _ cipher.AEAD = (*ChaCha20Poly1305)(nil)

+ 93 - 0
aead_test.go

@@ -0,0 +1,93 @@
+// aead_test.go - AEAD_CHACHA20_POLY1305 implementation tests.
+//
+// To the extent possible under law, Yawning Angel waived all copyright
+// and related or neighboring rights to chacha20poly1305, using the Creative
+// Commons "CC0" public domain dedication. See LICENSE or
+// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
+
+package chacha20poly1305
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestChaCha20Poly1305(t *testing.T) {
+	// Test vector taken from RFC 7539.
+	key := []byte{
+		0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+		0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+		0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+		0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+	}
+	nonce := []byte{
+		// "32-bit fixed common part"
+		0x07, 0x00, 0x00, 0x00,
+
+		// "IV"
+		0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	}
+	aad := []byte{
+		0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3,
+		0xc4, 0xc5, 0xc6, 0xc7,
+	}
+	vecPlaintext := []byte{
+		0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61,
+		0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
+		0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20,
+		0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
+		0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39,
+		0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
+		0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66,
+		0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
+		0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20,
+		0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
+		0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75,
+		0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
+		0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f,
+		0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
+		0x74, 0x2e,
+	}
+	vecCiphertext := []byte{
+		0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb,
+		0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
+		0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe,
+		0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
+		0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12,
+		0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
+		0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29,
+		0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
+		0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c,
+		0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58,
+		0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94,
+		0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
+		0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d,
+		0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
+		0x61, 0x16,
+	}
+	vecTag := []byte{
+		0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a,
+		0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91,
+	}
+
+	a, err := New(key)
+	if err != nil {
+		t.Fatalf("Failed to instantiate AEAD instance: %v", err)
+	}
+
+	ciphertext := a.Seal(nil, nonce, vecPlaintext, aad)
+	if !bytes.Equal(ciphertext[len(vecCiphertext):], vecTag) {
+		t.Fatalf("Seal: Tag mismatch")
+	}
+	if !bytes.Equal(ciphertext[:len(vecCiphertext)], vecCiphertext) {
+		t.Fatalf("Seal: Ciphertext mismatch")
+	}
+
+	plaintext, err := a.Open(nil, nonce, ciphertext, aad)
+	if err != nil {
+		t.Fatalf("Open: Failed")
+	}
+	if !bytes.Equal(plaintext, vecPlaintext) {
+		t.Fatalf("Open: Plaintext mismatch")
+	}
+}