mirror of https://github.com/go-gitea/gitea.git
parent
1b85b248e4
commit
8d9d6aa903
@ -0,0 +1,285 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package argon2 implements the key derivation function Argon2.
|
||||
// Argon2 was selected as the winner of the Password Hashing Competition and can
|
||||
// be used to derive cryptographic keys from passwords.
|
||||
//
|
||||
// For a detailed specification of Argon2 see [1].
|
||||
//
|
||||
// If you aren't sure which function you need, use Argon2id (IDKey) and
|
||||
// the parameter recommendations for your scenario.
|
||||
//
|
||||
//
|
||||
// Argon2i
|
||||
//
|
||||
// Argon2i (implemented by Key) is the side-channel resistant version of Argon2.
|
||||
// It uses data-independent memory access, which is preferred for password
|
||||
// hashing and password-based key derivation. Argon2i requires more passes over
|
||||
// memory than Argon2id to protect from trade-off attacks. The recommended
|
||||
// parameters (taken from [2]) for non-interactive operations are time=3 and to
|
||||
// use the maximum available memory.
|
||||
//
|
||||
//
|
||||
// Argon2id
|
||||
//
|
||||
// Argon2id (implemented by IDKey) is a hybrid version of Argon2 combining
|
||||
// Argon2i and Argon2d. It uses data-independent memory access for the first
|
||||
// half of the first iteration over the memory and data-dependent memory access
|
||||
// for the rest. Argon2id is side-channel resistant and provides better brute-
|
||||
// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
|
||||
// parameters for non-interactive operations (taken from [2]) are time=1 and to
|
||||
// use the maximum available memory.
|
||||
//
|
||||
// [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
|
||||
// [2] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3
|
||||
package argon2 |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"sync" |
||||
|
||||
"golang.org/x/crypto/blake2b" |
||||
) |
||||
|
||||
// The Argon2 version implemented by this package.
|
||||
const Version = 0x13 |
||||
|
||||
const ( |
||||
argon2d = iota |
||||
argon2i |
||||
argon2id |
||||
) |
||||
|
||||
// Key derives a key from the password, salt, and cost parameters using Argon2i
|
||||
// returning a byte slice of length keyLen that can be used as cryptographic
|
||||
// key. The CPU cost and parallelism degree must be greater than zero.
|
||||
//
|
||||
// For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||
// 32-byte key) by doing:
|
||||
//
|
||||
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number.
|
||||
// If using that amount of memory (32 MB) is not possible in some contexts then
|
||||
// the time parameter can be increased to compensate.
|
||||
//
|
||||
// The time parameter specifies the number of passes over the memory and the
|
||||
// memory parameter specifies the size of the memory in KiB. For example
|
||||
// memory=32*1024 sets the memory cost to ~32 MB. The number of threads can be
|
||||
// adjusted to the number of available CPUs. The cost parameters should be
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// good random salt.
|
||||
func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { |
||||
return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen) |
||||
} |
||||
|
||||
// IDKey derives a key from the password, salt, and cost parameters using
|
||||
// Argon2id returning a byte slice of length keyLen that can be used as
|
||||
// cryptographic key. The CPU cost and parallelism degree must be greater than
|
||||
// zero.
|
||||
//
|
||||
// For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||
// 32-byte key) by doing:
|
||||
//
|
||||
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
|
||||
//
|
||||
// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number.
|
||||
// If using that amount of memory (64 MB) is not possible in some contexts then
|
||||
// the time parameter can be increased to compensate.
|
||||
//
|
||||
// The time parameter specifies the number of passes over the memory and the
|
||||
// memory parameter specifies the size of the memory in KiB. For example
|
||||
// memory=64*1024 sets the memory cost to ~64 MB. The number of threads can be
|
||||
// adjusted to the numbers of available CPUs. The cost parameters should be
|
||||
// increased as memory latency and CPU parallelism increases. Remember to get a
|
||||
// good random salt.
|
||||
func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { |
||||
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen) |
||||
} |
||||
|
||||
func deriveKey(mode int, password, salt, secret, data []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { |
||||
if time < 1 { |
||||
panic("argon2: number of rounds too small") |
||||
} |
||||
if threads < 1 { |
||||
panic("argon2: parallelism degree too low") |
||||
} |
||||
h0 := initHash(password, salt, secret, data, time, memory, uint32(threads), keyLen, mode) |
||||
|
||||
memory = memory / (syncPoints * uint32(threads)) * (syncPoints * uint32(threads)) |
||||
if memory < 2*syncPoints*uint32(threads) { |
||||
memory = 2 * syncPoints * uint32(threads) |
||||
} |
||||
B := initBlocks(&h0, memory, uint32(threads)) |
||||
processBlocks(B, time, memory, uint32(threads), mode) |
||||
return extractKey(B, memory, uint32(threads), keyLen) |
||||
} |
||||
|
||||
const ( |
||||
blockLength = 128 |
||||
syncPoints = 4 |
||||
) |
||||
|
||||
type block [blockLength]uint64 |
||||
|
||||
func initHash(password, salt, key, data []byte, time, memory, threads, keyLen uint32, mode int) [blake2b.Size + 8]byte { |
||||
var ( |
||||
h0 [blake2b.Size + 8]byte |
||||
params [24]byte |
||||
tmp [4]byte |
||||
) |
||||
|
||||
b2, _ := blake2b.New512(nil) |
||||
binary.LittleEndian.PutUint32(params[0:4], threads) |
||||
binary.LittleEndian.PutUint32(params[4:8], keyLen) |
||||
binary.LittleEndian.PutUint32(params[8:12], memory) |
||||
binary.LittleEndian.PutUint32(params[12:16], time) |
||||
binary.LittleEndian.PutUint32(params[16:20], uint32(Version)) |
||||
binary.LittleEndian.PutUint32(params[20:24], uint32(mode)) |
||||
b2.Write(params[:]) |
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(password))) |
||||
b2.Write(tmp[:]) |
||||
b2.Write(password) |
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(salt))) |
||||
b2.Write(tmp[:]) |
||||
b2.Write(salt) |
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(key))) |
||||
b2.Write(tmp[:]) |
||||
b2.Write(key) |
||||
binary.LittleEndian.PutUint32(tmp[:], uint32(len(data))) |
||||
b2.Write(tmp[:]) |
||||
b2.Write(data) |
||||
b2.Sum(h0[:0]) |
||||
return h0 |
||||
} |
||||
|
||||
func initBlocks(h0 *[blake2b.Size + 8]byte, memory, threads uint32) []block { |
||||
var block0 [1024]byte |
||||
B := make([]block, memory) |
||||
for lane := uint32(0); lane < threads; lane++ { |
||||
j := lane * (memory / threads) |
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size+4:], lane) |
||||
|
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size:], 0) |
||||
blake2bHash(block0[:], h0[:]) |
||||
for i := range B[j+0] { |
||||
B[j+0][i] = binary.LittleEndian.Uint64(block0[i*8:]) |
||||
} |
||||
|
||||
binary.LittleEndian.PutUint32(h0[blake2b.Size:], 1) |
||||
blake2bHash(block0[:], h0[:]) |
||||
for i := range B[j+1] { |
||||
B[j+1][i] = binary.LittleEndian.Uint64(block0[i*8:]) |
||||
} |
||||
} |
||||
return B |
||||
} |
||||
|
||||
func processBlocks(B []block, time, memory, threads uint32, mode int) { |
||||
lanes := memory / threads |
||||
segments := lanes / syncPoints |
||||
|
||||
processSegment := func(n, slice, lane uint32, wg *sync.WaitGroup) { |
||||
var addresses, in, zero block |
||||
if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) { |
||||
in[0] = uint64(n) |
||||
in[1] = uint64(lane) |
||||
in[2] = uint64(slice) |
||||
in[3] = uint64(memory) |
||||
in[4] = uint64(time) |
||||
in[5] = uint64(mode) |
||||
} |
||||
|
||||
index := uint32(0) |
||||
if n == 0 && slice == 0 { |
||||
index = 2 // we have already generated the first two blocks
|
||||
if mode == argon2i || mode == argon2id { |
||||
in[6]++ |
||||
processBlock(&addresses, &in, &zero) |
||||
processBlock(&addresses, &addresses, &zero) |
||||
} |
||||
} |
||||
|
||||
offset := lane*lanes + slice*segments + index |
||||
var random uint64 |
||||
for index < segments { |
||||
prev := offset - 1 |
||||
if index == 0 && slice == 0 { |
||||
prev += lanes // last block in lane
|
||||
} |
||||
if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) { |
||||
if index%blockLength == 0 { |
||||
in[6]++ |
||||
processBlock(&addresses, &in, &zero) |
||||
processBlock(&addresses, &addresses, &zero) |
||||
} |
||||
random = addresses[index%blockLength] |
||||
} else { |
||||
random = B[prev][0] |
||||
} |
||||
newOffset := indexAlpha(random, lanes, segments, threads, n, slice, lane, index) |
||||
processBlockXOR(&B[offset], &B[prev], &B[newOffset]) |
||||
index, offset = index+1, offset+1 |
||||
} |
||||
wg.Done() |
||||
} |
||||
|
||||
for n := uint32(0); n < time; n++ { |
||||
for slice := uint32(0); slice < syncPoints; slice++ { |
||||
var wg sync.WaitGroup |
||||
for lane := uint32(0); lane < threads; lane++ { |
||||
wg.Add(1) |
||||
go processSegment(n, slice, lane, &wg) |
||||
} |
||||
wg.Wait() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func extractKey(B []block, memory, threads, keyLen uint32) []byte { |
||||
lanes := memory / threads |
||||
for lane := uint32(0); lane < threads-1; lane++ { |
||||
for i, v := range B[(lane*lanes)+lanes-1] { |
||||
B[memory-1][i] ^= v |
||||
} |
||||
} |
||||
|
||||
var block [1024]byte |
||||
for i, v := range B[memory-1] { |
||||
binary.LittleEndian.PutUint64(block[i*8:], v) |
||||
} |
||||
key := make([]byte, keyLen) |
||||
blake2bHash(key, block[:]) |
||||
return key |
||||
} |
||||
|
||||
func indexAlpha(rand uint64, lanes, segments, threads, n, slice, lane, index uint32) uint32 { |
||||
refLane := uint32(rand>>32) % threads |
||||
if n == 0 && slice == 0 { |
||||
refLane = lane |
||||
} |
||||
m, s := 3*segments, ((slice+1)%syncPoints)*segments |
||||
if lane == refLane { |
||||
m += index |
||||
} |
||||
if n == 0 { |
||||
m, s = slice*segments, 0 |
||||
if slice == 0 || lane == refLane { |
||||
m += index |
||||
} |
||||
} |
||||
if index == 0 || lane == refLane { |
||||
m-- |
||||
} |
||||
return phi(rand, uint64(m), uint64(s), refLane, lanes) |
||||
} |
||||
|
||||
func phi(rand, m, s uint64, lane, lanes uint32) uint32 { |
||||
p := rand & 0xFFFFFFFF |
||||
p = (p * p) >> 32 |
||||
p = (p * m) >> 32 |
||||
return lane*lanes + uint32((s+m-(p+1))%uint64(lanes)) |
||||
} |
@ -0,0 +1,53 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package argon2 |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"hash" |
||||
|
||||
"golang.org/x/crypto/blake2b" |
||||
) |
||||
|
||||
// blake2bHash computes an arbitrary long hash value of in
|
||||
// and writes the hash to out.
|
||||
func blake2bHash(out []byte, in []byte) { |
||||
var b2 hash.Hash |
||||
if n := len(out); n < blake2b.Size { |
||||
b2, _ = blake2b.New(n, nil) |
||||
} else { |
||||
b2, _ = blake2b.New512(nil) |
||||
} |
||||
|
||||
var buffer [blake2b.Size]byte |
||||
binary.LittleEndian.PutUint32(buffer[:4], uint32(len(out))) |
||||
b2.Write(buffer[:4]) |
||||
b2.Write(in) |
||||
|
||||
if len(out) <= blake2b.Size { |
||||
b2.Sum(out[:0]) |
||||
return |
||||
} |
||||
|
||||
outLen := len(out) |
||||
b2.Sum(buffer[:0]) |
||||
b2.Reset() |
||||
copy(out, buffer[:32]) |
||||
out = out[32:] |
||||
for len(out) > blake2b.Size { |
||||
b2.Write(buffer[:]) |
||||
b2.Sum(buffer[:0]) |
||||
copy(out, buffer[:32]) |
||||
out = out[32:] |
||||
b2.Reset() |
||||
} |
||||
|
||||
if outLen%blake2b.Size > 0 { // outLen > 64
|
||||
r := ((outLen + 31) / 32) - 2 // โฯ /32โ-2
|
||||
b2, _ = blake2b.New(outLen-32*r, nil) |
||||
} |
||||
b2.Write(buffer[:]) |
||||
b2.Sum(out[:0]) |
||||
} |
@ -0,0 +1,60 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64,!gccgo,!appengine
|
||||
|
||||
package argon2 |
||||
|
||||
import "golang.org/x/sys/cpu" |
||||
|
||||
func init() { |
||||
useSSE4 = cpu.X86.HasSSE41 |
||||
} |
||||
|
||||
//go:noescape
|
||||
func mixBlocksSSE2(out, a, b, c *block) |
||||
|
||||
//go:noescape
|
||||
func xorBlocksSSE2(out, a, b, c *block) |
||||
|
||||
//go:noescape
|
||||
func blamkaSSE4(b *block) |
||||
|
||||
func processBlockSSE(out, in1, in2 *block, xor bool) { |
||||
var t block |
||||
mixBlocksSSE2(&t, in1, in2, &t) |
||||
if useSSE4 { |
||||
blamkaSSE4(&t) |
||||
} else { |
||||
for i := 0; i < blockLength; i += 16 { |
||||
blamkaGeneric( |
||||
&t[i+0], &t[i+1], &t[i+2], &t[i+3], |
||||
&t[i+4], &t[i+5], &t[i+6], &t[i+7], |
||||
&t[i+8], &t[i+9], &t[i+10], &t[i+11], |
||||
&t[i+12], &t[i+13], &t[i+14], &t[i+15], |
||||
) |
||||
} |
||||
for i := 0; i < blockLength/8; i += 2 { |
||||
blamkaGeneric( |
||||
&t[i], &t[i+1], &t[16+i], &t[16+i+1], |
||||
&t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1], |
||||
&t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1], |
||||
&t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1], |
||||
) |
||||
} |
||||
} |
||||
if xor { |
||||
xorBlocksSSE2(out, in1, in2, &t) |
||||
} else { |
||||
mixBlocksSSE2(out, in1, in2, &t) |
||||
} |
||||
} |
||||
|
||||
func processBlock(out, in1, in2 *block) { |
||||
processBlockSSE(out, in1, in2, false) |
||||
} |
||||
|
||||
func processBlockXOR(out, in1, in2 *block) { |
||||
processBlockSSE(out, in1, in2, true) |
||||
} |
@ -0,0 +1,243 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved. |
||||
// Use of this source code is governed by a BSD-style |
||||
// license that can be found in the LICENSE file. |
||||
|
||||
// +build amd64,!gccgo,!appengine |
||||
|
||||
#include "textflag.h" |
||||
|
||||
DATA ยทc40<>+0x00(SB)/8, $0x0201000706050403 |
||||
DATA ยทc40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b |
||||
GLOBL ยทc40<>(SB), (NOPTR+RODATA), $16 |
||||
|
||||
DATA ยทc48<>+0x00(SB)/8, $0x0100070605040302 |
||||
DATA ยทc48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a |
||||
GLOBL ยทc48<>(SB), (NOPTR+RODATA), $16 |
||||
|
||||
#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ |
||||
MOVO v4, t1; \
|
||||
MOVO v5, v4; \
|
||||
MOVO t1, v5; \
|
||||
MOVO v6, t1; \
|
||||
PUNPCKLQDQ v6, t2; \
|
||||
PUNPCKHQDQ v7, v6; \
|
||||
PUNPCKHQDQ t2, v6; \
|
||||
PUNPCKLQDQ v7, t2; \
|
||||
MOVO t1, v7; \
|
||||
MOVO v2, t1; \
|
||||
PUNPCKHQDQ t2, v7; \
|
||||
PUNPCKLQDQ v3, t2; \
|
||||
PUNPCKHQDQ t2, v2; \
|
||||
PUNPCKLQDQ t1, t2; \
|
||||
PUNPCKHQDQ t2, v3 |
||||
|
||||
#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ |
||||
MOVO v4, t1; \
|
||||
MOVO v5, v4; \
|
||||
MOVO t1, v5; \
|
||||
MOVO v2, t1; \
|
||||
PUNPCKLQDQ v2, t2; \
|
||||
PUNPCKHQDQ v3, v2; \
|
||||
PUNPCKHQDQ t2, v2; \
|
||||
PUNPCKLQDQ v3, t2; \
|
||||
MOVO t1, v3; \
|
||||
MOVO v6, t1; \
|
||||
PUNPCKHQDQ t2, v3; \
|
||||
PUNPCKLQDQ v7, t2; \
|
||||
PUNPCKHQDQ t2, v6; \
|
||||
PUNPCKLQDQ t1, t2; \
|
||||
PUNPCKHQDQ t2, v7 |
||||
|
||||
#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, c40, c48) \ |
||||
MOVO v0, t0; \
|
||||
PMULULQ v2, t0; \
|
||||
PADDQ v2, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PXOR v0, v6; \
|
||||
PSHUFD $0xB1, v6, v6; \
|
||||
MOVO v4, t0; \
|
||||
PMULULQ v6, t0; \
|
||||
PADDQ v6, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PXOR v4, v2; \
|
||||
PSHUFB c40, v2; \
|
||||
MOVO v0, t0; \
|
||||
PMULULQ v2, t0; \
|
||||
PADDQ v2, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PADDQ t0, v0; \
|
||||
PXOR v0, v6; \
|
||||
PSHUFB c48, v6; \
|
||||
MOVO v4, t0; \
|
||||
PMULULQ v6, t0; \
|
||||
PADDQ v6, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PADDQ t0, v4; \
|
||||
PXOR v4, v2; \
|
||||
MOVO v2, t0; \
|
||||
PADDQ v2, t0; \
|
||||
PSRLQ $63, v2; \
|
||||
PXOR t0, v2; \
|
||||
MOVO v1, t0; \
|
||||
PMULULQ v3, t0; \
|
||||
PADDQ v3, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PXOR v1, v7; \
|
||||
PSHUFD $0xB1, v7, v7; \
|
||||
MOVO v5, t0; \
|
||||
PMULULQ v7, t0; \
|
||||
PADDQ v7, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PXOR v5, v3; \
|
||||
PSHUFB c40, v3; \
|
||||
MOVO v1, t0; \
|
||||
PMULULQ v3, t0; \
|
||||
PADDQ v3, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PADDQ t0, v1; \
|
||||
PXOR v1, v7; \
|
||||
PSHUFB c48, v7; \
|
||||
MOVO v5, t0; \
|
||||
PMULULQ v7, t0; \
|
||||
PADDQ v7, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PADDQ t0, v5; \
|
||||
PXOR v5, v3; \
|
||||
MOVO v3, t0; \
|
||||
PADDQ v3, t0; \
|
||||
PSRLQ $63, v3; \
|
||||
PXOR t0, v3 |
||||
|
||||
#define LOAD_MSG_0(block, off) \ |
||||
MOVOU 8*(off+0)(block), X0; \
|
||||
MOVOU 8*(off+2)(block), X1; \
|
||||
MOVOU 8*(off+4)(block), X2; \
|
||||
MOVOU 8*(off+6)(block), X3; \
|
||||
MOVOU 8*(off+8)(block), X4; \
|
||||
MOVOU 8*(off+10)(block), X5; \
|
||||
MOVOU 8*(off+12)(block), X6; \
|
||||
MOVOU 8*(off+14)(block), X7 |
||||
|
||||
#define STORE_MSG_0(block, off) \ |
||||
MOVOU X0, 8*(off+0)(block); \
|
||||
MOVOU X1, 8*(off+2)(block); \
|
||||
MOVOU X2, 8*(off+4)(block); \
|
||||
MOVOU X3, 8*(off+6)(block); \
|
||||
MOVOU X4, 8*(off+8)(block); \
|
||||
MOVOU X5, 8*(off+10)(block); \
|
||||
MOVOU X6, 8*(off+12)(block); \
|
||||
MOVOU X7, 8*(off+14)(block) |
||||
|
||||
#define LOAD_MSG_1(block, off) \ |
||||
MOVOU 8*off+0*8(block), X0; \
|
||||
MOVOU 8*off+16*8(block), X1; \
|
||||
MOVOU 8*off+32*8(block), X2; \
|
||||
MOVOU 8*off+48*8(block), X3; \
|
||||
MOVOU 8*off+64*8(block), X4; \
|
||||
MOVOU 8*off+80*8(block), X5; \
|
||||
MOVOU 8*off+96*8(block), X6; \
|
||||
MOVOU 8*off+112*8(block), X7 |
||||
|
||||
#define STORE_MSG_1(block, off) \ |
||||
MOVOU X0, 8*off+0*8(block); \
|
||||
MOVOU X1, 8*off+16*8(block); \
|
||||
MOVOU X2, 8*off+32*8(block); \
|
||||
MOVOU X3, 8*off+48*8(block); \
|
||||
MOVOU X4, 8*off+64*8(block); \
|
||||
MOVOU X5, 8*off+80*8(block); \
|
||||
MOVOU X6, 8*off+96*8(block); \
|
||||
MOVOU X7, 8*off+112*8(block) |
||||
|
||||
#define BLAMKA_ROUND_0(block, off, t0, t1, c40, c48) \ |
||||
LOAD_MSG_0(block, off); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
STORE_MSG_0(block, off) |
||||
|
||||
#define BLAMKA_ROUND_1(block, off, t0, t1, c40, c48) \ |
||||
LOAD_MSG_1(block, off); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \
|
||||
SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \
|
||||
STORE_MSG_1(block, off) |
||||
|
||||
// func blamkaSSE4(b *block) |
||||
TEXT ยทblamkaSSE4(SB), 4, $0-8 |
||||
MOVQ b+0(FP), AX |
||||
|
||||
MOVOU ยทc40<>(SB), X10 |
||||
MOVOU ยทc48<>(SB), X11 |
||||
|
||||
BLAMKA_ROUND_0(AX, 0, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 16, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 32, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 48, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 64, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 80, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 96, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_0(AX, 112, X8, X9, X10, X11) |
||||
|
||||
BLAMKA_ROUND_1(AX, 0, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 2, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 4, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 6, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 8, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 10, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 12, X8, X9, X10, X11) |
||||
BLAMKA_ROUND_1(AX, 14, X8, X9, X10, X11) |
||||
RET |
||||
|
||||
// func mixBlocksSSE2(out, a, b, c *block) |
||||
TEXT ยทmixBlocksSSE2(SB), 4, $0-32 |
||||
MOVQ out+0(FP), DX |
||||
MOVQ a+8(FP), AX |
||||
MOVQ b+16(FP), BX |
||||
MOVQ a+24(FP), CX |
||||
MOVQ $128, BP |
||||
|
||||
loop: |
||||
MOVOU 0(AX), X0 |
||||
MOVOU 0(BX), X1 |
||||
MOVOU 0(CX), X2 |
||||
PXOR X1, X0 |
||||
PXOR X2, X0 |
||||
MOVOU X0, 0(DX) |
||||
ADDQ $16, AX |
||||
ADDQ $16, BX |
||||
ADDQ $16, CX |
||||
ADDQ $16, DX |
||||
SUBQ $2, BP |
||||
JA loop |
||||
RET |
||||
|
||||
// func xorBlocksSSE2(out, a, b, c *block) |
||||
TEXT ยทxorBlocksSSE2(SB), 4, $0-32 |
||||
MOVQ out+0(FP), DX |
||||
MOVQ a+8(FP), AX |
||||
MOVQ b+16(FP), BX |
||||
MOVQ a+24(FP), CX |
||||
MOVQ $128, BP |
||||
|
||||
loop: |
||||
MOVOU 0(AX), X0 |
||||
MOVOU 0(BX), X1 |
||||
MOVOU 0(CX), X2 |
||||
MOVOU 0(DX), X3 |
||||
PXOR X1, X0 |
||||
PXOR X2, X0 |
||||
PXOR X3, X0 |
||||
MOVOU X0, 0(DX) |
||||
ADDQ $16, AX |
||||
ADDQ $16, BX |
||||
ADDQ $16, CX |
||||
ADDQ $16, DX |
||||
SUBQ $2, BP |
||||
JA loop |
||||
RET |
@ -0,0 +1,163 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package argon2 |
||||
|
||||
var useSSE4 bool |
||||
|
||||
func processBlockGeneric(out, in1, in2 *block, xor bool) { |
||||
var t block |
||||
for i := range t { |
||||
t[i] = in1[i] ^ in2[i] |
||||
} |
||||
for i := 0; i < blockLength; i += 16 { |
||||
blamkaGeneric( |
||||
&t[i+0], &t[i+1], &t[i+2], &t[i+3], |
||||
&t[i+4], &t[i+5], &t[i+6], &t[i+7], |
||||
&t[i+8], &t[i+9], &t[i+10], &t[i+11], |
||||
&t[i+12], &t[i+13], &t[i+14], &t[i+15], |
||||
) |
||||
} |
||||
for i := 0; i < blockLength/8; i += 2 { |
||||
blamkaGeneric( |
||||
&t[i], &t[i+1], &t[16+i], &t[16+i+1], |
||||
&t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1], |
||||
&t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1], |
||||
&t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1], |
||||
) |
||||
} |
||||
if xor { |
||||
for i := range t { |
||||
out[i] ^= in1[i] ^ in2[i] ^ t[i] |
||||
} |
||||
} else { |
||||
for i := range t { |
||||
out[i] = in1[i] ^ in2[i] ^ t[i] |
||||
} |
||||
} |
||||
} |
||||
|
||||
func blamkaGeneric(t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15 *uint64) { |
||||
v00, v01, v02, v03 := *t00, *t01, *t02, *t03 |
||||
v04, v05, v06, v07 := *t04, *t05, *t06, *t07 |
||||
v08, v09, v10, v11 := *t08, *t09, *t10, *t11 |
||||
v12, v13, v14, v15 := *t12, *t13, *t14, *t15 |
||||
|
||||
v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04)) |
||||
v12 ^= v00 |
||||
v12 = v12>>32 | v12<<32 |
||||
v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12)) |
||||
v04 ^= v08 |
||||
v04 = v04>>24 | v04<<40 |
||||
|
||||
v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04)) |
||||
v12 ^= v00 |
||||
v12 = v12>>16 | v12<<48 |
||||
v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12)) |
||||
v04 ^= v08 |
||||
v04 = v04>>63 | v04<<1 |
||||
|
||||
v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05)) |
||||
v13 ^= v01 |
||||
v13 = v13>>32 | v13<<32 |
||||
v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13)) |
||||
v05 ^= v09 |
||||
v05 = v05>>24 | v05<<40 |
||||
|
||||
v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05)) |
||||
v13 ^= v01 |
||||
v13 = v13>>16 | v13<<48 |
||||
v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13)) |
||||
v05 ^= v09 |
||||
v05 = v05>>63 | v05<<1 |
||||
|
||||
v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06)) |
||||
v14 ^= v02 |
||||
v14 = v14>>32 | v14<<32 |
||||
v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14)) |
||||
v06 ^= v10 |
||||
v06 = v06>>24 | v06<<40 |
||||
|
||||
v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06)) |
||||
v14 ^= v02 |
||||
v14 = v14>>16 | v14<<48 |
||||
v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14)) |
||||
v06 ^= v10 |
||||
v06 = v06>>63 | v06<<1 |
||||
|
||||
v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07)) |
||||
v15 ^= v03 |
||||
v15 = v15>>32 | v15<<32 |
||||
v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15)) |
||||
v07 ^= v11 |
||||
v07 = v07>>24 | v07<<40 |
||||
|
||||
v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07)) |
||||
v15 ^= v03 |
||||
v15 = v15>>16 | v15<<48 |
||||
v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15)) |
||||
v07 ^= v11 |
||||
v07 = v07>>63 | v07<<1 |
||||
|
||||
v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05)) |
||||
v15 ^= v00 |
||||
v15 = v15>>32 | v15<<32 |
||||
v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15)) |
||||
v05 ^= v10 |
||||
v05 = v05>>24 | v05<<40 |
||||
|
||||
v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05)) |
||||
v15 ^= v00 |
||||
v15 = v15>>16 | v15<<48 |
||||
v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15)) |
||||
v05 ^= v10 |
||||
v05 = v05>>63 | v05<<1 |
||||
|
||||
v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06)) |
||||
v12 ^= v01 |
||||
v12 = v12>>32 | v12<<32 |
||||
v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12)) |
||||
v06 ^= v11 |
||||
v06 = v06>>24 | v06<<40 |
||||
|
||||
v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06)) |
||||
v12 ^= v01 |
||||
v12 = v12>>16 | v12<<48 |
||||
v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12)) |
||||
v06 ^= v11 |
||||
v06 = v06>>63 | v06<<1 |
||||
|
||||
v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07)) |
||||
v13 ^= v02 |
||||
v13 = v13>>32 | v13<<32 |
||||
v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13)) |
||||
v07 ^= v08 |
||||
v07 = v07>>24 | v07<<40 |
||||
|
||||
v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07)) |
||||
v13 ^= v02 |
||||
v13 = v13>>16 | v13<<48 |
||||
v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13)) |
||||
v07 ^= v08 |
||||
v07 = v07>>63 | v07<<1 |
||||
|
||||
v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04)) |
||||
v14 ^= v03 |
||||
v14 = v14>>32 | v14<<32 |
||||
v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14)) |
||||
v04 ^= v09 |
||||
v04 = v04>>24 | v04<<40 |
||||
|
||||
v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04)) |
||||
v14 ^= v03 |
||||
v14 = v14>>16 | v14<<48 |
||||
v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14)) |
||||
v04 ^= v09 |
||||
v04 = v04>>63 | v04<<1 |
||||
|
||||
*t00, *t01, *t02, *t03 = v00, v01, v02, v03 |
||||
*t04, *t05, *t06, *t07 = v04, v05, v06, v07 |
||||
*t08, *t09, *t10, *t11 = v08, v09, v10, v11 |
||||
*t12, *t13, *t14, *t15 = v12, v13, v14, v15 |
||||
} |
@ -0,0 +1,15 @@ |
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package argon2 |
||||
|
||||
func processBlock(out, in1, in2 *block) { |
||||
processBlockGeneric(out, in1, in2, false) |
||||
} |
||||
|
||||
func processBlockXOR(out, in1, in2 *block) { |
||||
processBlockGeneric(out, in1, in2, true) |
||||
} |
@ -0,0 +1,289 @@ |
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693
|
||||
// and the extendable output function (XOF) BLAKE2Xb.
|
||||
//
|
||||
// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf
|
||||
// and for BLAKE2Xb see https://blake2.net/blake2x.pdf
|
||||
//
|
||||
// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512).
|
||||
// If you need a secret-key MAC (message authentication code), use the New512
|
||||
// function with a non-nil key.
|
||||
//
|
||||
// BLAKE2X is a construction to compute hash values larger than 64 bytes. It
|
||||
// can produce hash values between 0 and 4 GiB.
|
||||
package blake2b |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"hash" |
||||
) |
||||
|
||||
const ( |
||||
// The blocksize of BLAKE2b in bytes.
|
||||
BlockSize = 128 |
||||
// The hash size of BLAKE2b-512 in bytes.
|
||||
Size = 64 |
||||
// The hash size of BLAKE2b-384 in bytes.
|
||||
Size384 = 48 |
||||
// The hash size of BLAKE2b-256 in bytes.
|
||||
Size256 = 32 |
||||
) |
||||
|
||||
var ( |
||||
useAVX2 bool |
||||
useAVX bool |
||||
useSSE4 bool |
||||
) |
||||
|
||||
var ( |
||||
errKeySize = errors.New("blake2b: invalid key size") |
||||
errHashSize = errors.New("blake2b: invalid hash size") |
||||
) |
||||
|
||||
var iv = [8]uint64{ |
||||
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, |
||||
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, |
||||
} |
||||
|
||||
// Sum512 returns the BLAKE2b-512 checksum of the data.
|
||||
func Sum512(data []byte) [Size]byte { |
||||
var sum [Size]byte |
||||
checkSum(&sum, Size, data) |
||||
return sum |
||||
} |
||||
|
||||
// Sum384 returns the BLAKE2b-384 checksum of the data.
|
||||
func Sum384(data []byte) [Size384]byte { |
||||
var sum [Size]byte |
||||
var sum384 [Size384]byte |
||||
checkSum(&sum, Size384, data) |
||||
copy(sum384[:], sum[:Size384]) |
||||
return sum384 |
||||
} |
||||
|
||||
// Sum256 returns the BLAKE2b-256 checksum of the data.
|
||||
func Sum256(data []byte) [Size256]byte { |
||||
var sum [Size]byte |
||||
var sum256 [Size256]byte |
||||
checkSum(&sum, Size256, data) |
||||
copy(sum256[:], sum[:Size256]) |
||||
return sum256 |
||||
} |
||||
|
||||
// New512 returns a new hash.Hash computing the BLAKE2b-512 checksum. A non-nil
|
||||
// key turns the hash into a MAC. The key must be between zero and 64 bytes long.
|
||||
func New512(key []byte) (hash.Hash, error) { return newDigest(Size, key) } |
||||
|
||||
// New384 returns a new hash.Hash computing the BLAKE2b-384 checksum. A non-nil
|
||||
// key turns the hash into a MAC. The key must be between zero and 64 bytes long.
|
||||
func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) } |
||||
|
||||
// New256 returns a new hash.Hash computing the BLAKE2b-256 checksum. A non-nil
|
||||
// key turns the hash into a MAC. The key must be between zero and 64 bytes long.
|
||||
func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } |
||||
|
||||
// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length.
|
||||
// A non-nil key turns the hash into a MAC. The key must be between zero and 64 bytes long.
|
||||
// The hash size can be a value between 1 and 64 but it is highly recommended to use
|
||||
// values equal or greater than:
|
||||
// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long).
|
||||
// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long).
|
||||
// When the key is nil, the returned hash.Hash implements BinaryMarshaler
|
||||
// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash.
|
||||
func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } |
||||
|
||||
func newDigest(hashSize int, key []byte) (*digest, error) { |
||||
if hashSize < 1 || hashSize > Size { |
||||
return nil, errHashSize |
||||
} |
||||
if len(key) > Size { |
||||
return nil, errKeySize |
||||
} |
||||
d := &digest{ |
||||
size: hashSize, |
||||
keyLen: len(key), |
||||
} |
||||
copy(d.key[:], key) |
||||
d.Reset() |
||||
return d, nil |
||||
} |
||||
|
||||
func checkSum(sum *[Size]byte, hashSize int, data []byte) { |
||||
h := iv |
||||
h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) |
||||
var c [2]uint64 |
||||
|
||||
if length := len(data); length > BlockSize { |
||||
n := length &^ (BlockSize - 1) |
||||
if length == n { |
||||
n -= BlockSize |
||||
} |
||||
hashBlocks(&h, &c, 0, data[:n]) |
||||
data = data[n:] |
||||
} |
||||
|
||||
var block [BlockSize]byte |
||||
offset := copy(block[:], data) |
||||
remaining := uint64(BlockSize - offset) |
||||
if c[0] < remaining { |
||||
c[1]-- |
||||
} |
||||
c[0] -= remaining |
||||
|
||||
hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) |
||||
|
||||
for i, v := range h[:(hashSize+7)/8] { |
||||
binary.LittleEndian.PutUint64(sum[8*i:], v) |
||||
} |
||||
} |
||||
|
||||
type digest struct { |
||||
h [8]uint64 |
||||
c [2]uint64 |
||||
size int |
||||
block [BlockSize]byte |
||||
offset int |
||||
|
||||
key [BlockSize]byte |
||||
keyLen int |
||||
} |
||||
|
||||
const ( |
||||
magic = "b2b" |
||||
marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 |
||||
) |
||||
|
||||
func (d *digest) MarshalBinary() ([]byte, error) { |
||||
if d.keyLen != 0 { |
||||
return nil, errors.New("crypto/blake2b: cannot marshal MACs") |
||||
} |
||||
b := make([]byte, 0, marshaledSize) |
||||
b = append(b, magic...) |
||||
for i := 0; i < 8; i++ { |
||||
b = appendUint64(b, d.h[i]) |
||||
} |
||||
b = appendUint64(b, d.c[0]) |
||||
b = appendUint64(b, d.c[1]) |
||||
// Maximum value for size is 64
|
||||
b = append(b, byte(d.size)) |
||||
b = append(b, d.block[:]...) |
||||
b = append(b, byte(d.offset)) |
||||
return b, nil |
||||
} |
||||
|
||||
func (d *digest) UnmarshalBinary(b []byte) error { |
||||
if len(b) < len(magic) || string(b[:len(magic)]) != magic { |
||||
return errors.New("crypto/blake2b: invalid hash state identifier") |
||||
} |
||||
if len(b) != marshaledSize { |
||||
return errors.New("crypto/blake2b: invalid hash state size") |
||||
} |
||||
b = b[len(magic):] |
||||
for i := 0; i < 8; i++ { |
||||
b, d.h[i] = consumeUint64(b) |
||||
} |
||||
b, d.c[0] = consumeUint64(b) |
||||
b, d.c[1] = consumeUint64(b) |
||||
d.size = int(b[0]) |
||||
b = b[1:] |
||||
copy(d.block[:], b[:BlockSize]) |
||||
b = b[BlockSize:] |
||||
d.offset = int(b[0]) |
||||
return nil |
||||
} |
||||
|
||||
func (d *digest) BlockSize() int { return BlockSize } |
||||
|
||||
func (d *digest) Size() int { return d.size } |
||||
|
||||
func (d *digest) Reset() { |
||||
d.h = iv |
||||
d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) |
||||
d.offset, d.c[0], d.c[1] = 0, 0, 0 |
||||
if d.keyLen > 0 { |
||||
d.block = d.key |
||||
d.offset = BlockSize |
||||
} |
||||
} |
||||
|
||||
func (d *digest) Write(p []byte) (n int, err error) { |
||||
n = len(p) |
||||
|
||||
if d.offset > 0 { |
||||
remaining := BlockSize - d.offset |
||||
if n <= remaining { |
||||
d.offset += copy(d.block[d.offset:], p) |
||||
return |
||||
} |
||||
copy(d.block[d.offset:], p[:remaining]) |
||||
hashBlocks(&d.h, &d.c, 0, d.block[:]) |
||||
d.offset = 0 |
||||
p = p[remaining:] |
||||
} |
||||
|
||||
if length := len(p); length > BlockSize { |
||||
nn := length &^ (BlockSize - 1) |
||||
if length == nn { |
||||
nn -= BlockSize |
||||
} |
||||
hashBlocks(&d.h, &d.c, 0, p[:nn]) |
||||
p = p[nn:] |
||||
} |
||||
|
||||
if len(p) > 0 { |
||||
d.offset += copy(d.block[:], p) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func (d *digest) Sum(sum []byte) []byte { |
||||
var hash [Size]byte |
||||
d.finalize(&hash) |
||||
return append(sum, hash[:d.size]...) |
||||
} |
||||
|
||||
func (d *digest) finalize(hash *[Size]byte) { |
||||
var block [BlockSize]byte |
||||