core/crypto/chacha20poly1305: Change the interface to match GCM

This commit is contained in:
Yawning Angel
2024-08-10 18:32:37 +09:00
parent 1f3107e693
commit 8efc98ce90
3 changed files with 98 additions and 78 deletions
@@ -24,13 +24,10 @@ TAG_SIZE :: poly1305.TAG_SIZE
_P_MAX :: 64 * 0xffffffff // 64 * (2^32-1) _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1)
@(private) @(private)
_validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) { _validate_common_slice_sizes :: proc (tag, nonce, aad, text: []byte) {
if len(tag) != TAG_SIZE { if len(tag) != TAG_SIZE {
panic("crypto/chacha20poly1305: invalid destination tag size") panic("crypto/chacha20poly1305: invalid destination tag size")
} }
if len(key) != KEY_SIZE {
panic("crypto/chacha20poly1305: invalid key size")
}
if len(nonce) != NONCE_SIZE { if len(nonce) != NONCE_SIZE {
panic("crypto/chacha20poly1305: invalid nonce size") panic("crypto/chacha20poly1305: invalid nonce size")
} }
@@ -59,16 +56,37 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) {
} }
} }
// encrypt encrypts the plaintext and authenticates the aad and ciphertext, // Context is a keyed Chacha20Poly1305 instance.
// with the provided key and nonce, stores the output in ciphertext and tag. Context :: struct {
encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { _key: [KEY_SIZE]byte,
_validate_common_slice_sizes(tag, key, nonce, aad, plaintext) _impl: chacha20.Implementation,
_is_initialized: bool,
}
// init initializes a Context with the provided key.
init :: proc(ctx: ^Context, key: []byte, impl := chacha20.Implementation.Simd256) {
if len(key) != KEY_SIZE {
panic("crypto/chacha20poly1305: invalid key size")
}
copy(ctx._key[:], key)
ctx._impl = impl
ctx._is_initialized = true
}
// seal encrypts the plaintext and authenticates the aad and ciphertext,
// with the provided Context and nonce, stores the output in dst and tag.
//
// dst and plaintext MUST alias exactly or not at all.
seal :: proc(ctx: ^Context, dst, tag, nonce, aad, plaintext: []byte) {
ciphertext := dst
_validate_common_slice_sizes(tag, nonce, aad, plaintext)
if len(ciphertext) != len(plaintext) { if len(ciphertext) != len(plaintext) {
panic("crypto/chacha20poly1305: invalid destination ciphertext size") panic("crypto/chacha20poly1305: invalid destination ciphertext size")
} }
stream_ctx: chacha20.Context = --- stream_ctx: chacha20.Context = ---
chacha20.init(&stream_ctx, key, nonce) chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
// otk = poly1305_key_gen(key, nonce) // otk = poly1305_key_gen(key, nonce)
otk: [poly1305.KEY_SIZE]byte = --- otk: [poly1305.KEY_SIZE]byte = ---
@@ -107,13 +125,15 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) {
poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context. poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context.
} }
// decrypt authenticates the aad and ciphertext, and decrypts the ciphertext, // open authenticates the aad and ciphertext, and decrypts the ciphertext,
// with the provided key, nonce, and tag, and stores the output in plaintext, // with the provided Context, nonce, and tag, and stores the output in dst,
// returning true iff the authentication was successful. // returning true iff the authentication was successful. If authentication
// fails, the destination buffer will be zeroed.
// //
// If authentication fails, the destination plaintext buffer will be zeroed. // dst and plaintext MUST alias exactly or not at all.
decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
_validate_common_slice_sizes(tag, key, nonce, aad, ciphertext) plaintext := dst
_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
if len(ciphertext) != len(plaintext) { if len(ciphertext) != len(plaintext) {
panic("crypto/chacha20poly1305: invalid destination plaintext size") panic("crypto/chacha20poly1305: invalid destination plaintext size")
} }
@@ -123,7 +143,7 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
// points where needed. // points where needed.
stream_ctx: chacha20.Context = --- stream_ctx: chacha20.Context = ---
chacha20.init(&stream_ctx, key, nonce) chacha20.init(&stream_ctx, ctx._key[:], nonce, ctx._impl)
// otk = poly1305_key_gen(key, nonce) // otk = poly1305_key_gen(key, nonce)
otk: [poly1305.KEY_SIZE]byte = --- otk: [poly1305.KEY_SIZE]byte = ---
@@ -166,3 +186,10 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool {
return true return true
} }
// reset sanitizes the Context. The Context must be
// re-initialized to be used again.
reset :: proc "contextless" (ctx: ^Context) {
mem.zero_explicit(&ctx._key, len(ctx._key))
ctx._is_initialized = false
}
+4 -1
View File
@@ -339,10 +339,13 @@ _benchmark_chacha20poly1305 :: proc(
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} }
ctx: chacha20poly1305.Context = ---
chacha20poly1305.init(&ctx, key[:]) // Basically 0 overhead.
tag: [chacha20poly1305.TAG_SIZE]byte = --- tag: [chacha20poly1305.TAG_SIZE]byte = ---
for _ in 0 ..= options.rounds { for _ in 0 ..= options.rounds {
chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) chacha20poly1305.seal(&ctx, buf, tag[:], nonce[:], nil, buf)
} }
options.count = options.rounds options.count = options.rounds
options.processed = options.rounds * options.bytes options.processed = options.rounds * options.bytes
+51 -61
View File
@@ -44,8 +44,8 @@ test_chacha20 :: proc(t: ^testing.T) {
for impl in impls { for impl in impls {
test_chacha20_stream(t, impl) test_chacha20_stream(t, impl)
test_chacha20poly1305(t, impl)
} }
test_chacha20poly1305(t) // TODO: Move into loop.
} }
test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) { test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
@@ -93,7 +93,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
testing.expectf( testing.expectf(
t, t,
derived_ciphertext_str == ciphertext_str, derived_ciphertext_str == ciphertext_str,
"Expected %s for xor_bytes(plaintext_str), but got %s instead", "chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead",
impl,
ciphertext_str, ciphertext_str,
derived_ciphertext_str, derived_ciphertext_str,
) )
@@ -138,7 +139,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
testing.expectf( testing.expectf(
t, t,
derived_ciphertext_str == xciphertext_str, derived_ciphertext_str == xciphertext_str,
"Expected %s for xor_bytes(plaintext_str), but got %s instead", "chacha20/%v: Expected %s for xor_bytes(plaintext_str), but got %s instead",
impl,
xciphertext_str, xciphertext_str,
derived_ciphertext_str, derived_ciphertext_str,
) )
@@ -170,14 +172,16 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) {
testing.expectf( testing.expectf(
t, t,
expected_digest_str == digest_str, expected_digest_str == digest_str,
"Expected %s for keystream digest, but got %s instead", "chacha20/%v: Expected %s for keystream digest, but got %s instead",
impl,
expected_digest_str, expected_digest_str,
digest_str, digest_str,
) )
} }
test_chacha20poly1305 :: proc(t: ^testing.T) { test_chacha20poly1305 :: proc(t: ^testing.T, impl: chacha20.Implementation) {
plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR)
plaintext_str := string(hex.encode(plaintext, context.temp_allocator))
aad := [12]byte { aad := [12]byte {
0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3,
@@ -221,76 +225,62 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
} }
tag_str := string(hex.encode(tag[:], context.temp_allocator)) tag_str := string(hex.encode(tag[:], context.temp_allocator))
derived_tag: [chacha20poly1305.TAG_SIZE]byte tag_ := make([]byte, chacha20poly1305.TAG_SIZE, context.temp_allocator)
derived_ciphertext: [114]byte dst := make([]byte, len(ciphertext), context.temp_allocator)
chacha20poly1305.encrypt( ctx: chacha20poly1305.Context
derived_ciphertext[:], chacha20poly1305.init(&ctx, key[:])
derived_tag[:],
key[:], chacha20poly1305.seal(&ctx, dst, tag_, nonce[:], aad[:], plaintext)
nonce[:], dst_str := string(hex.encode(dst, context.temp_allocator))
aad[:], tag_str_ := string(hex.encode(tag_, context.temp_allocator))
plaintext,
)
derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator))
testing.expectf( testing.expectf(
t, t,
derived_ciphertext_str == ciphertext_str, dst_str == ciphertext_str && tag_str_ == tag_str,
"Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", "chacha20poly1305/%v: Expected: (%s, %s) for seal(%x, %x, %x, %x), but got (%s, %s) instead",
impl,
ciphertext_str, ciphertext_str,
derived_ciphertext_str,
)
derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator))
testing.expectf(
t,
derived_tag_str == tag_str,
"Expected tag %s for encrypt(aad, plaintext), but got %s instead",
tag_str, tag_str,
derived_tag_str, key,
nonce,
aad,
plaintext,
dst_str,
tag_str_,
) )
derived_plaintext: [114]byte ok := chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], ciphertext[:], tag[:])
ok := chacha20poly1305.decrypt( dst_str = string(hex.encode(dst, context.temp_allocator))
derived_plaintext[:],
tag[:],
key[:],
nonce[:],
aad[:],
ciphertext[:],
)
derived_plaintext_str := string(derived_plaintext[:])
testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)")
testing.expectf( testing.expectf(
t, t,
derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, ok && dst_str == plaintext_str,
"Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", "chacha20poly1305/%v: Expected: (%s, true) for open(%x, %x, %x, %x, %s), but got (%s, %v) instead",
_PLAINTEXT_SUNSCREEN_STR, impl,
derived_plaintext_str, plaintext_str,
key,
nonce,
aad,
ciphertext,
tag_str,
dst_str,
ok,
) )
derived_ciphertext[0] ~= 0xa5 copy(dst, ciphertext[:])
ok = chacha20poly1305.decrypt( tag_[0] ~= 0xa5
derived_plaintext[:], ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag_)
tag[:], testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(bad_tag, aad, ciphertext)", impl)
key[:],
nonce[:],
aad[:],
derived_ciphertext[:],
)
testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)")
dst[0] ~= 0xa5
ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag[:])
testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, aad, bad_ciphertext)", impl)
copy(dst, ciphertext[:])
aad[0] ~= 0xa5 aad[0] ~= 0xa5
ok = chacha20poly1305.decrypt( ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag[:])
derived_plaintext[:], testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, bad_aad, ciphertext)", impl)
tag[:],
key[:],
nonce[:],
aad[:],
ciphertext[:],
)
testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)")
} }
@(test) @(test)