diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index 7fc112d0d..b87e5a519 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -24,13 +24,10 @@ TAG_SIZE :: poly1305.TAG_SIZE _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1) @(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 { panic("crypto/chacha20poly1305: invalid destination tag size") } - if len(key) != KEY_SIZE { - panic("crypto/chacha20poly1305: invalid key size") - } if len(nonce) != 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, -// with the provided key and nonce, stores the output in ciphertext and tag. -encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { - _validate_common_slice_sizes(tag, key, nonce, aad, plaintext) +// Context is a keyed Chacha20Poly1305 instance. +Context :: struct { + _key: [KEY_SIZE]byte, + _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) { panic("crypto/chacha20poly1305: invalid destination ciphertext size") } 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_SIZE]byte = --- @@ -107,13 +125,15 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context. } -// decrypt authenticates the aad and ciphertext, and decrypts the ciphertext, -// with the provided key, nonce, and tag, and stores the output in plaintext, -// returning true iff the authentication was successful. +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, nonce, and tag, and stores the output in dst, +// 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. -decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { - _validate_common_slice_sizes(tag, key, nonce, aad, ciphertext) +// dst and plaintext MUST alias exactly or not at all. +open :: proc(ctx: ^Context, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + plaintext := dst + _validate_common_slice_sizes(tag, nonce, aad, ciphertext) if len(ciphertext) != len(plaintext) { panic("crypto/chacha20poly1305: invalid destination plaintext size") } @@ -123,7 +143,7 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { // points where needed. 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_SIZE]byte = --- @@ -166,3 +186,10 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { 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 +} diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin index b2ac4bca3..4c9e1382b 100644 --- a/tests/benchmark/crypto/benchmark_crypto.odin +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -339,10 +339,13 @@ _benchmark_chacha20poly1305 :: proc( 0x00, 0x00, 0x00, 0x00, } + ctx: chacha20poly1305.Context = --- + chacha20poly1305.init(&ctx, key[:]) // Basically 0 overhead. + tag: [chacha20poly1305.TAG_SIZE]byte = --- 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.processed = options.rounds * options.bytes diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index a4f4aa656..c57e4e46d 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -44,8 +44,8 @@ test_chacha20 :: proc(t: ^testing.T) { for impl in impls { 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) { @@ -93,7 +93,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) { testing.expectf( t, 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, derived_ciphertext_str, ) @@ -138,7 +139,8 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) { testing.expectf( t, 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, derived_ciphertext_str, ) @@ -170,14 +172,16 @@ test_chacha20_stream :: proc(t: ^testing.T, impl: chacha20.Implementation) { testing.expectf( t, 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, digest_str, ) } -test_chacha20poly1305 :: proc(t: ^testing.T) { +test_chacha20poly1305 :: proc(t: ^testing.T, impl: chacha20.Implementation) { plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) + plaintext_str := string(hex.encode(plaintext, context.temp_allocator)) aad := [12]byte { 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)) - derived_tag: [chacha20poly1305.TAG_SIZE]byte - derived_ciphertext: [114]byte + tag_ := make([]byte, chacha20poly1305.TAG_SIZE, context.temp_allocator) + dst := make([]byte, len(ciphertext), context.temp_allocator) - chacha20poly1305.encrypt( - derived_ciphertext[:], - derived_tag[:], - key[:], - nonce[:], - aad[:], - plaintext, - ) + ctx: chacha20poly1305.Context + chacha20poly1305.init(&ctx, key[:]) + + chacha20poly1305.seal(&ctx, dst, tag_, nonce[:], aad[:], plaintext) + dst_str := string(hex.encode(dst, context.temp_allocator)) + tag_str_ := string(hex.encode(tag_, context.temp_allocator)) - derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) testing.expectf( t, - derived_ciphertext_str == ciphertext_str, - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", + dst_str == ciphertext_str && tag_str_ == tag_str, + "chacha20poly1305/%v: Expected: (%s, %s) for seal(%x, %x, %x, %x), but got (%s, %s) instead", + impl, 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, - derived_tag_str, + key, + nonce, + aad, + plaintext, + dst_str, + tag_str_, ) - derived_plaintext: [114]byte - ok := chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - derived_plaintext_str := string(derived_plaintext[:]) - testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") + ok := chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], ciphertext[:], tag[:]) + dst_str = string(hex.encode(dst, context.temp_allocator)) + testing.expectf( t, - derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, + ok && dst_str == plaintext_str, + "chacha20poly1305/%v: Expected: (%s, true) for open(%x, %x, %x, %x, %s), but got (%s, %v) instead", + impl, + plaintext_str, + key, + nonce, + aad, + ciphertext, + tag_str, + dst_str, + ok, ) - derived_ciphertext[0] ~= 0xa5 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - derived_ciphertext[:], - ) - testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") + copy(dst, ciphertext[:]) + tag_[0] ~= 0xa5 + ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag_) + testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(bad_tag, aad, ciphertext)", impl) + 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 - ok = chacha20poly1305.decrypt( - derived_plaintext[:], - tag[:], - key[:], - nonce[:], - aad[:], - ciphertext[:], - ) - testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") + ok = chacha20poly1305.open(&ctx, dst, nonce[:], aad[:], dst[:], tag[:]) + testing.expectf(t, !ok, "chacha20poly1305/%v: Expected false for open(tag, bad_aad, ciphertext)", impl) } @(test)