Skip to content

Commit 80ea76e

Browse files
FiloSottilegopherbot
authored andcommitted
sha3: fix padding for long cSHAKE parameters
We used to compute the incorrect value if len(initBlock) % rate == 0. Also, add a test vector for golang/go#66232, confirmed to fail on GOARCH=386 without CL 570876. Fixes golang/go#69169 Change-Id: I3f2400926fca111dd0ca1327d6b5975e51b28f96 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/616576 Reviewed-by: Andrew Ekstedt <andrew.ekstedt@gmail.com> Reviewed-by: Daniel McCarney <daniel@binaryparadox.net> Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org>
1 parent c17aa50 commit 80ea76e

File tree

2 files changed

+134
-22
lines changed

2 files changed

+134
-22
lines changed

sha3/sha3_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"encoding/json"
1818
"fmt"
1919
"hash"
20+
"io"
2021
"math/rand"
2122
"os"
2223
"strings"
@@ -375,6 +376,116 @@ func TestClone(t *testing.T) {
375376
}
376377
}
377378

379+
func TestCSHAKEAccumulated(t *testing.T) {
380+
// Generated with pycryptodome@3.20.0
381+
//
382+
// from Crypto.Hash import cSHAKE128
383+
// rng = cSHAKE128.new()
384+
// acc = cSHAKE128.new()
385+
// for n in range(200):
386+
// N = rng.read(n)
387+
// for s in range(200):
388+
// S = rng.read(s)
389+
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
390+
// c.update(rng.read(100))
391+
// acc.update(c.read(200))
392+
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
393+
// c.update(rng.read(168))
394+
// acc.update(c.read(200))
395+
// c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
396+
// c.update(rng.read(200))
397+
// acc.update(c.read(200))
398+
// print(acc.read(32).hex())
399+
//
400+
// and with @noble/hashes@v1.5.0
401+
//
402+
// import { bytesToHex } from "@noble/hashes/utils";
403+
// import { cshake128 } from "@noble/hashes/sha3-addons";
404+
// const rng = cshake128.create();
405+
// const acc = cshake128.create();
406+
// for (let n = 0; n < 200; n++) {
407+
// const N = rng.xof(n);
408+
// for (let s = 0; s < 200; s++) {
409+
// const S = rng.xof(s);
410+
// let c = cshake128.create({ NISTfn: N, personalization: S });
411+
// c.update(rng.xof(100));
412+
// acc.update(c.xof(200));
413+
// c = cshake128.create({ NISTfn: N, personalization: S });
414+
// c.update(rng.xof(168));
415+
// acc.update(c.xof(200));
416+
// c = cshake128.create({ NISTfn: N, personalization: S });
417+
// c.update(rng.xof(200));
418+
// acc.update(c.xof(200));
419+
// }
420+
// }
421+
// console.log(bytesToHex(acc.xof(32)));
422+
//
423+
t.Run("cSHAKE128", func(t *testing.T) {
424+
testCSHAKEAccumulated(t, NewCShake128, rate128,
425+
"bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252")
426+
})
427+
t.Run("cSHAKE256", func(t *testing.T) {
428+
testCSHAKEAccumulated(t, NewCShake256, rate256,
429+
"0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef")
430+
})
431+
}
432+
433+
func testCSHAKEAccumulated(t *testing.T, newCShake func(N, S []byte) ShakeHash, rate int64, exp string) {
434+
rnd := newCShake(nil, nil)
435+
acc := newCShake(nil, nil)
436+
for n := 0; n < 200; n++ {
437+
N := make([]byte, n)
438+
rnd.Read(N)
439+
for s := 0; s < 200; s++ {
440+
S := make([]byte, s)
441+
rnd.Read(S)
442+
443+
c := newCShake(N, S)
444+
io.CopyN(c, rnd, 100 /* < rate */)
445+
io.CopyN(acc, c, 200)
446+
447+
c.Reset()
448+
io.CopyN(c, rnd, rate)
449+
io.CopyN(acc, c, 200)
450+
451+
c.Reset()
452+
io.CopyN(c, rnd, 200 /* > rate */)
453+
io.CopyN(acc, c, 200)
454+
}
455+
}
456+
if got := hex.EncodeToString(acc.Sum(nil)[:32]); got != exp {
457+
t.Errorf("got %s, want %s", got, exp)
458+
}
459+
}
460+
461+
func TestCSHAKELargeS(t *testing.T) {
462+
if testing.Short() {
463+
t.Skip("skipping test in short mode.")
464+
}
465+
466+
// See https://go.dev/issue/66232.
467+
const s = (1<<32)/8 + 1000 // s * 8 > 2^32
468+
S := make([]byte, s)
469+
rnd := NewShake128()
470+
rnd.Read(S)
471+
c := NewCShake128(nil, S)
472+
io.CopyN(c, rnd, 1000)
473+
474+
// Generated with pycryptodome@3.20.0
475+
//
476+
// from Crypto.Hash import cSHAKE128
477+
// rng = cSHAKE128.new()
478+
// S = rng.read(536871912)
479+
// c = cSHAKE128.new(custom=S)
480+
// c.update(rng.read(1000))
481+
// print(c.read(32).hex())
482+
//
483+
exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0"
484+
if got := hex.EncodeToString(c.Sum(nil)); got != exp {
485+
t.Errorf("got %s, want %s", got, exp)
486+
}
487+
}
488+
378489
// BenchmarkPermutationFunction measures the speed of the permutation function
379490
// with no input data.
380491
func BenchmarkPermutationFunction(b *testing.B) {

sha3/shake.go

+23-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"encoding/binary"
2020
"hash"
2121
"io"
22+
"math/bits"
2223
)
2324

2425
// ShakeHash defines the interface to hash functions that support
@@ -58,33 +59,33 @@ const (
5859
rate256 = 136
5960
)
6061

61-
func bytepad(input []byte, w int) []byte {
62-
// leftEncode always returns max 9 bytes
63-
buf := make([]byte, 0, 9+len(input)+w)
64-
buf = append(buf, leftEncode(uint64(w))...)
65-
buf = append(buf, input...)
66-
padlen := w - (len(buf) % w)
67-
return append(buf, make([]byte, padlen)...)
68-
}
69-
70-
func leftEncode(value uint64) []byte {
71-
var b [9]byte
72-
binary.BigEndian.PutUint64(b[1:], value)
73-
// Trim all but last leading zero bytes
74-
i := byte(1)
75-
for i < 8 && b[i] == 0 {
76-
i++
62+
func bytepad(data []byte, rate int) []byte {
63+
out := make([]byte, 0, 9+len(data)+rate-1)
64+
out = append(out, leftEncode(uint64(rate))...)
65+
out = append(out, data...)
66+
if padlen := rate - len(out)%rate; padlen < rate {
67+
out = append(out, make([]byte, padlen)...)
7768
}
78-
// Prepend number of encoded bytes
79-
b[i-1] = 9 - i
80-
return b[i-1:]
69+
return out
70+
}
71+
72+
func leftEncode(x uint64) []byte {
73+
// Let n be the smallest positive integer for which 2^(8n) > x.
74+
n := (bits.Len64(x) + 7) / 8
75+
if n == 0 {
76+
n = 1
77+
}
78+
// Return n || x with n as a byte and x an n bytes in big-endian order.
79+
b := make([]byte, 9)
80+
binary.BigEndian.PutUint64(b[1:], x)
81+
b = b[9-n-1:]
82+
b[0] = byte(n)
83+
return b
8184
}
8285

8386
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
8487
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
85-
86-
// leftEncode returns max 9 bytes
87-
c.initBlock = make([]byte, 0, 9*2+len(N)+len(S))
88+
c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes
8889
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...)
8990
c.initBlock = append(c.initBlock, N...)
9091
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...)

0 commit comments

Comments
 (0)