Decryption fails because the ciphertext used (enc) is invalid for AES-CBC.
With a valid AES-CBC ciphertext, when encrypting the first plaintext block, the initial IV is used as IV, and when encrypting the following plaintext blocks, the respective preceding ciphertext block is used as IV, see CBC. In addition, a valid AES-CBC ciphertext contains the (encrypted) padding bytes only at the end (if padding is used).
The ciphertext used (enc) in the posted example code, on the other hand, was generated by concatenating three AES-CBC single ciphertexts. The result is applied as ciphertext for AES-CBC decryption, which does not work because the last two single ciphertexts use the wrong IV for this (namely the initial IV) and the first two single ciphertexts also contain (encrypted) padding bytes.
If you want to stick with your approach (see a possible alternative in JonasH's comment), the correct IVs must be used for the encryption of the second and third single ciphertexts:
...
var ct1 = aes.EncryptCbc(line, aes.IV);
var ct2 = aes.EncryptCbc(line, ct1[^16..]);
var ct3 = aes.EncryptCbc(line, ct2[^16..]);
byte[] enc = [..ct1, ..ct2, ..ct3];
...
The ciphertext produced in this way can be decrypted using the posted decryption code.
However, the decrypted plaintext still contains the padding bytes. If your use case allows it, this problem could be solved e.g. with a custom padding, where all padding bytes have the same value (which must not occur in the plaintext), so that after decryption, the padding sequences can simply be removed (or used as separators). The default padding must be disabled for this purpose with PaddingMode.None.
Another approach is to use a stream cipher mode. Stream cipher modes do not require padding, so the padding problem would not arise in the first place. .NET supports CFB mode, e.g.:
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CFB;
...
var ct1 = aes.EncryptCfb(line, aes.IV, PaddingMode.None);
var ct2 = aes.EncryptCfb(line, ct1[^16..], PaddingMode.None);
var ct3 = aes.EncryptCfb(line, ct2[^16..], PaddingMode.None);
byte[] enc = [..ct1, ..ct2, ..ct3];
...
.NET uses CFB-8 by default. However, it can also be switched to the more performant CFB-128 (via the FeedbackSize parameter). Unfortunately, this variant pads to 128 bits (due to an MS bug), which is completely unnecessary for a stream cipher, but can be easily fixed (by truncating the encrypted padding bytes after encryption or padding to the required length with arbitrary padding bytes before decryption).
More frequently than CFB mode, CTR mode, also a stream cipher mode, is used. To apply CTR mode, BouncyCastle must be used, as .NET does not natively support CTR mode. The determination of the IVs of the last two single ciphertexts must be modified so that it is compatible with CTR mode.
Note that reusing key-IV pairs is a vulnerability whose severity depends on the mode. For CTR (and CTR-based modes), it is a severe vulnerability.
Concerning your comment:
AES-CBC only allows the encryption of plaintexts whose length is a multiple of the blocksize (16 bytes for AES). However, your plaintext has a length of 120 bytes (3x20 ASCII characters, UTF16LE encoded), which does not meet this condition. Either change the plaintext length (as pointed out in the error message The input data is not a complete block) or use a padding.
aesinstance for encryption and decryption. AES morphs its internal state as it goes.r.void Read(byte[] buffer, int offset, int limit)NumberOfValidBytes | ValidBytes | GarbageBytes. I don't see why you would need to mess with the encryption step at all. And what do you mean with "insecure protocol"? We typically use encryption to create a "secure" channel over an insecure one.