/* `ChaCha20` and `XChaCha20` stream ciphers. See: - [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] - [[ https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ ]] */ package chacha20 import "core:bytes" import "core:crypto" import "core:crypto/_chacha20" // KEY_SIZE is the (X)ChaCha20 key size in bytes. KEY_SIZE :: _chacha20.KEY_SIZE // IV_SIZE is the ChaCha20 IV size in bytes. IV_SIZE :: _chacha20.IV_SIZE // XIV_SIZE is the XChaCha20 IV size in bytes. XIV_SIZE :: _chacha20.XIV_SIZE // Context is a ChaCha20 or XChaCha20 instance. Context :: struct { _state: _chacha20.Context, _impl: Implementation, } // init inititializes a Context for ChaCha20 or XChaCha20 with the provided // key and iv. init :: proc(ctx: ^Context, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { ensure(len(key) == KEY_SIZE, "crypto/chacha20: invalid (X)ChaCha20 key size") ensure(len(iv) == IV_SIZE || len(iv) == XIV_SIZE, "crypto/chacha20: invalid (X)ChaCha20 IV size") k, n := key, iv init_impl(ctx, impl) is_xchacha := len(iv) == XIV_SIZE if is_xchacha { sub_iv: [IV_SIZE]byte sub_key := ctx._state._buffer[:KEY_SIZE] hchacha20(sub_key, k, n, ctx._impl) k = sub_key copy(sub_iv[4:], n[16:]) n = sub_iv[:] } _chacha20.init(&ctx._state, k, n, is_xchacha) if is_xchacha { // The sub-key is stored in the keystream buffer. While // this will be overwritten in most circumstances, explicitly // clear it out early. crypto.zero_explicit(&ctx._state._buffer, KEY_SIZE) } } // seek seeks the (X)ChaCha20 stream counter to the specified block. seek :: proc(ctx: ^Context, block_nr: u64) { _chacha20.seek(&ctx._state, block_nr) } // xor_bytes XORs each byte in src with bytes taken from the (X)ChaCha20 // keystream, and writes the resulting output to dst. Dst and src MUST // alias exactly or not at all. xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { ensure(ctx._state._is_initialized) src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } ensure(!bytes.alias_inexactly(dst, src), "crypto/chacha20: dst and src alias inexactly") st := &ctx._state #no_bounds_check for remaining := len(src); remaining > 0; { // Process multiple blocks at once if st._off == _chacha20.BLOCK_SIZE { if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE stream_blocks(ctx, dst, src, nr_blocks) remaining -= direct_bytes if remaining == 0 { return } dst = dst[direct_bytes:] src = src[direct_bytes:] } // If there is a partial block, generate and buffer 1 block // worth of keystream. stream_blocks(ctx, st._buffer[:], nil, 1) st._off = 0 } // Process partial blocks from the buffered keystream. to_xor := min(_chacha20.BLOCK_SIZE - st._off, remaining) buffered_keystream := st._buffer[st._off:] for i := 0; i < to_xor; i = i + 1 { dst[i] = buffered_keystream[i] ~ src[i] } st._off += to_xor dst = dst[to_xor:] src = src[to_xor:] remaining -= to_xor } } // keystream_bytes fills dst with the raw (X)ChaCha20 keystream output. keystream_bytes :: proc(ctx: ^Context, dst: []byte) { ensure(ctx._state._is_initialized) dst, st := dst, &ctx._state #no_bounds_check for remaining := len(dst); remaining > 0; { // Process multiple blocks at once if st._off == _chacha20.BLOCK_SIZE { if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE stream_blocks(ctx, dst, nil, nr_blocks) remaining -= direct_bytes if remaining == 0 { return } dst = dst[direct_bytes:] } // If there is a partial block, generate and buffer 1 block // worth of keystream. stream_blocks(ctx, st._buffer[:], nil, 1) st._off = 0 } // Process partial blocks from the buffered keystream. to_copy := min(_chacha20.BLOCK_SIZE - st._off, remaining) buffered_keystream := st._buffer[st._off:] copy(dst[:to_copy], buffered_keystream[:to_copy]) st._off += to_copy dst = dst[to_copy:] remaining -= to_copy } } // reset sanitizes the Context. The Context must be re-initialized to // be used again. reset :: proc(ctx: ^Context) { _chacha20.reset(&ctx._state) }