aesgcm

package
v1.7.2 Latest Latest
Warning

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

Go to latest
Published: Jun 3, 2024 License: MPL-2.0 Imports: 8 Imported by: 0

README

AES-GCM encryption method

[!WARNING] This file is not an end-user documentation, it is intended for developers. Please follow the user documentation on the OpenTofu website unless you want to work on the encryption code.

This folder contains the state encryption implementation of the AES-GCM encryption method. This is implemented following the guidance of the following document: (NIST SP 800-38D).

Configuration

You can configure the encryption by specifying the following method block:

terraform {
  encryption {
    method "aes_gcm" "mymethod" {
      # Pass the key provider with a 16, 24, or 32 byte encryption key here:
      keys = key_provider.someprovider.somename
      
      # Leave the AAD empty unless needed. Pass as a list of bytes if needed:  
      aad  = [1,2,3,4,...]
    }
  }
}
Field Description
keys (required) Encryption and decryption key in the standard output structure of the key providers ({"encryption_key":[]byte, "decryption_key":[]byte}).
aad Additional Authenticated Data. This data is stored along the encrypted form and authenticated. The AAD value of the encrypted form must match the configuration, otherwise the decryption fails.

Key exhaustion

AES-GCM keys have a limited lifetime of 2^32 blocks, equaling roughly 64 GB of data that can be encrypted before the keys should be considered compromised. The end-user documentation of this method should guide users to use either a key-derivation function, such as PBKDF2 or Argon2 with a sufficiently long passphrase, or a key management system that can automatically rotate the keys.

Encryption vs. Authentication

The AES-GCM implementation protects data at rest from being accessed. It does not, however, protect against malicious actors reusing old data (replay attacks) to compromise the integrity of the system. Users with the need for payload authentication should rotate their key and/or AAD frequently to ensure that old data cannot be used in this manner.

Implementation notes

Additional Authenticated Data (AAD)

The AAD in AES-GCM is a general-purpose authenticated, but not encrypted field in the encrypted payload. The Go implementation only supports using this field as a canary value, rejecting decryption if the value mismatches. AES-GCM would support using this field as a means to store data. Since Go does not support it, neither do we.

Panics

The current Go implementation of AES-GCM uses panic() to handle some input errors.

Documentation

Overview

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
)

func main() {
	descriptor := aesgcm.New()

	// Get the config struct. You can fill it manually by type-asserting it to aesgcm.Config, but you could also use
	// it as JSON.
	config := descriptor.ConfigStruct()

	if err := json.Unmarshal(
		// Set up a randomly generated 32-byte key. In JSON, you can base64-encode the value.
		[]byte(`{
    "keys": {
		"encryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ=",
		"decryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ="
	}
}`), &config); err != nil {
		panic(err)
	}

	method, err := config.Build()
	if err != nil {
		panic(err)
	}

	// Encrypt some data:
	encrypted, err := method.Encrypt([]byte("Hello world!"))
	if err != nil {
		panic(err)
	}

	// Now decrypt it:
	decrypted, err := method.Decrypt(encrypted)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", decrypted)
}
Output:

Hello world!
Example (Config)
package main

import (
	"fmt"

	"github.com/opentofu/opentofu/internal/encryption/keyprovider"
	"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
)

func main() {
	// First, get the descriptor to make sure we always have the default values.
	descriptor := aesgcm.New()

	// Obtain a modifiable, buildable config. Alternatively, you can also use ConfigStruct() method to obtain a
	// struct you can fill with HCL or JSON tags.
	config := descriptor.TypedConfig()

	// Set up an encryption key:
	config.Keys = keyprovider.Output{
		EncryptionKey: []byte("AiphoogheuwohShal8Aefohy7ooLeeyu"),
		DecryptionKey: []byte("AiphoogheuwohShal8Aefohy7ooLeeyu"),
	}

	// Now you can build a method:
	method, err := config.Build()
	if err != nil {
		panic(err)
	}

	// Encrypt something:
	encrypted, err := method.Encrypt([]byte("Hello world!"))
	if err != nil {
		panic(err)
	}

	// Decrypt it:
	decrypted, err := method.Decrypt(encrypted)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", decrypted)
}
Output:

Hello world!
Example (Config_hcl)
// First, get the descriptor to make sure we always have the default values.
descriptor := aesgcm.New()

// Get an untyped config struct you can use for HCL unmarshalling:
config := descriptor.ConfigStruct()

// Unmarshal HCL code into the config struct. The input must be a list of bytes, so in a real world scenario
// you may want to put in a hex-decoding function:
rawHCLInput := `keys = {
	encryption_key = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],
	decryption_key = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
}`
file, diags := hclsyntax.ParseConfig(
	[]byte(rawHCLInput),
	"example.hcl",
	hcl.Pos{Byte: 0, Line: 1, Column: 1},
)
if diags.HasErrors() {
	panic(diags)
}
if diags := gohcl.DecodeBody(file.Body, nil, config); diags.HasErrors() {
	panic(diags)
}

// Now you can build a method:
method, err := config.Build()
if err != nil {
	panic(err)
}

// Encrypt something:
encrypted, err := method.Encrypt([]byte("Hello world!"))
if err != nil {
	panic(err)
}

// Decrypt it:
decrypted, err := method.Decrypt(encrypted)
if err != nil {
	panic(err)
}

fmt.Printf("%s", decrypted)
Output:

Hello world!
Example (Config_json)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
)

func main() {
	// First, get the descriptor to make sure we always have the default values.
	descriptor := aesgcm.New()

	// Get an untyped config struct you can use for JSON unmarshalling:
	config := descriptor.ConfigStruct()

	// Unmarshal JSON into the config struct:
	if err := json.Unmarshal(
		// Set up a randomly generated 32-byte key. In JSON, you can base64-encode the value.
		[]byte(`{
    "keys": {
		"encryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ=",
		"decryption_key": "Y29veTRhaXZ1NWFpeW9vMWlhMG9vR29vVGFlM1BhaTQ="
	}
}`), &config); err != nil {
		panic(err)
	}

	// Now you can build a method:
	method, err := config.Build()
	if err != nil {
		panic(err)
	}

	// Encrypt something:
	encrypted, err := method.Encrypt([]byte("Hello world!"))
	if err != nil {
		panic(err)
	}

	// Decrypt it:
	decrypted, err := method.Decrypt(encrypted)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", decrypted)
}
Output:

Hello world!
Example (HandlePanic)
_, err := handlePanic(func() ([]byte, error) {
	panic("Hello world!")
})
fmt.Printf("%v", err)
Output:

Hello world!

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Key is the encryption key for the AES-GCM encryption. It has to be 16, 24, or 32 bytes long for AES-128, 192, or
	// 256, respectively.
	Keys keyprovider.Output `hcl:"keys" json:"keys" yaml:"keys"`

	// AAD is the Additional Authenticated Data that is authenticated, but not encrypted. In the Go implementation, this
	// data serves as a canary value against replay attacks. The AAD value on decryption must match this setting,
	// otherwise the decryption will fail. (Note: this is Go-specific and differs from the NIST SP 800-38D description
	// of the AAD.)
	AAD []byte `hcl:"aad,optional" json:"aad,omitempty" yaml:"aad,omitempty"`
}

Config is the configuration for the AES-GCM method.

func (*Config) Build

func (c *Config) Build() (method.Method, error)

Build checks the validity of the configuration and returns a ready-to-use AES-GCM implementation.

type Descriptor

type Descriptor interface {
	method.Descriptor

	// TypedConfig returns a config typed for this method.
	TypedConfig() *Config
}

Descriptor integrates the method.Descriptor and provides a TypedConfig for easier configuration.

func New

func New() Descriptor

New creates a new descriptor for the AES-GCM encryption method, which requires a 32-byte key.

Jump to

Keyboard shortcuts

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