goaesgcmio

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2023 License: Apache-2.0 Imports: 6 Imported by: 0

README

Golang AES GCM IO Read/Writer

This library implements both the io.WriteCloser and io.Reader interface when you need to encrypt/decrypt an unknown payload size with AES GCM in Golang.

The writer will take the provided payload bytes buffer then encrypt into specified chunk sizes. GCM encrypts a payload and provides authentication and integrity, each chunk is decrypted and verified before being returned to the caller. Often the amount needed to be read at any one time can be greater than the chunk size, therefore the whole chunk is read (due to verification and integrity) and kept in a buffer.

Due to the nature of this writer and the unknown total payload size, the writer needs to be explicitly closed otherwise the ciphertext will likely be missing bytes on the end.

You can provide whatever chunk size you like, ofcourse there will be a 16 (gcm) plus 12 (nonce/iv) byte overhead for each chunk. For uniformity each chunks payload will be multiples of aes.BlockSize. So the provided chunkSize is a maximum, it may not result in exactly the provided chunk size.

For simplicity the chunkSize is written at the start of the payload in clear text, the reader then reads this 4 byte value from the source reader upon creation of the reader. It would be pretty easy for an attacker to determine the chunk size anyway upon close inspection of the encrypted bytes anyway.

Finally a new random nonce/iv is created for every single chunk and prepended to the ciphertext bytes.

For example:

# Encrypting a 1096 byte cleartext payload with a provided 512 chunk,
# would result in the following.

512 - 16(gcm) - 12(nonce) = 484
484 / 16(aes.BlockSize) = 30
30 * aes.BlockSize = 480

So 1096 breaks down into three chunks:

480, 480, 136

With the 28 byte overhead plus chunk_size this should equal:

4, 508, 508, 164

Total Encrypted Bytes: 1184 (88 byte overhead)

Important

This library uses the standard crypto/cipher library and the function (https://pkg.go.dev/crypto/cipher#NewGCM), along with the above information you must be comfortable with the following:

  1. Chunk Size integer will be in clear text at the start of each chunk, an attacker would likely be able to work out the chunk size anyway if they analyze all the bytes/padding.
  2. The order of which the chunks are returned is not verified for integrity, you can implement your own HMAC hash fairly easily, but this would denote storing the entire decrypted payload in a buffer and only returning it to the caller once the hash is verified.
  3. Use a 32 byte key for AES256.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

func NewReader

func NewReader(r io.Reader, key []byte) (*Reader, error)

NewReader returns a reader to read plaintext bytes from the encrypted source reader.

Example

This example shows reading ciphertext from an io.Reader.

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"

	gcm "github.com/dlfoo/goaesgcmio"
)

func main() {
	// Declare the key we will use to decrypt the ciphertext.
	key, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
	if err != nil {
		// TODO: handle error.
	}

	// Decode the cleartext hex of 10 bytes.
	cleartext, err := hex.DecodeString("5d81f3c1b7d7bc599439")
	if err != nil {
		// TODO: handle error.
	}

	// Decode ciphertext hex which is 42 bytes (10 cleartext, 28 aes/gcm, 4 chunkSize.
	ciphertext, err := hex.DecodeString("fc010000f44a6d308c86b3360d2b891dda518dcf3df1aac63ff762e506cb4d0d3495c6d6d41e3eb6d69d")
	if err != nil {
		// TODO: handle error.
	}

	// Create new reader to decrypt ciphertext.
	r, err := gcm.NewReader(bytes.NewBuffer(ciphertext), key)
	if err != nil {
		// TODO: handle error.
	}

	cleartextBuffer := new(bytes.Buffer)

	// Read the cleartext cleartext from the reader.
	_, err = io.Copy(cleartextBuffer, r)
	if err != nil {
		// TODO: handle error.
	}

	fmt.Printf("Cleartext: %x\n", cleartext)
	fmt.Printf("Cleartext Size: %d bytes\n", len(cleartext))
	fmt.Printf("Cleartext: %x\n", cleartextBuffer.Bytes())
	fmt.Printf("Cleartext Size: %d bytes\n", cleartextBuffer.Len())
	fmt.Printf("Equal: %t\n", bytes.Equal(cleartext, cleartextBuffer.Bytes()))
}
Output:

func (*Reader) Close added in v1.0.1

func (g *Reader) Close() error

Close resets the reader, for the next new read.

func (*Reader) Read

func (g *Reader) Read(p []byte) (int, error)

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

func NewWriter

func NewWriter(w io.Writer, key []byte, chunkSize int) (*Writer, error)

NewWriter returns a writer to write plaintext payload to, if chunkSize is set to 0 then defaultChunkSize will be used.

Example

This example shows writing cleartext to an io.Writer. Note: You can't compare the ciphertext bytes output to the Reader.Read() example below, due to the random nonce.

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"

	gcm "github.com/dlfoo/goaesgcmio"
)

func main() {
	// Declare the key we will use to encrypt the cleartext.
	key, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
	if err != nil {
		// TODO: handle error.
	}

	// Decode the cleartext hex of 10 bytes.
	cleartext, err := hex.DecodeString("5d81f3c1b7d7bc599439")
	if err != nil {
		// TODO: handle error.
	}

	cipherTextBuffer := new(bytes.Buffer)

	// Create new GCM writer to encrypt ciphertext buffer. Setting 0 means
	// default chunk size will be used.
	w, err := gcm.NewWriter(cipherTextBuffer, key, 0)
	if err != nil {
		// TODO: handle error.
	}

	// Write the cleartext bytes to the cipherTextWriter.
	_, err = io.Copy(w, bytes.NewBuffer(cleartext))
	if err != nil {
		// TODO: handle error.
	}

	// Be sure to explicitly close the writer to flush any remaining data.
	if err := w.Close(); err != nil {
		// TODO: handle error.
	}

	fmt.Printf("Cleartext: %x\n", cleartext)
	fmt.Printf("Cleartext Size: %d bytes\n", len(cleartext))
	fmt.Printf("Ciphertext: %x\n", cipherTextBuffer.Bytes())
	fmt.Printf("Ciphertext Size: %d bytes\n", cipherTextBuffer.Len())
}
Output:

func (*Writer) Close

func (g *Writer) Close() error

func (*Writer) Write

func (g *Writer) Write(p []byte) (int, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL