RSA encryption is based on a trapdoor function, which is to say a pair of functions. I'll call them D
and E
. The functions are designed so that D(E(x)) = x
and E(D(x)) = x
(for any x
). In other words, D
and E
are inverses. What makes it a trapdoor function is that if you have a public key, you can only compute E
(practically speaking). If you have a private key, you can compute both D
and E
.
The way that encryption works is pretty obvious from that description. If Bob wants to send Alice an encrypted message, he computes ciphertext := E(plaintext)
. Then Bob sends ciphertext
to Alice. Alice computes D(ciphertext)
, which is D(E(plaintext))
, which is just plaintext
.
Now, let's talk about how signing works. If Alice wants wants to sign message
, then she computes signature := D(message)
. She then sends both message
and signature
to Bob. Bob then computes validation := E(signature)
. Since signature
is D(message)
, then validation = E(D(message)) = message
.
In other words: to sign a message, you act as if you're decrypting it, and that's your signature. To verify your signature, people can encrypt the signature and make sure they get back your original message.
I'll say that again: signing is the same operation as decryption.
This is the fundamental concern about separating signature and encryption keys. If somebody can get you to sign something, then they've just gotten you to decrypt it.
Suppose you're running a notary company. If somebody gives you $10 and a message (for instance, song lyrics), then you'll sign that message and send it back to you. If somebody later copies your song lyrics, you can produce the signature from the trusted notary company to demonstrate that you wrote those song lyrics.
Now suppose that Eve intercepted an encrypted message to your notary company. How can she subvert the encryption? She sends that same message to you for notarization! Now you run the signature operation (which, remember, is the same as the decryption operation), and send the result back to her. She now has the decrypted message.
In practice, protocols have steps that make this attack more difficult. For instance, PGP (by which I mean the protocol; gpg is the most common implementation here) doesn't sign the original message; it signs a hash of the message. But security proofs are best in simple situations. You don't want your proof about RSA's security to depend on the hash function. (For instance, many people used MD5 as the preferred hash for a long time, but today MD5 is considered quite broken.) Alone, RSA's security depends on the idea that you will not sign arbitrary messages with a key that's being used for encryption. Keeping that requirement in place is the best way to ensure PGP's security. (As I recall, this is the most frequently-repeated admonition about asymmetric encryption in Bruce Scheier's book Applied Cryptography.)
Now, let's talk about another question you asked: "Would this not also mean you need to distribute two public keys to everyone with whom you wish to communicate?"
A "key" means one thing to users, and a different thing to the crypto implementations. You only need to communicate one user-level "key", although that may contain many RSA public keys.
PGP has a concept of subkeys. My master key is a signing-only key. I have a separate encryption subkey. That subkey is signed by my master key. If you import my PGP key from the keyservers, or download it from my website, then you'll get my master key and all my subkeys. Even though you may have signed only my master key, my master key has signed my encryption subkey, so you know that it belongs to me too. That means that by downloading my PGP key (which encompasses many RSA public keys), you now have everything you need to both verify my signatures and to encrypt messages to me.
With subkeys, key management is more complex from a cryptographic standpoint (there's an extra key verification step to go through), but not from a practical standpoint (my PGP key includes my master key, as well as all my subkeys). The extra complexity is hidden in the implementation, and isn't exposed to the user.