How to safely exchange encryption keys between server and client?

Hi,

i am writing a small server / client application using Asio and i exchange messages in plain text between a server and multiple clients.
This works fine but now i want to encrypt the messages using Crypto++.

I wrote a small wrapper class around ChaChaTLS to encrypt and decrypt messages, which works fine also.

But i wonder how to safely exchange the generated keys without the possibility to intercept them via a man in the middle attack.
I mean if i create a key on the server and send it to the client, i can capture the network traffic and read the key.

How do you do it so that the key can't be read by others?
And should i reuse the same key on the client and the server or should i create two (one for client, one for server)?

Sorry if i miss the obvious, but i am a crypto noob.
 
Certificate Authority (CA) is the most common way to protect exchange of encryption keys, and it comes with fees unless there's an exemption for type of service.

Here's about initiatives for verifying connections for encryption: https://blog.prosody.im/mandatory-encryption-on-xmpp-starts-today/. It's about XMPP, but there's relevancy for other connections.

DNSSEC, security/monkeysphere, and manual fingerprint checking (using another communication to visually verify/compare fingerprints). (DNSSEC is also used for websites.)
 
Thank you.

After reading a bit more, i found this:

Asymmetric Key Flow.png
  1. The Sender and Recipient verify each other’s certificates:
    1. The sender sends a certificate to the recipient for verification.
    2. The recipient then checks the certificate against their Certificate Authority (CA) or an external Validation Authority (VA) for authentication.
    3. Once the sender’s certificate has been verified, the recipient then sends their certificate to the sender for authentication and acceptance.
  2. Once the sender and recipient have mutual acceptance:
    1. The sender requests the recipient’s public key.
    2. The recipient sends their public key to the sender.
  3. The sender creates an ephemeral symmetric key and encrypts the file to be sent. (an ephemeral symmetric key is a symmetric encryption key used only for one session)
  4. The sender encrypts the symmetric key with the public key.
  5. The sender then sends the encrypted data with the encrypted symmetric key.
  6. The recipient receives the packet and decrypts the symmetric key with the private key.
  7. The recipient decrypts the data with the symmetric key.
 
eternal_noob That is pretty much the standard method of doing what you want.
Each side has a private/public key pair, each side needs to know the other side public key.
Sender generates the symmetric key, encrypts with the receiver public key and sends.

It works because to decrypt you need the private key (PGP, OpenPGP, GnuPG etc).
 
Ok, so now i created a server certificate and a private key via
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
and use SSL sockets to communicate.

I load the private key in the server and the certificate in the client.
Communication works for now.

But i don't get why i need a second certificate for the client.
The Sender and Recipient verify each other’s certificates
...
Once the sender’s certificate has been verified, the recipient then sends their certificate to the sender for authentication and acceptance
Isn't it sufficient to now generate the symetric key on the client and use the public key of the server to encrypt it and send it to the server?

Only the server can read it with its private key and use the symetric key for further communication with the client?

I am confused.
 
I'm no cryptography expert, but your problem sounds like the one tackled by Diffie & Hellman: https://en.wikipedia.org/wiki/Diffie–Hellman_key_exchange. This doesn't need a certificate authority.

In fact, I'm such a non-expert that I'm not even sure that this isn't the same as the method you've already tried :(
 
Yeah, i am using a Diffie Hellman file on the server:
Code:
context.use_certificate_chain_file("../common/cert.pem"); 
context.use_private_key_file("../common/key.pem", asio::ssl::context::pem); 
context.use_tmp_dh_file("../common/dh2048.pem"); //temporary Diffie-Hellman parameters

The client only loads the certificate:
Code:
ctx.load_verify_file("../common/cert.pem");

In fact, i don't have a problem since alls works fine. I am just confused why the article i posted says that i need a second certificate for the client.
 
In theory you shouldn't need certificates at all. But using them does add a layer of confidence that each end is who they say they are.
One thing that can be confusing is use of sender/receipient and client/server.
I assume that the client initiates the conversation with the server looks like over a SSL connection. The SSL connection needs valid certs to work (I think) so if the socket connects, you have some level of validity.
If each side then sends a cert or a token of something that will then establish each sides identity.
The SSL connection can use certs signed by a global CA, then the secondary certs could be signed by a different CA (say self signed by the manufacturer). You can also put different information in the second set of certs if you want.

Think of someone knocking on your door:
Is this the Jones residence?
Yes, it is. ( this is the SSL connection success )
I'm Tom Smith, are you Bob Jones?
You exchange IDs, verify they match the person ( second cert exchange )
Tom Smith hands a sealed envelope to Bob Jones ( symmetric key handover )
Bob Jones takes the envelope, opens it, and decodes the contents with his private key,
reads it and finds out his car's extended warranty expired. (just kidding, he has the symmetric key now).
 
One can also generate the symmetric key, encrypt it with the receiver public key, attach that to the message block then the sender can sign (generate a hash over the block, then encrypt the hash) the message block with his private key; the signature is appended to the message block. Then send it.
Receiver then first strips the signature, generates a hash on the message locally, then using the senders public key extracts the sender's value for the hash and compare the two. If hashes match you know the data has not changed in transit and that the sender really sent it. Then receiver can decrypt the symmetric key with his private key.

If you do this, you can probably just do the top level SSL connection between client and server and not the secondary cert exchange.
 
On setting up a connection, a potential hijacker can claim to be the desired party. A self-signed cetificate can't validate whether an encrypted connection is with who you intended it to be with.

With a CA, a potential hijacker would have to spoof an additional connection point simultaneously, which is exponentially more difficult and highly unlikely to be acheived. The CA acts like a referee, checkpoint, guard or watcher, to make sure that the other connection is who it says it is, before encrypting a connection to that party.

With a self-signed certificate, the connection is encrypted, but with who? Was the encrypted key shared with the intended party or a spoofer claiming to be the intended party? Sharing the key itself is the vulnerability. A self-signed certificate assumes there won't be any attempted spoofers, or there will be no consequence if there is spoofing to the relevancy to the data. For instance, if the encrypted data is only relevant for the session, and not to multisessions. Also, if the data is sort of important, but not critical.

On setting up wifi, setting up is the most vulnerable part, because many in-range connections can claim to be the desired connection. One is assuming that no one within wifi range is trying to spoof or knows how to spoof.

Manual fingerprint checking, DNSSEC, and monkeysphere are intended alternatives to using a CA.
 
For the record, this is how i am doing it now:
Bash:
# create private key for root certificate
openssl genrsa -out rootca.key 2048

# make a root certificate based on this key
openssl req -x509 -new -nodes -key rootca.key -days 20000 -out rootca.crt

# generate a new key
openssl genrsa -out user.key 2048

# make a certificate signing request
openssl req -new -key user.key -out user.csr

# sign this request by root certificate
openssl x509 -req -in user.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out user.crt -days 20000

# generate DH parameters for Diffie–Hellman key exchange
openssl dhparam -dsaparam -out dh2048.pem 2048

Then i load the root certificate in the client
C++:
asio::ssl::context ctx(asio::ssl::context::tlsv12_client);
ctx.load_verify_file("rootca.crt");

and the other files in the server like this
C++:
context.set_options(
        asio::ssl::context::default_workarounds | // Implement various bug workarounds
        asio::ssl::context::no_sslv2 |            // Disable SSL v2
        asio::ssl::context::no_sslv3 |            // Disable SSL v3
        asio::ssl::context::no_tlsv1 |            // Disable TLS v1
        asio::ssl::context::no_tlsv1_1 |          // Disable TLS v1.1
        asio::ssl::context::single_dh_use         // Always create a new key when using tmp_dh parameter
);
context.use_certificate_chain_file("user.crt");
context.use_private_key_file("user.key", asio::ssl::context::pem);
context.use_tmp_dh_file("dh2048.pem");

I wonder if this is sufficient to encrypt the communication or if i should now create a symetric key on the client and send it to the server so that they both use it on top of SSL to encrypt the communication. Isn't the SSL socket sufficient?
 
Back
Top