without introducing another library
Well, this isn't exactly an ideal situation, but there's still hope. After having browsed the list of available tools there are still some things that can be done, but of course the solution will be less robust and less modern than if one could rely on external libraries.
Which one would be better to use?
This still depends. If you absolutely cannot tolerate ciphertext expansion under any circumstances whatsoever and if you absolutely cannot tolerate using external libraries under any circumstances whatsoever then yes, CBC-mode is your best choice. If you cannot tolerate ciphertext expansion, you want to look into using EME or XTS mode as a fallback, but for the rest of this section I'll assume that ciphertext expansion is acceptable.
Your best choice if you absolutely cannot tolerate using external libraries under any circumstances whatsoever is to use AES-CTR + HMAC-SHA256. You already found the Enum for CTR-Mode, this leads the question to HMAC-SHA256. The optimal way here is to take the entire ciphertext with all associated data (such as routing information and / or salts), run it through HMAC and append (or otherwise pair it with) the resulting authentication tag. Then on decryption reject any incoming messsage that either lacks a tag or has an invalid tag. The key should be derived as described below, but should definitely be independent of the AES key and the CTR IV.
Use of Use of PBKDF2 is outdated, and it was suggested to use Argon,
but here is what I'm using. Not sure of what to replace this with
within the constraints of .NET out of the box
That's the problem. There is nothing. This is the only situation where falling back to PBKDF2 is acceptable. There are two things to still get somewhat good security out of PBKDF2 and you really want to do both:
- Use a non-fixed iteration count. The one really nice thing about PBKDF2 is that its runtime is perfectly linear in its iteration count, so ideally you want to dynamically measure how many iterations per second you can do on your target system and then linearly extrapolate how many you actually want to do. Usually with this you want to target 100ms on something like a server, but the more CPU-time you can spare the better.
- Aggregate-Derive-Expand. As pointed out as a "side-note" in the answer on CodeReview.SE the current PBKDF2 parameters lead three separate "chains" of derivation which are easily parallelized by an attacker. Ideally (and this is possible here), one concentrates the keying material into a single string ("aggregate") using eg a straight SHA256 hash (this may get more complicated for more complex keying material structures and properties), then one runs this through the expensive key-derivation function so this function gets called exactly once and thus can be as expensive as possible ("derive"). Finally one gets what TLS calls a "master secret". From this one derives the actual secrets needed like e.g. the AES key, the HMAC key and potentially the CTR IV ("expand"). Ideally one would do this using HKDF, but in this case PBKDF2 with a single iteration also does the trick as would do "label-driven-expansion" where you put the master secret into the key input of an HMAC instance and a label for the key (such as the string "AES key") into the message input.