core:crypto/_blake2: Cleanups and fixes

The fixes apply to "use it as a MAC" which was not part of the
documented/exposed API.  It now is, and is covered by the self-test
routines from the RFC.
This commit is contained in:
Yawning Angel
2026-03-04 20:07:52 +09:00
parent 1a5126c6b7
commit 058bd9a914
4 changed files with 178 additions and 43 deletions

View File

@@ -19,17 +19,12 @@ BLAKE2S_SIZE :: 32
BLAKE2B_BLOCK_SIZE :: 128
BLAKE2B_SIZE :: 64
MAX_SIZE :: 255
Blake2s_Context :: struct {
h: [8]u32,
t: [2]u32,
f: [2]u32,
x: [BLAKE2S_BLOCK_SIZE]byte,
nx: int,
ih: [8]u32,
padded_key: [BLAKE2S_BLOCK_SIZE]byte,
is_keyed: bool,
size: byte,
is_last_node: bool,
@@ -42,9 +37,6 @@ Blake2b_Context :: struct {
f: [2]u64,
x: [BLAKE2B_BLOCK_SIZE]byte,
nx: int,
ih: [8]u64,
padded_key: [BLAKE2B_BLOCK_SIZE]byte,
is_keyed: bool,
size: byte,
is_last_node: bool,
@@ -87,11 +79,12 @@ BLAKE2B_IV := [8]u64 {
init :: proc "contextless" (ctx: ^$T, cfg: ^Blake2_Config) {
when T == Blake2s_Context {
max_size :: BLAKE2S_SIZE
MAX_SIZE :: BLAKE2S_SIZE
} else when T == Blake2b_Context {
max_size :: BLAKE2B_SIZE
MAX_SIZE :: BLAKE2B_SIZE
}
ensure_contextless(cfg.size <= max_size, "blake2: requested output size exceeeds algorithm max")
ensure_contextless(cfg.size <= MAX_SIZE, "blake2: requested output size exceeeds algorithm max")
ensure_contextless(len(cfg.key) <= MAX_SIZE, "blake2: requested key size exceeeds algorithm max")
// To save having to allocate a scratch buffer, use the internal
// data buffer (`ctx.x`), as it is exactly the correct size.
@@ -152,17 +145,11 @@ init :: proc "contextless" (ctx: ^$T, cfg: ^Blake2_Config) {
ctx.is_last_node = true
}
if len(cfg.key) > 0 {
copy(ctx.padded_key[:], cfg.key)
update(ctx, ctx.padded_key[:])
ctx.is_keyed = true
copy(ctx.x[:], cfg.key)
ctx.nx = len(ctx.x)
} else {
ctx.nx = 0
}
copy(ctx.ih[:], ctx.h[:])
copy(ctx.h[:], ctx.ih[:])
if ctx.is_keyed {
update(ctx, ctx.padded_key[:])
}
ctx.nx = 0
ctx.is_initialized = true
}
@@ -172,22 +159,22 @@ update :: proc "contextless" (ctx: ^$T, p: []byte) {
p := p
when T == Blake2s_Context {
block_size :: BLAKE2S_BLOCK_SIZE
BLOCK_SIZE :: BLAKE2S_BLOCK_SIZE
} else when T == Blake2b_Context {
block_size :: BLAKE2B_BLOCK_SIZE
BLOCK_SIZE :: BLAKE2B_BLOCK_SIZE
}
left := block_size - ctx.nx
left := BLOCK_SIZE - ctx.nx
if len(p) > left {
copy(ctx.x[ctx.nx:], p[:left])
p = p[left:]
blocks(ctx, ctx.x[:])
ctx.nx = 0
}
if len(p) > block_size {
n := len(p) &~ (block_size - 1)
if len(p) > BLOCK_SIZE {
n := len(p) &~ (BLOCK_SIZE - 1)
if n == len(p) {
n -= block_size
n -= BLOCK_SIZE
}
blocks(ctx, p[:n])
p = p[n:]
@@ -228,12 +215,6 @@ reset :: proc "contextless" (ctx: ^$T) {
@(private)
blake2s_final :: proc "contextless" (ctx: ^Blake2s_Context, hash: []byte) {
if ctx.is_keyed {
for i := 0; i < len(ctx.padded_key); i += 1 {
ctx.padded_key[i] = 0
}
}
dec := BLAKE2S_BLOCK_SIZE - u32(ctx.nx)
if ctx.t[0] < dec {
ctx.t[1] -= 1
@@ -254,17 +235,11 @@ blake2s_final :: proc "contextless" (ctx: ^Blake2s_Context, hash: []byte) {
for i := 0; i < BLAKE2S_SIZE / 4; i += 1 {
endian.unchecked_put_u32le(dst[i * 4:], ctx.h[i])
}
copy(hash, dst[:])
copy(hash, dst[:ctx.size])
}
@(private)
blake2b_final :: proc "contextless" (ctx: ^Blake2b_Context, hash: []byte) {
if ctx.is_keyed {
for i := 0; i < len(ctx.padded_key); i += 1 {
ctx.padded_key[i] = 0
}
}
dec := BLAKE2B_BLOCK_SIZE - u64(ctx.nx)
if ctx.t[0] < dec {
ctx.t[1] -= 1

View File

@@ -28,13 +28,24 @@ Context :: _blake2.Blake2b_Context
// init initializes a Context with the default BLAKE2b config.
init :: proc(ctx: ^Context, digest_size := DIGEST_SIZE) {
ensure(digest_size <= _blake2.MAX_SIZE, "crypto/blake2b: invalid digest size")
ensure(digest_size <= DIGEST_SIZE, "crypto/blake2b: invalid digest size")
cfg: _blake2.Blake2_Config
cfg.size = u8(digest_size)
_blake2.init(ctx, &cfg)
}
// init_mac initializes a Context with a user provided key.
init_mac :: proc(ctx: ^Context, key: []byte, digest_size := DIGEST_SIZE) {
ensure(digest_size <= DIGEST_SIZE, "crypto/blake2b: invalid digest size")
ensure(len(key) <= DIGEST_SIZE, "crypto/blake2b: invalid key size")
cfg: _blake2.Blake2_Config
cfg.size = u8(digest_size)
cfg.key = key
_blake2.init(ctx, &cfg)
}
// update adds more data to the Context.
update :: proc(ctx: ^Context, data: []byte) {
_blake2.update(ctx, data)

View File

@@ -28,13 +28,24 @@ Context :: _blake2.Blake2s_Context
// init initializes a Context with the default BLAKE2s config.
init :: proc(ctx: ^Context, digest_size := DIGEST_SIZE) {
ensure(digest_size <= _blake2.MAX_SIZE, "crypto/blake2s: invalid digest size")
ensure(digest_size <= DIGEST_SIZE, "crypto/blake2s: invalid digest size")
cfg: _blake2.Blake2_Config
cfg.size = u8(digest_size)
_blake2.init(ctx, &cfg)
}
// init_mac initializes a Context with a user provided key.
init_mac :: proc(ctx: ^Context, key: []byte, digest_size := DIGEST_SIZE) {
ensure(digest_size <= DIGEST_SIZE, "crypto/blake2s: invalid digest size")
ensure(len(key) <= DIGEST_SIZE, "crypto/blake2s: invalid key size")
cfg: _blake2.Blake2_Config
cfg.size = u8(digest_size)
cfg.key = key
_blake2.init(ctx, &cfg)
}
// update adds more data to the Context.
update :: proc(ctx: ^Context, data: []byte) {
_blake2.update(ctx, data)

View File

@@ -5,6 +5,9 @@ import "core:bytes"
import "core:encoding/hex"
import "core:strings"
import "core:testing"
import "core:crypto/blake2b"
import "core:crypto/blake2s"
import "core:crypto/hash"
@(test)
@@ -596,4 +599,139 @@ test_hash :: proc(t: ^testing.T) {
c_str,
)
}
}
}
@(private="file")
selftest_seq :: proc(dst: []byte, seed: u32) {
a := 0xdead4bad * seed
b: u32 = 1
for i in 0 ..< len(dst) {
a, b = b, a + b
dst[i] = byte(b >> 24)
}
}
@(test)
test_blake2b_self :: proc(t: ^testing.T) {
expected := []byte{
0xC2, 0x3A, 0x78, 0x00, 0xD9, 0x81, 0x23, 0xBD,
0x10, 0xF5, 0x06, 0xC6, 0x1E, 0x29, 0xDA, 0x56,
0x03, 0xD7, 0x63, 0xB8, 0xBB, 0xAD, 0x2E, 0x73,
0x7F, 0x5E, 0x76, 0x5A, 0x7B, 0xCC, 0xD4, 0x75,
}
md_lens := []int{20, 32, 48, 64}
src_lens := []int{0, 3, 128, 129, 255, 1024}
b2b := proc(dst, src: []byte) {
ctx: blake2b.Context
blake2b.init(&ctx, len(dst))
blake2b.update(&ctx, src)
blake2b.final(&ctx, dst)
}
b2b_keyed := proc(dst, key, src: []byte) {
ctx: blake2b.Context
blake2b.init_mac(&ctx, key, len(dst))
blake2b.update(&ctx, src)
blake2b.final(&ctx, dst)
}
buf: [1024]byte
md, key: [64]byte
ctx: blake2b.Context
blake2b.init(&ctx, 32)
for md_len in md_lens {
dst := md[:md_len]
for src_len in src_lens {
src := buf[:src_len]
selftest_seq(src, u32(src_len))
b2b(dst, src)
blake2b.update(&ctx, dst)
k := key[:md_len]
selftest_seq(k, u32(md_len))
b2b_keyed(dst, k, src)
blake2b.update(&ctx, dst)
}
}
blake2b.final(&ctx, md[:32])
expected_str := string(hex.encode(expected, context.temp_allocator))
actual_str := string(hex.encode(md[:32], context.temp_allocator))
testing.expectf(
t,
expected_str == actual_str,
"blake2b/self-test: Expected: %s Got %s",
expected_str,
actual_str,
)
}
@(test)
test_blake2s_self :: proc(t: ^testing.T) {
expected := []byte{
0x6A, 0x41, 0x1F, 0x08, 0xCE, 0x25, 0xAD, 0xCD,
0xFB, 0x02, 0xAB, 0xA6, 0x41, 0x45, 0x1C, 0xEC,
0x53, 0xC5, 0x98, 0xB2, 0x4F, 0x4F, 0xC7, 0x87,
0xFB, 0xDC, 0x88, 0x79, 0x7F, 0x4C, 0x1D, 0xFE,
}
md_lens := []int{16, 20, 28, 32}
src_lens := []int{0, 3, 64, 65, 255, 1024}
b2s := proc(dst, src: []byte) {
ctx: blake2s.Context
blake2s.init(&ctx, len(dst))
blake2s.update(&ctx, src)
blake2s.final(&ctx, dst)
}
b2s_keyed := proc(dst, key, src: []byte) {
ctx: blake2s.Context
blake2s.init_mac(&ctx, key, len(dst))
blake2s.update(&ctx, src)
blake2s.final(&ctx, dst)
}
buf: [1024]byte
md, key: [32]byte
ctx: blake2s.Context
blake2s.init(&ctx)
for md_len in md_lens {
dst := md[:md_len]
for src_len in src_lens {
src := buf[:src_len]
selftest_seq(src, u32(src_len))
b2s(dst, src)
blake2s.update(&ctx, dst)
k := key[:md_len]
selftest_seq(k, u32(md_len))
b2s_keyed(dst, k, src)
blake2s.update(&ctx, dst)
}
}
blake2s.final(&ctx, md[:])
expected_str := string(hex.encode(expected, context.temp_allocator))
actual_str := string(hex.encode(md[:], context.temp_allocator))
testing.expectf(
t,
expected_str == actual_str,
"blake2s/self-test: Expected: %s Got %s",
expected_str,
actual_str,
)
}