Implementing RSA-AES encryption protocol
whether this is a correct - that is secure - implementation of such an
  "algorithm pair" for transmitting messages via an unsafe and
  permanently visible channel


Your approach provides zero forward security. As soon as Bobs private key is compromised, all past communication with Bob is compromised as well.

You should use a Diffie-Hellman like handshake protocol for agreeing on a common secret for the actual communication.



You are not providing any form of authentication either.

Right now, Eva can perform a trivial man-in-the-middle attack. While Alice could in theory still verify Bobs identity by comparing the provided public key against a trusted database, Bob in return has no means to verify Alice's identity.

A simple band-aid would be to sign the chosen communication key with Alice's public RSA key, so that Bob has the confirmation that the key was in fact chosen by Alice, and not Eva.

For that, just apply a cryptographic hash function to the key, and encrypt the hash with Alice's private RSA key. Bob then uses Alice's known public RSA key to decrypt the signature and verifies that he can generate the same hash. Alternatively you can hash the encrypted shared secret instead of the plain text, if you don't wish to leak information on the plain text.

Be wary that this is still potentially subject to replay attacks. In order to protect against that, Bob should start the communication with a plaintext nonce which Alice needs to include in the signature. This may be an arbitrary string, but something as simple as the current time stamp suffices.

This is still only authentication though, and does not remove the need to agree on a new key via DH to provide forward security. You need to get rid of the temporary key as fast as possible.



The problem of replay attacks also applies to the actual messages themselves. As each message is only protected by the shared secret, you are unable to detect when Eve maliciously repeats a recorded message of the same session repeatedly.

A practical solution to that, is to append a fresh nonce to each sent message, which is expected to be repeated in the next message. Now you can tell for sure if the order of messages in one direction has been altered.

As for ensuring the coherence of a dialog, you might want to think about acknowledgment messages, which confirm receiving a content message.




byte[] bobsPublicKey = extractedParameters.Modulus;



That's not the public key. You need both the public exponent and the modulus.



using (MemoryStream ms = new MemoryStream(data, iv.Length, 0))
using (ICryptoTransform trans = _aes.CreateDecryptor(sessionKey, iv))
using (CryptoStream cs = new CryptoStream(ms, trans, CryptoStreamMode.Read)
using (StreamReader sr = new StreamReader(cs))
    decryptedMessage = sr.ReadToEnd();


More curly braces and indentation please. The syntax with multiple using directives on the same indentation level should IMHO only be used when the declared disposables are independent. If they are dependent, they can no longer have the same life time, and that should be reflected by the code structure:

using (MemoryStream ms = new MemoryStream(data, iv.Length, 0))
using (ICryptoTransform trans = _aes.CreateDecryptor(sessionKey, iv))
{
    using (CryptoStream cs = new CryptoStream(ms, trans, CryptoStreamMode.Read)
    {
        using (StreamReader sr = new StreamReader(cs))
        {
            decryptedMessage = sr.ReadToEnd();
        }
    }
}