Skip to content

Commit c2843e0

Browse files
Andreas AuernhammerFiloSottile
Andreas Auernhammer
authored andcommitted
poly1305: implement a subset of the hash.Hash interface
This CL adds the poly1305.MAC type which implements a subset of the hash.Hash interface. With MAC it is possible to compute an authentication tag of data without copying it into a single byte slice. This commit modifies the reference/generic and the AMD64 assembler but not the ARM/s390x implementation to support an io.Writer interface. Updates golang/go#25219 Change-Id: I7ee5a9eadd43387cf3cd887d734c625575eee47d Reviewed-on: https://go-review.googlesource.com/c/crypto/+/111335 Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]>
1 parent 8dd112b commit c2843e0

File tree

7 files changed

+322
-92
lines changed

7 files changed

+322
-92
lines changed

poly1305/mac_noasm.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build !amd64 gccgo appengine
6+
7+
package poly1305
8+
9+
type mac struct{ macGeneric }
10+
11+
func newMAC(key *[32]byte) mac { return mac{newMACGeneric(key)} }

poly1305/poly1305.go

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
/*
6-
Package poly1305 implements Poly1305 one-time message authentication code as
7-
specified in https://cr.yp.to/mac/poly1305-20050329.pdf.
8-
9-
Poly1305 is a fast, one-time authentication function. It is infeasible for an
10-
attacker to generate an authenticator for a message without the key. However, a
11-
key must only be used for a single message. Authenticating two different
12-
messages with the same key allows an attacker to forge authenticators for other
13-
messages with the same key.
14-
15-
Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was
16-
used with a fixed key in order to generate one-time keys from an nonce.
17-
However, in this package AES isn't used and the one-time key is specified
18-
directly.
19-
*/
5+
// Package poly1305 implements Poly1305 one-time message authentication code as
6+
// specified in https://cr.yp.to/mac/poly1305-20050329.pdf.
7+
//
8+
// Poly1305 is a fast, one-time authentication function. It is infeasible for an
9+
// attacker to generate an authenticator for a message without the key. However, a
10+
// key must only be used for a single message. Authenticating two different
11+
// messages with the same key allows an attacker to forge authenticators for other
12+
// messages with the same key.
13+
//
14+
// Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was
15+
// used with a fixed key in order to generate one-time keys from an nonce.
16+
// However, in this package AES isn't used and the one-time key is specified
17+
// directly.
2018
package poly1305 // import "golang.org/x/crypto/poly1305"
2119

2220
import "crypto/subtle"
@@ -31,3 +29,55 @@ func Verify(mac *[16]byte, m []byte, key *[32]byte) bool {
3129
Sum(&tmp, m, key)
3230
return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1
3331
}
32+
33+
// New returns a new MAC computing an authentication
34+
// tag of all data written to it with the given key.
35+
// This allows writing the message progressively instead
36+
// of passing it as a single slice. Common users should use
37+
// the Sum function instead.
38+
//
39+
// The key must be unique for each message, as authenticating
40+
// two different messages with the same key allows an attacker
41+
// to forge messages at will.
42+
func New(key *[32]byte) *MAC {
43+
return &MAC{
44+
mac: newMAC(key),
45+
finalized: false,
46+
}
47+
}
48+
49+
// MAC is an io.Writer computing an authentication tag
50+
// of the data written to it.
51+
//
52+
// MAC cannot be used like common hash.Hash implementations,
53+
// because using a poly1305 key twice breaks its security.
54+
// Therefore writing data to a running MAC after calling
55+
// Sum causes it to panic.
56+
type MAC struct {
57+
mac // platform-dependent implementation
58+
59+
finalized bool
60+
}
61+
62+
// Size returns the number of bytes Sum will return.
63+
func (h *MAC) Size() int { return TagSize }
64+
65+
// Write adds more data to the running message authentication code.
66+
// It never returns an error.
67+
//
68+
// It must not be called after the first call of Sum.
69+
func (h *MAC) Write(p []byte) (n int, err error) {
70+
if h.finalized {
71+
panic("poly1305: write to MAC after Sum")
72+
}
73+
return h.mac.Write(p)
74+
}
75+
76+
// Sum computes the authenticator of all data written to the
77+
// message authentication code.
78+
func (h *MAC) Sum(b []byte) []byte {
79+
var mac [TagSize]byte
80+
h.mac.Sum(&mac)
81+
h.finalized = true
82+
return append(b, mac[:]...)
83+
}

poly1305/poly1305_test.go

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,50 @@ func TestSumUnaligned(t *testing.T) { testSum(t, true, Sum) }
100100
func TestSumGeneric(t *testing.T) { testSum(t, false, sumGeneric) }
101101
func TestSumGenericUnaligned(t *testing.T) { testSum(t, true, sumGeneric) }
102102

103-
func benchmark(b *testing.B, size int, unaligned bool) {
103+
func TestWriteGeneric(t *testing.T) { testWriteGeneric(t, false) }
104+
func TestWriteGenericUnaligned(t *testing.T) { testWriteGeneric(t, true) }
105+
func TestWrite(t *testing.T) { testWrite(t, false) }
106+
func TestWriteUnaligned(t *testing.T) { testWrite(t, true) }
107+
108+
func testWriteGeneric(t *testing.T, unaligned bool) {
109+
for i, v := range testData {
110+
key := v.Key()
111+
input := v.Input()
112+
var out [16]byte
113+
114+
if unaligned {
115+
input = unalignBytes(input)
116+
}
117+
h := newMACGeneric(&key)
118+
h.Write(input[:len(input)/2])
119+
h.Write(input[len(input)/2:])
120+
h.Sum(&out)
121+
if tag := v.Tag(); out != tag {
122+
t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
123+
}
124+
}
125+
}
126+
127+
func testWrite(t *testing.T, unaligned bool) {
128+
for i, v := range testData {
129+
key := v.Key()
130+
input := v.Input()
131+
var out [16]byte
132+
133+
if unaligned {
134+
input = unalignBytes(input)
135+
}
136+
h := New(&key)
137+
h.Write(input[:len(input)/2])
138+
h.Write(input[len(input)/2:])
139+
h.Sum(out[:0])
140+
if tag := v.Tag(); out != tag {
141+
t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
142+
}
143+
}
144+
}
145+
146+
func benchmarkSum(b *testing.B, size int, unaligned bool) {
104147
var out [16]byte
105148
var key [32]byte
106149
in := make([]byte, size)
@@ -114,11 +157,33 @@ func benchmark(b *testing.B, size int, unaligned bool) {
114157
}
115158
}
116159

117-
func Benchmark64(b *testing.B) { benchmark(b, 64, false) }
118-
func Benchmark1K(b *testing.B) { benchmark(b, 1024, false) }
119-
func Benchmark64Unaligned(b *testing.B) { benchmark(b, 64, true) }
120-
func Benchmark1KUnaligned(b *testing.B) { benchmark(b, 1024, true) }
121-
func Benchmark2M(b *testing.B) { benchmark(b, 2097152, true) }
160+
func benchmarkWrite(b *testing.B, size int, unaligned bool) {
161+
var key [32]byte
162+
h := New(&key)
163+
in := make([]byte, size)
164+
if unaligned {
165+
in = unalignBytes(in)
166+
}
167+
b.SetBytes(int64(len(in)))
168+
b.ResetTimer()
169+
for i := 0; i < b.N; i++ {
170+
h.Write(in)
171+
}
172+
}
173+
174+
func Benchmark64(b *testing.B) { benchmarkSum(b, 64, false) }
175+
func Benchmark1K(b *testing.B) { benchmarkSum(b, 1024, false) }
176+
func Benchmark2M(b *testing.B) { benchmarkSum(b, 2*1024*1024, false) }
177+
func Benchmark64Unaligned(b *testing.B) { benchmarkSum(b, 64, true) }
178+
func Benchmark1KUnaligned(b *testing.B) { benchmarkSum(b, 1024, true) }
179+
func Benchmark2MUnaligned(b *testing.B) { benchmarkSum(b, 2*1024*1024, true) }
180+
181+
func BenchmarkWrite64(b *testing.B) { benchmarkWrite(b, 64, false) }
182+
func BenchmarkWrite1K(b *testing.B) { benchmarkWrite(b, 1024, false) }
183+
func BenchmarkWrite2M(b *testing.B) { benchmarkWrite(b, 2*1024*1024, false) }
184+
func BenchmarkWrite64Unaligned(b *testing.B) { benchmarkWrite(b, 64, true) }
185+
func BenchmarkWrite1KUnaligned(b *testing.B) { benchmarkWrite(b, 1024, true) }
186+
func BenchmarkWrite2MUnaligned(b *testing.B) { benchmarkWrite(b, 2*1024*1024, true) }
122187

123188
func unalignBytes(in []byte) []byte {
124189
out := make([]byte, len(in)+1)

poly1305/sum_amd64.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,63 @@
66

77
package poly1305
88

9-
// This function is implemented in sum_amd64.s
109
//go:noescape
11-
func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]byte)
10+
func initialize(state *[7]uint64, key *[32]byte)
11+
12+
//go:noescape
13+
func update(state *[7]uint64, msg []byte)
14+
15+
//go:noescape
16+
func finalize(tag *[TagSize]byte, state *[7]uint64)
1217

1318
// Sum generates an authenticator for m using a one-time key and puts the
1419
// 16-byte result into out. Authenticating two different messages with the same
1520
// key allows an attacker to forge messages at will.
1621
func Sum(out *[16]byte, m []byte, key *[32]byte) {
17-
var mPtr *byte
18-
if len(m) > 0 {
19-
mPtr = &m[0]
22+
h := newMAC(key)
23+
h.Write(m)
24+
h.Sum(out)
25+
}
26+
27+
func newMAC(key *[32]byte) (h mac) {
28+
initialize(&h.state, key)
29+
return
30+
}
31+
32+
type mac struct {
33+
state [7]uint64 // := uint64{ h0, h1, h2, r0, r1, pad0, pad1 }
34+
35+
buffer [TagSize]byte
36+
offset int
37+
}
38+
39+
func (h *mac) Write(p []byte) (n int, err error) {
40+
n = len(p)
41+
if h.offset > 0 {
42+
remaining := TagSize - h.offset
43+
if n < remaining {
44+
h.offset += copy(h.buffer[h.offset:], p)
45+
return n, nil
46+
}
47+
copy(h.buffer[h.offset:], p[:remaining])
48+
p = p[remaining:]
49+
h.offset = 0
50+
update(&h.state, h.buffer[:])
51+
}
52+
if nn := len(p) - (len(p) % TagSize); nn > 0 {
53+
update(&h.state, p[:nn])
54+
p = p[nn:]
55+
}
56+
if len(p) > 0 {
57+
h.offset += copy(h.buffer[h.offset:], p)
58+
}
59+
return n, nil
60+
}
61+
62+
func (h *mac) Sum(out *[16]byte) {
63+
state := h.state
64+
if h.offset > 0 {
65+
update(&state, h.buffer[:h.offset])
2066
}
21-
poly1305(out, mPtr, uint64(len(m)), key)
67+
finalize(out, &state)
2268
}

poly1305/sum_amd64.s

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,17 @@ DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
5858
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
5959
GLOBL ·poly1305Mask<>(SB), RODATA, $16
6060

61-
// func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]key)
62-
TEXT ·poly1305(SB), $0-32
63-
MOVQ out+0(FP), DI
64-
MOVQ m+8(FP), SI
65-
MOVQ mlen+16(FP), R15
66-
MOVQ key+24(FP), AX
67-
68-
MOVQ 0(AX), R11
69-
MOVQ 8(AX), R12
70-
ANDQ ·poly1305Mask<>(SB), R11 // r0
71-
ANDQ ·poly1305Mask<>+8(SB), R12 // r1
72-
XORQ R8, R8 // h0
73-
XORQ R9, R9 // h1
74-
XORQ R10, R10 // h2
61+
// func update(state *[7]uint64, msg []byte)
62+
TEXT ·update(SB), $0-32
63+
MOVQ state+0(FP), DI
64+
MOVQ msg_base+8(FP), SI
65+
MOVQ msg_len+16(FP), R15
66+
67+
MOVQ 0(DI), R8 // h0
68+
MOVQ 8(DI), R9 // h1
69+
MOVQ 16(DI), R10 // h2
70+
MOVQ 24(DI), R11 // r0
71+
MOVQ 32(DI), R12 // r1
7572

7673
CMPQ R15, $16
7774
JB bytes_between_0_and_15
@@ -109,16 +106,42 @@ flush_buffer:
109106
JMP multiply
110107

111108
done:
112-
MOVQ R8, AX
113-
MOVQ R9, BX
109+
MOVQ R8, 0(DI)
110+
MOVQ R9, 8(DI)
111+
MOVQ R10, 16(DI)
112+
RET
113+
114+
// func initialize(state *[7]uint64, key *[32]byte)
115+
TEXT ·initialize(SB), $0-16
116+
MOVQ state+0(FP), DI
117+
MOVQ key+8(FP), SI
118+
119+
// state[0...7] is initialized with zero
120+
MOVOU 0(SI), X0
121+
MOVOU 16(SI), X1
122+
MOVOU ·poly1305Mask<>(SB), X2
123+
PAND X2, X0
124+
MOVOU X0, 24(DI)
125+
MOVOU X1, 40(DI)
126+
RET
127+
128+
// func finalize(tag *[TagSize]byte, state *[7]uint64)
129+
TEXT ·finalize(SB), $0-16
130+
MOVQ tag+0(FP), DI
131+
MOVQ state+8(FP), SI
132+
133+
MOVQ 0(SI), AX
134+
MOVQ 8(SI), BX
135+
MOVQ 16(SI), CX
136+
MOVQ AX, R8
137+
MOVQ BX, R9
114138
SUBQ $0xFFFFFFFFFFFFFFFB, AX
115139
SBBQ $0xFFFFFFFFFFFFFFFF, BX
116-
SBBQ $3, R10
140+
SBBQ $3, CX
117141
CMOVQCS R8, AX
118142
CMOVQCS R9, BX
119-
MOVQ key+24(FP), R8
120-
ADDQ 16(R8), AX
121-
ADCQ 24(R8), BX
143+
ADDQ 40(SI), AX
144+
ADCQ 48(SI), BX
122145

123146
MOVQ AX, 0(DI)
124147
MOVQ BX, 8(DI)

0 commit comments

Comments
 (0)