Repository navigation
Expand file tree
/
Copy pathencryption.go
More file actions
103 lines (89 loc) · 3.39 KB
/
Copy pathencryption.go
File metadata and controls
103 lines (89 loc) · 3.39 KB
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Package encryption provides authenticated encryption for complete byte
// slices.
//
// A [Box] uses AES-256-GCM with a fresh random nonce for every call to
// [Box.Seal]. The nonce and authentication tag are stored in a versioned
// envelope, so callers do not manage nonces, padding, or message
// authentication themselves.
//
// Keys must contain [KeySize] bytes of cryptographically random data. A
// password is not a key; derive keys from passwords in a package designed for
// password-based key derivation before constructing a Box.
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
)
const (
// KeySize is the required AES-256 key size in bytes.
KeySize = 32
envelopeVersion = byte(1)
algorithmAESGCM = byte(1)
headerSize = 6
)
var (
// ErrInvalidKeySize is returned when a key is not exactly [KeySize] bytes.
ErrInvalidKeySize = errors.New("encryption: key must be 32 bytes")
// ErrInvalidEnvelope is returned when an envelope is truncated,
// unsupported, modified, or cannot be authenticated with the supplied key
// and additional data.
ErrInvalidEnvelope = errors.New("encryption: invalid envelope")
envelopeHeader = [headerSize]byte{'Q', 'B', 'E', 'N', envelopeVersion, algorithmAESGCM}
)
// Box seals and opens authenticated, versioned encryption envelopes.
//
// A key must not be used to seal more than 2^32 messages. Applications are
// responsible for key storage, distribution, rotation, and lifecycle.
type Box struct {
aead cipher.AEAD
}
// GenerateKey returns a new cryptographically random AES-256 key.
func GenerateKey() []byte {
key := make([]byte, KeySize)
rand.Read(key)
return key
}
// New constructs a Box from a [KeySize]-byte key.
func New(key []byte) (*Box, error) {
if len(key) != KeySize {
return nil, ErrInvalidKeySize
}
// The exact key-size check above guarantees that aes.NewCipher succeeds.
block, _ := aes.NewCipher(key)
// NewGCMWithRandomNonce accepts every block returned by aes.NewCipher.
aead, _ := cipher.NewGCMWithRandomNonce(block)
return &Box{aead: aead}, nil
}
// Seal encrypts and authenticates plaintext and returns a versioned envelope.
// Additional data is authenticated but is not stored in the envelope; the
// same bytes must be supplied to [Box.Open].
func (b *Box) Seal(plaintext, additionalData []byte) []byte {
envelope := make([]byte, headerSize, headerSize+b.aead.Overhead()+len(plaintext))
copy(envelope, envelopeHeader[:])
authenticatedData := joinAdditionalData(envelopeHeader[:], additionalData)
return b.aead.Seal(envelope, nil, plaintext, authenticatedData)
}
// Open authenticates and decrypts an envelope produced by [Box.Seal]. It
// returns [ErrInvalidEnvelope] without plaintext if the envelope, key, or
// additional data is invalid.
func (b *Box) Open(envelope, additionalData []byte) ([]byte, error) {
if len(envelope) < headerSize+b.aead.Overhead() ||
!bytes.Equal(envelope[:headerSize], envelopeHeader[:]) {
return nil, ErrInvalidEnvelope
}
authenticatedData := joinAdditionalData(envelope[:headerSize], additionalData)
plaintext, err := b.aead.Open(nil, nil, envelope[headerSize:], authenticatedData)
if err != nil {
return nil, ErrInvalidEnvelope
}
return plaintext, nil
}
func joinAdditionalData(header, additionalData []byte) []byte {
joined := make([]byte, len(header)+len(additionalData))
copy(joined, header)
copy(joined[len(header):], additionalData)
return joined
}