Skip to content

Commit 0a4e40c

Browse files
committed
New aes-hmac implemention.
1 parent 18ccf21 commit 0a4e40c

File tree

4 files changed

+167
-133
lines changed

4 files changed

+167
-133
lines changed

go1/sqlite3/codec/aes-hmac.go

Lines changed: 69 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -15,101 +15,100 @@ import (
1515
. "code.google.com/p/go-sqlite/go1/sqlite3"
1616
)
1717

18-
// TODO(max): Remove the master key.
19-
// TODO(max): MAC the cipher spec.
20-
// TODO(max): HMAC key should be the same size as the output.
21-
2218
type aesHmac struct {
23-
key []byte // Key provided to newAesHmac
24-
p1k []byte // Page 1 key (subslice of key)
25-
p1i []byte // Page 1 HKDF info ("go-sqlite")
26-
buf []byte // Page encryption buffer
27-
kLen int // Cipher and HMAC key length in bytes (16, 24, or 32)
28-
tLen int // Tag length in bytes (HMAC truncation)
29-
30-
// Hash function and cipher chaining mode constructors
19+
key []byte // Key provided to newAesHmac with the master key removed
20+
buf []byte // Page encryption buffer
21+
hdr [4]byte // Header included in each HMAC calculation (page number)
22+
tLen int // Tag length in bytes (HMAC truncation)
23+
24+
// Hash function and chaining mode constructors
3125
hash func() hash.Hash
3226
mode func(block cipher.Block, iv []byte) cipher.Stream
3327

34-
// Block cipher and HMAC hash initialized from the master key
28+
// Block cipher and HMAC initialized from the master key
3529
block cipher.Block
3630
hmac hash.Hash
3731
}
3832

3933
func newAesHmac(ctx *CodecCtx, key []byte) (Codec, *Error) {
40-
_, opts, mk := parseKey(key)
41-
if mk == nil {
34+
name, opts, mk := parseKey(key)
35+
if len(mk) == 0 {
4236
return nil, keyErr
4337
}
38+
defer wipe(mk)
39+
40+
// Configure the codec
4441
c := &aesHmac{
45-
key: key,
46-
p1k: mk,
47-
p1i: []byte("go-sqlite\x00")[:9],
48-
kLen: 16,
42+
key: key[:len(key)-len(mk)],
4943
tLen: 16,
5044
hash: sha1.New,
5145
mode: cipher.NewCTR,
5246
}
53-
if err := c.config(opts); err != nil {
47+
suite := suiteId{
48+
Cipher: "aes",
49+
KeySize: "128",
50+
Mode: "ctr",
51+
MAC: "hmac",
52+
Hash: "sha1",
53+
Trunc: "128",
54+
}
55+
kLen := 16
56+
if err := c.config(opts, &suite, &kLen); err != nil {
5457
return nil, err
5558
}
59+
60+
// Derive encryption and authentication keys
61+
hLen := c.hash().Size()
62+
salt := make([]byte, hLen)
63+
copy(salt, name)
64+
dk := hkdf(mk, salt, kLen+hLen, c.hash)(suite.Id())
65+
defer wipe(dk)
66+
67+
// Initialize the block cipher and HMAC
68+
var err error
69+
if c.block, err = aes.NewCipher(dk[:kLen]); err != nil {
70+
return nil, NewError(MISUSE, err.Error())
71+
}
72+
c.hmac = hmac.New(c.hash, dk[kLen:])
5673
return c, nil
5774
}
5875

5976
func (c *aesHmac) Reserve() int {
60-
return c.kLen + aes.BlockSize + c.tLen
77+
return aes.BlockSize + c.tLen
6178
}
6279

6380
func (c *aesHmac) Resize(pageSize, reserve int) {
6481
if reserve != c.Reserve() {
6582
panic("sqlite3: codec reserve value mismatch")
6683
}
67-
tMax := c.hash().Size()
68-
c.buf = make([]byte, pageSize, pageSize-c.tLen+tMax)
84+
hLen := c.hash().Size()
85+
c.buf = make([]byte, pageSize, pageSize-c.tLen+hLen)
6986
}
7087

7188
func (c *aesHmac) Encode(p []byte, n uint32, op int) ([]byte, *Error) {
72-
// Generate new random IV (HKDF salt for page 1)
7389
iv := c.pIV(c.buf)
7490
if !rnd(iv) {
7591
return nil, prngErr
7692
}
77-
78-
// Create the master key if encrypting page 1 for a new database
79-
if c.block == nil && !c.init(p, n, true) {
80-
return nil, codecErr
81-
}
82-
83-
// Encrypt-then-MAC
84-
cipher, hmac := c.cipher(n, iv)
85-
cipher.XORKeyStream(c.buf, c.pText(p))
93+
c.mode(c.block, iv).XORKeyStream(c.buf, c.pText(p))
8694
if n == 1 {
87-
copy(c.buf[16:], p[16:24]) // Bytes 16 through 23 cannot be encrypted
95+
copy(c.buf[16:], p[16:24])
8896
}
89-
c.mac(c.buf, n, hmac, false)
97+
c.auth(c.buf, n, false)
9098
return c.buf, nil
9199
}
92100

93101
func (c *aesHmac) Decode(p []byte, n uint32, op int) *Error {
94-
// Verify tag
95-
cipher, hmac := c.cipher(n, c.pIV(p))
96-
if !c.mac(p, n, hmac, true) {
102+
if !c.auth(p, n, true) {
97103
return codecErr
98104
}
99-
100-
// Decrypt
101105
if n == 1 {
102106
copy(c.buf, p[16:24])
103107
}
104-
cipher.XORKeyStream(p, c.pText(p))
108+
c.mode(c.block, c.pIV(p)).XORKeyStream(p, c.pText(p))
105109
if n == 1 {
106110
copy(p[16:24], c.buf)
107111
}
108-
109-
// Get the master key if decrypting page 1 for the first time
110-
if c.block == nil && !c.init(p, n, false) {
111-
return codecErr
112-
}
113112
return nil
114113
}
115114

@@ -118,21 +117,26 @@ func (c *aesHmac) Key() []byte {
118117
}
119118

120119
func (c *aesHmac) Free() {
121-
wipe(c.key)
122-
*c = aesHmac{}
120+
c.buf = nil
121+
c.block = nil
122+
c.hmac = nil
123123
}
124124

125125
// config applies the codec options that were provided in the key.
126-
func (c *aesHmac) config(opts map[string]string) *Error {
126+
func (c *aesHmac) config(opts map[string]string, s *suiteId, kLen *int) *Error {
127127
for k := range opts {
128128
switch k {
129129
case "192":
130-
c.kLen = 24
130+
s.KeySize = k
131+
*kLen = 24
131132
case "256":
132-
c.kLen = 32
133+
s.KeySize = k
134+
*kLen = 32
133135
case "ofb":
136+
s.Mode = k
134137
c.mode = cipher.NewOFB
135138
case "sha256":
139+
s.Hash = k
136140
c.hash = sha256.New
137141
default:
138142
return NewError(MISUSE, "invalid codec option: "+k)
@@ -141,66 +145,21 @@ func (c *aesHmac) config(opts map[string]string) *Error {
141145
return nil
142146
}
143147

144-
// cipher returns the stream cipher and HMAC hash for page n. Both are reset to
145-
// their initial state.
146-
func (c *aesHmac) cipher(n uint32, iv []byte) (cipher.Stream, hash.Hash) {
147-
if n > 1 {
148-
c.hmac.Reset()
149-
return c.mode(c.block, iv), c.hmac
150-
}
151-
152-
// Derive page 1 cipher key, HMAC key, and IV
153-
dkLen := 2 * c.kLen
154-
dk := hkdf(c.p1k, iv, dkLen+aes.BlockSize, c.hash)(c.p1i)
155-
dk, iv = dk[:dkLen], dk[dkLen:]
156-
defer wipe(dk)
148+
// auth calculates and verifies the HMAC tag for page p. It returns true iff the
149+
// tag is successfully verified.
150+
func (c *aesHmac) auth(p []byte, n uint32, verify bool) bool {
151+
c.hdr[0] = byte(n >> 24)
152+
c.hdr[1] = byte(n >> 16)
153+
c.hdr[2] = byte(n >> 8)
154+
c.hdr[3] = byte(n)
157155

158-
// Initialize the stream cipher and HMAC for page 1
159-
if block, hmac := c.rekey(dk[:c.kLen], dk[c.kLen:]); block != nil {
160-
return c.mode(block, iv), hmac
161-
}
162-
return nil, nil
163-
}
164-
165-
// mac calculates and optionally verifies the HMAC tag for page p. It returns
166-
// true iff the tag verification is successful.
167-
func (c *aesHmac) mac(p []byte, n uint32, h hash.Hash, verify bool) bool {
168-
h.Write([]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)})
169-
h.Write(c.pAuth(p))
170156
tag := c.pTag(c.buf)
171-
h.Sum(tag[:0])
172-
return verify && hmac.Equal(tag, c.pTag(p))
173-
}
174-
175-
// init initializes the block cipher and HMAC hash using the page 1 master key.
176-
// It returns true on success and false otherwise.
177-
func (c *aesHmac) init(p []byte, n uint32, newKey bool) bool {
178-
if n != 1 {
179-
return false // First call to Encrypt or Decrypt must be for page 1
180-
}
181-
182-
// Load or generate the master key
183-
mk := c.pKey(p)
184-
if newKey && !rnd(mk) {
185-
return false
186-
}
157+
c.hmac.Reset()
158+
c.hmac.Write(c.hdr[:])
159+
c.hmac.Write(c.pAuth(p))
160+
c.hmac.Sum(tag[:0])
187161

188-
// Derive cipher and HMAC keys from the master key
189-
hdr := append(make([]byte, 0, 17), p[:16]...)
190-
dk := hkdf(mk, nil, 2*c.kLen, c.hash)(hdr)
191-
defer wipe(dk)
192-
193-
// Initialize the block cipher and HMAC
194-
c.block, c.hmac = c.rekey(dk[:c.kLen], dk[c.kLen:])
195-
return c.block != nil
196-
}
197-
198-
// rekey creates a new block cipher and HMAC hash using the specified keys.
199-
func (c *aesHmac) rekey(ck, hk []byte) (cipher.Block, hash.Hash) {
200-
if block, err := aes.NewCipher(ck); err == nil {
201-
return block, hmac.New(c.hash, hk)
202-
}
203-
return nil, nil
162+
return verify && hmac.Equal(tag, c.pTag(p))
204163
}
205164

206165
// pAuth returns the page subslice that gets authenticated.
@@ -213,16 +172,9 @@ func (c *aesHmac) pText(p []byte) []byte {
213172
return p[:len(p)-c.tLen-aes.BlockSize]
214173
}
215174

216-
// pKey returns the master key from page 1.
217-
func (c *aesHmac) pKey(p []byte) []byte {
218-
off := len(p) - c.tLen - aes.BlockSize - c.kLen
219-
return p[off : off+c.kLen]
220-
}
221-
222-
// pIV returns the page initialization vector (HKDF salt for page 1).
175+
// pIV returns the page initialization vector.
223176
func (c *aesHmac) pIV(p []byte) []byte {
224-
off := len(p) - c.tLen - aes.BlockSize
225-
return p[off : off+aes.BlockSize]
177+
return p[len(p)-c.tLen-aes.BlockSize : len(p)-c.tLen]
226178
}
227179

228180
// pTag returns the page authentication tag.

go1/sqlite3/codec/codec.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,40 @@ func wipe(b []byte) {
9191
b[i] = 0
9292
}
9393
}
94+
95+
// suiteId constructs a canonical cipher suite identifier.
96+
type suiteId struct {
97+
Cipher string
98+
KeySize string
99+
Mode string
100+
MAC string
101+
Hash string
102+
Trunc string
103+
}
104+
105+
func (s *suiteId) Id() []byte {
106+
id := make([]byte, 0, 64)
107+
section := func(parts ...string) {
108+
for i, p := range parts {
109+
if p != "" {
110+
parts = parts[i:]
111+
goto write
112+
}
113+
}
114+
return
115+
write:
116+
if len(id) > 0 {
117+
id = append(id, ',')
118+
}
119+
id = append(id, parts[0]...)
120+
for _, p := range parts[1:] {
121+
if p != "" {
122+
id = append(id, '-')
123+
id = append(id, p...)
124+
}
125+
}
126+
}
127+
section(s.Cipher, s.KeySize, s.Mode)
128+
section(s.MAC, s.Hash, s.Trunc)
129+
return id
130+
}

go1/sqlite3/codec/codec_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,46 @@ func TestHKDF(t *testing.T) {
8383
}
8484
}
8585
}
86+
87+
func TestSuiteId(t *testing.T) {
88+
tests := []struct {
89+
suite suiteId
90+
out string
91+
}{
92+
{suiteId{},
93+
""},
94+
{suiteId{Cipher: "aes"},
95+
"aes"},
96+
{suiteId{KeySize: "128"},
97+
"128"},
98+
{suiteId{Cipher: "aes", KeySize: "128"},
99+
"aes-128"},
100+
{suiteId{Cipher: "aes", Mode: "ctr"},
101+
"aes-ctr"},
102+
{suiteId{Cipher: "aes", KeySize: "128", Mode: "ctr"},
103+
"aes-128-ctr"},
104+
{suiteId{MAC: "hmac"},
105+
"hmac"},
106+
{suiteId{MAC: "hmac", Hash: "sha1"},
107+
"hmac-sha1"},
108+
{suiteId{MAC: "hmac", Hash: "sha1", Trunc: "128"},
109+
"hmac-sha1-128"},
110+
{suiteId{Cipher: "aes", MAC: "hmac"},
111+
"aes,hmac"},
112+
{suiteId{Cipher: "aes", Hash: "sha1"},
113+
"aes,sha1"},
114+
{suiteId{Mode: "ctr", Hash: "sha1"},
115+
"ctr,sha1"},
116+
{suiteId{Cipher: "aes", KeySize: "128", MAC: "hmac", Hash: "sha256"},
117+
"aes-128,hmac-sha256"},
118+
{suiteId{Cipher: "aes", Mode: "ctr", Hash: "sha256", Trunc: "128"},
119+
"aes-ctr,sha256-128"},
120+
{suiteId{Cipher: "aes", KeySize: "256", Mode: "ctr", MAC: "hmac", Hash: "sha256", Trunc: "128"},
121+
"aes-256-ctr,hmac-sha256-128"},
122+
}
123+
for _, test := range tests {
124+
if out := string(test.suite.Id()); out != test.out {
125+
t.Errorf("%#v expected %q; got %q", test.suite, test.out, out)
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)