Skip to content

Commit 112cdc2

Browse files
committed
hash: implement Clone() and speed up NewShaX
1 parent 3a8d069 commit 112cdc2

File tree

2 files changed

+66
-14
lines changed

2 files changed

+66
-14
lines changed

xcrypto/hash.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ func SHA512(p []byte) (sum [64]byte) {
9292
return
9393
}
9494

95+
// cloneHash is an interface that defines a Clone method.
96+
//
97+
// hash.CloneHash will probably be added in Go 1.25, see https://golang.org/issue/69521,
98+
// but we need it now.
99+
type cloneHash interface {
100+
hash.Hash
101+
// Clone returns a separate Hash instance with the same state as h.
102+
Clone() hash.Hash
103+
}
104+
105+
var _ hash.Hash = (*evpHash)(nil)
106+
var _ cloneHash = (*evpHash)(nil)
107+
108+
// evpHash implements generic hash methods.
95109
type evpHash struct {
96110
ctx unsafe.Pointer
97111
// ctx2 is used in evpHash.sum to avoid changing
@@ -107,12 +121,7 @@ type evpHash struct {
107121
}
108122

109123
func newEvpHash(init func(ctx unsafe.Pointer) C.int, update func(ctx unsafe.Pointer, data []byte) C.int, final func(ctx unsafe.Pointer, digest []byte) C.int, ctxSize, blockSize, size int) *evpHash {
110-
ctx := C.malloc(C.size_t(ctxSize))
111-
ctx2 := C.malloc(C.size_t(ctxSize))
112-
init(ctx)
113124
h := &evpHash{
114-
ctx: ctx,
115-
ctx2: ctx2,
116125
init: init,
117126
update: update,
118127
final: final,
@@ -125,8 +134,24 @@ func newEvpHash(init func(ctx unsafe.Pointer) C.int, update func(ctx unsafe.Poin
125134
}
126135

127136
func (h *evpHash) finalize() {
128-
C.free(h.ctx)
129-
C.free(h.ctx2)
137+
if h.ctx != nil {
138+
C.free(h.ctx)
139+
}
140+
if h.ctx2 != nil {
141+
C.free(h.ctx2)
142+
}
143+
}
144+
145+
func (h *evpHash) initialize() {
146+
if h.ctx == nil {
147+
h.ctx = C.malloc(C.size_t(h.ctxSize))
148+
h.ctx2 = C.malloc(C.size_t(h.ctxSize))
149+
if h.init(h.ctx) != 1 {
150+
C.free(h.ctx)
151+
C.free(h.ctx2)
152+
panic("commoncrypto: initialization failed")
153+
}
154+
}
130155
}
131156

132157
func (h *evpHash) Reset() {
@@ -137,6 +162,7 @@ func (h *evpHash) Reset() {
137162
}
138163

139164
func (h *evpHash) Write(p []byte) (int, error) {
165+
h.initialize()
140166
if len(p) > 0 {
141167
// Use a local variable to prevent the compiler from misinterpreting the pointer
142168
data := p
@@ -149,6 +175,7 @@ func (h *evpHash) Write(p []byte) (int, error) {
149175
}
150176

151177
func (h *evpHash) WriteString(s string) (int, error) {
178+
h.initialize()
152179
if len(s) > 0 {
153180
data := []byte(s)
154181
if h.update(h.ctx, data) != 1 {
@@ -160,6 +187,7 @@ func (h *evpHash) WriteString(s string) (int, error) {
160187
}
161188

162189
func (h *evpHash) WriteByte(c byte) error {
190+
h.initialize()
163191
if h.update(h.ctx, []byte{c}) != 1 {
164192
return errors.New("commoncrypto: Update function failed")
165193
}
@@ -175,12 +203,35 @@ func (h *evpHash) BlockSize() int {
175203
}
176204

177205
func (h *evpHash) Sum(b []byte) []byte {
206+
h.initialize()
178207
digest := make([]byte, h.size)
179208
C.memcpy(h.ctx2, h.ctx, C.size_t(h.ctxSize))
180209
h.final(h.ctx2, digest)
181210
return append(b, digest...)
182211
}
183212

213+
// Clone returns a new evpHash object that is a deep clone of itself.
214+
// The duplicate object contains all state and data contained in the
215+
// original object at the point of duplication.
216+
func (h *evpHash) Clone() hash.Hash {
217+
h.initialize()
218+
cloned := &evpHash{
219+
init: h.init,
220+
update: h.update,
221+
final: h.final,
222+
blockSize: h.blockSize,
223+
size: h.size,
224+
ctxSize: h.ctxSize,
225+
}
226+
cloned.ctx = C.malloc(C.size_t(h.ctxSize))
227+
cloned.ctx2 = C.malloc(C.size_t(h.ctxSize))
228+
C.memcpy(cloned.ctx, h.ctx, C.size_t(h.ctxSize))
229+
C.memcpy(cloned.ctx2, h.ctx2, C.size_t(h.ctxSize))
230+
runtime.SetFinalizer(cloned, (*evpHash).finalize)
231+
runtime.KeepAlive(h)
232+
return cloned
233+
}
234+
184235
type md4Hash struct {
185236
*evpHash
186237
}

xcrypto/hash_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,13 @@ func TestHash_Clone(t *testing.T) {
199199
t.Skip("not supported")
200200
}
201201
h := cryptoToHash(ch)()
202-
if _, ok := h.(encoding.BinaryMarshaler); !ok {
203-
t.Skip("not supported")
204-
}
205202
_, err := h.Write(msg)
206203
if err != nil {
207204
t.Fatal(err)
208205
}
209206
// We don't define an interface for the Clone method to avoid other
210207
// packages from depending on it. Use type assertion to call it.
211-
h2, err := h.(interface{ Clone() (hash.Hash, error) }).Clone()
212-
if err != nil {
213-
t.Fatal(err)
214-
}
208+
h2 := h.(interface{ Clone() hash.Hash }).Clone()
215209
h.Write(msg)
216210
h2.Write(msg)
217211
if actual, actual2 := h.Sum(nil), h2.Sum(nil); !bytes.Equal(actual, actual2) {
@@ -494,6 +488,13 @@ func BenchmarkSHA256(b *testing.B) {
494488
}
495489
}
496490

491+
func BenchmarkNewSHA256(b *testing.B) {
492+
b.ReportAllocs()
493+
for i := 0; i < b.N; i++ {
494+
xcrypto.NewSHA256()
495+
}
496+
}
497+
497498
// stubHash is a hash.Hash implementation that does nothing.
498499
type stubHash struct{}
499500

0 commit comments

Comments
 (0)