summaryrefslogtreecommitdiffstats
path: root/internal/cryptoutil/cryptoutil.go
blob: b58282b9f81107ffa700a2df8de4d9976bf86349 (plain) (blame)
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Package cryptoutil contains friendly wrappers around the algorithms from the
// standard library's crypto package.
package cryptoutil

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hkdf"
	"crypto/hmac"
	"crypto/pbkdf2"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"errors"
)

const (
	defaultIterations = 3749890 // from cmd/finditers

	saltSize   = 2 * sha256.Size
	hashSize   = 32
	certSize   = sha256.Size
	aesKeySize = 32
	nonceSize  = aes.BlockSize
)

// A PasswordHash must be PasswordHashSize bytes.
const PasswordHashSize = saltSize + hashSize

// A PasswordHash is derived from the user's password and can be passed to
// CheckPassword to verify if two passwords match. A PasswordHash must be
// PasswordHashSize bytes.
type PasswordHash []byte

func (h PasswordHash) String() string { return hex.EncodeToString(h) }

// A RawKey is generated from a password hash and can be used to derive further
// key material.
type RawKey struct {
	key []byte
}

// An HMACKey must be HMACKeySize bytes.
const HMACKeySize = sha256.Size

// An HMAC key can be used to symmetrically sign and verify messages
// using HMAC-SHA256. An HMACKey must be HMACKeyLen bytes.
type HMACKey []byte

// A SignedMessage is a message that has been cryptographically signed
// with HMAC-SHA256.
type SignedMessage []byte

// An EncryptionKey must be EncryptionKeySize bytes.
const EncryptionKeySize = aesKeySize + HMACKeySize

// An EncryptionKey is used for encrypting and decrypting data. An EncryptionKey
// must be EncryptionKeySize bytes.
type EncryptionKey []byte

// An EncryptedMessage is encrypted with AES-256-CTR-HMAC-SHA256.
type EncryptedMessage []byte

// HashIter runs PBKDF2-SHA256 for iter iterations. Useful for benchmarking.
func HashIter(password string, salt []byte, iter int) ([]byte, error) {
	return pbkdf2.Key(sha256.New, password, salt, iter, sha256.Size)
}

func hashWithSalt(password string, salt []byte) (RawKey, []byte, error) {
	hash, err := HashIter(password, salt[:sha256.Size], defaultIterations)
	if err != nil {
		return RawKey{}, nil, err
	}
	key, err := hkdf.Extract(sha256.New, hash, salt[sha256.Size:])
	if err != nil {
		return RawKey{}, nil, err
	}
	pwHash, err := hkdf.Expand(sha256.New, key, "pwhash", hashSize)
	if err != nil {
		return RawKey{}, nil, err
	}
	return RawKey{key}, pwHash, nil
}

// Hash hashes a user password using PBKDF2-SHA256.
func Hash(password string) (PasswordHash, error) {
	salt := make([]byte, saltSize, saltSize+hashSize)
	rand.Read(salt)
	_, pwHash, err := hashWithSalt(password, salt)
	if err != nil {
		return nil, err
	}
	return append(salt, pwHash...), nil
}

// CheckPassword verifies password against a PasswordHash and returns an
// EncryptionKey derived from the password if successful.
func (h PasswordHash) CheckPassword(password string) (RawKey, error) {
	salt, expectedHash := h[:saltSize], h[saltSize:]
	key, pwHash, err := hashWithSalt(password, salt)
	if err != nil {
		return RawKey{}, err
	}
	if subtle.ConstantTimeCompare(pwHash, expectedHash) == 0 {
		return RawKey{}, errors.New("incorrect password")
	}
	return key, nil
}

// Sign generates an HMAC-SHA256 signature and appends it to msg.
func (k HMACKey) Sign(msg []byte) SignedMessage {
	mac := hmac.New(sha256.New, k)
	mac.Write(msg)
	return mac.Sum(msg)
}

// Verify checks whether the given message has a valid signature, and returns
// the raw message if it does.
func (k HMACKey) Verify(msg SignedMessage) ([]byte, bool) {
	if len(msg) < certSize {
		return nil, false
	}
	msg, sig := msg[:len(msg)-certSize], msg[len(msg)-certSize:]
	mac := hmac.New(sha256.New, k)
	mac.Write(msg)
	if !hmac.Equal(sig, mac.Sum(nil)) {
		return nil, false
	}
	return msg, true
}

// EncryptionKey derives an EncryptionKey.
func (k RawKey) EncryptionKey() (EncryptionKey, error) {
	return hkdf.Expand(sha256.New, k.key, "encrypt", EncryptionKeySize)
}

// Encrypt encrypts a message with AES-256-CTR-HMAC-SHA256.
func (k EncryptionKey) Encrypt(msg []byte) (EncryptedMessage, error) {
	aesKey, hmacKey := k[:aesKeySize], HMACKey(k[aesKeySize:])
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	cipherText := make([]byte, len(msg)+nonceSize, len(msg)+nonceSize+certSize)
	nonce := cipherText[len(msg) : len(msg)+nonceSize]
	rand.Read(nonce)
	cipher.NewCTR(block, nonce).XORKeyStream(cipherText, msg)
	return EncryptedMessage(hmacKey.Sign(cipherText)), nil
}

// Decrypt verifies and decrypts an encrypted message. It overwrites msg with
// the resulting plain text.
func (k EncryptionKey) Decrypt(msg EncryptedMessage) ([]byte, error) {
	aesKey, hmacKey := k[:aesKeySize], HMACKey(k[aesKeySize:])
	msg, ok := hmacKey.Verify(SignedMessage(msg))
	if !ok {
		return nil, errors.New("bad signature")
	}
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	msg, nonce := msg[:len(msg)-nonceSize], msg[len(msg)-nonceSize:]
	cipher.NewCTR(block, nonce).XORKeyStream(msg, msg)
	return msg, nil
}