-
Notifications
You must be signed in to change notification settings - Fork 15
/
crypto.go
204 lines (175 loc) · 6.11 KB
/
crypto.go
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// crypto.go - Cryptographic primitive wrappers.
// Copyright (C) 2017 Yawning Angel.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package crypto provides the Katzenpost parameterization of the Sphinx Packet
// Format cryptographic operations.
package crypto
import (
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"hash"
"gitlab.com/yawning/aez.git"
"gitlab.com/yawning/bsaes.git"
"golang.org/x/crypto/hkdf"
"github.com/katzenpost/hpqc/nike"
"github.com/katzenpost/hpqc/rand"
"github.com/katzenpost/katzenpost/core/utils"
)
const (
// HashLength is the output size of the unkeyed hash in bytes.
HashLength = sha512.Size256
// MACKeyLength is the key size of the MAC in bytes.
MACKeyLength = 32
// MACLength is the tag size of the MAC in bytes.
MACLength = 32
// StreamKeyLength is the key size of the stream cipher in bytes.
StreamKeyLength = 32
// StreamIVLength is the IV size of the stream cipher in bytes.
StreamIVLength = 16
// SPRPKeyLength is the key size of the SPRP in bytes.
SPRPKeyLength = 48
// SPRPIVLength is the IV size of the SPRP in bytes.
SPRPIVLength = StreamIVLength
privateKeySeedSize = 32
)
var kdfInfo = []byte("katzenpost-kdf-v0-hkdf-sha256")
type resetable interface {
Reset()
}
type macWrapper struct {
hash.Hash
}
func (m *macWrapper) Sum(b []byte) []byte {
tmp := m.Hash.Sum(nil)
b = append(b, tmp...)
return b
}
// Stream is the Sphinx stream cipher.
type Stream struct {
cipher.Stream
}
// KeyStream fills the buffer dst with key stream output.
func (s *Stream) KeyStream(dst []byte) {
// TODO: Add a fast path for implementations that support it, to
// shave off the memset and XOR.
utils.ExplicitBzero(dst)
s.XORKeyStream(dst, dst)
}
// Reset clears the Stream instance such that no sensitive data is left in
// memory.
func (s *Stream) Reset() {
// bsaes's ctrAble implementation exposes this, `crypto/aes` does not,
// c'est la vie.
if r, ok := s.Stream.(resetable); ok {
r.Reset()
}
}
// Hash calculates the digest of message m.
func Hash(msg []byte) [HashLength]byte {
return sha512.Sum512_256(msg)
}
// NewMAC returns a new hash.Hash implementing the Sphinx MAC with the provided
// key.
func NewMAC(key *[MACKeyLength]byte) hash.Hash {
return &macWrapper{hmac.New(sha256.New, key[:])}
}
// NewStream returns a new Stream implementing the Sphinx Stream Cipher with
// the provided key and IV.
func NewStream(key *[StreamKeyLength]byte, iv *[StreamIVLength]byte) *Stream {
// bsaes is smart enough to detect if the Go runtime and the CPU support
// AES-NI and PCLMULQDQ and call `crypto/aes`.
//
// TODO: The AES-NI `crypto/aes` CTR mode implementation is horrid and
// massively underperforms so eventually bsaes should include assembly.
blk, err := bsaes.NewCipher(key[:])
if err != nil {
// Not covered by unit tests because this indicates a bug in bsaes.
panic("crypto/NewStream: failed to create AES instance: " + err.Error())
}
return &Stream{cipher.NewCTR(blk, iv[:])}
}
// SPRPEncrypt returns the ciphertext of the message msg, encrypted via the
// Sphinx SPRP with the provided key and IV.
func SPRPEncrypt(key *[SPRPKeyLength]byte, iv *[SPRPIVLength]byte, msg []byte) []byte {
return aez.Encrypt(key[:], iv[:], nil, 0, msg, nil)
}
// SPRPDecrypt returns the plaintext of the message msg, decrypted via the
// Sphinx SPRP with the provided key and IV.
func SPRPDecrypt(key *[SPRPKeyLength]byte, iv *[SPRPIVLength]byte, msg []byte) []byte {
dst, ok := aez.Decrypt(key[:], iv[:], nil, 0, msg, nil)
if !ok {
// Not covered by unit tests because this indicates a bug in the AEZ
// implementation, that is hard to force.
panic("crypto/SPRPDecrypt: BUG - aez.Decrypt failed with tau = 0")
}
return dst
}
// PacketKeys are the per-hop Sphinx Packet Keys, derived from the blinded
// DH key exchange.
type PacketKeys struct {
HeaderMAC [MACKeyLength]byte
HeaderEncryption [StreamKeyLength]byte
HeaderEncryptionIV [StreamIVLength]byte
PayloadEncryption [SPRPKeyLength]byte
BlindingFactor nike.PrivateKey
}
// Reset clears the PacketKeys structure such that no sensitive data is left
// in memory.
func (k *PacketKeys) Reset() {
utils.ExplicitBzero(k.HeaderMAC[:])
utils.ExplicitBzero(k.HeaderEncryption[:])
utils.ExplicitBzero(k.HeaderEncryptionIV[:])
utils.ExplicitBzero(k.PayloadEncryption[:])
if k.BlindingFactor != nil {
k.BlindingFactor.Reset()
}
}
// KDF takes the input key material and returns the Sphinx Packet keys.
// HOWEVER if the NIKE scheme interface object is set to nil then the BlindingFactor
// will not be generated by the KDF. The reason we do this is because
// KEMSphinx doesn't need the BlindingFactor.
func KDF(ikm []byte, scheme nike.Scheme) *PacketKeys {
okmLength := MACKeyLength + StreamKeyLength + StreamIVLength + SPRPKeyLength + privateKeySeedSize
okm := make([]byte, okmLength)
h := hkdf.Expand(sha256.New, ikm, kdfInfo)
count, err := h.Read(okm)
if count != okmLength {
panic("hkdf read fail")
}
if err != nil {
panic(err)
}
defer utils.ExplicitBzero(okm)
ptr := okm
k := new(PacketKeys)
copy(k.HeaderMAC[:], ptr[:MACKeyLength])
ptr = ptr[MACKeyLength:]
copy(k.HeaderEncryption[:], ptr[:StreamKeyLength])
ptr = ptr[StreamKeyLength:]
copy(k.HeaderEncryptionIV[:], ptr[:StreamIVLength])
ptr = ptr[StreamIVLength:]
copy(k.PayloadEncryption[:], ptr[:SPRPKeyLength])
ptr = ptr[SPRPKeyLength:]
if scheme != nil {
reader, err := rand.NewDeterministicRandReader(ptr[:privateKeySeedSize])
if err != nil {
panic(err)
}
k.BlindingFactor = scheme.GeneratePrivateKey(reader)
}
return k
}