0

Question

I would like to asymmetrically encrypt messages with a public key that I have been given in advance. How can I go about doing that? Some example code in C# would be much appreciated.

What do I need it for?

Our service receives SMS message bundles through a REST API and delivers the message text to the end user device, e.g. cellphones. In order to bill the customer, we store the bundle information along with a success/failure status in a database.

We give the customer the option of not storing the message text so that customer support cannot snoop in the database, but we would also like to provide the option of storing the message text in encrypted form. Due to the way the service is structured, we would need to store the encryption key in the database, so symmetric encryption would not be a good choice.

Research

Finding an answer to this question is hard for the following reasons:

  • Most articles about asymmetric encryption are about key exhange, i.e. where two actors are trying to establish a shared secret in real time over an insecure channel. The articles are so common that at one point I thought Diffie-Hellman could only be used for key exchange.
  • The articles that don't, mostly talk about RSA. RSA is not a good fit for arbitrary message text.
  • On StackExchange, most answers link to the Wikipedia page on hybrid cryptosystems, which at the time of writing is rather sparse: it only provides a theoretic example without naming any algorithms or systems.

I also understand that it's a bad idea to roll your own crypto, so even if I knew how to pick the algorithms for a hybrid system, I would prefer to use a simple ready-made package.

1

2 Answers 2

0

Use ECIES

I'm recommending it mostly because it's the only hybrid cryptosystem I could find that fits the use case, and because this page has a nice diagram as well as links to several implementations in different languages.

For C# applications that target Windows, I would use the SecurityDriven.NET Inferno NuGet package. It uses the Windows CNG instead of rolling its own primitives, has been professionally audited, and it gives you very few options to shoot yourself in the foot with.

The example on SecurityDriven.NET is not very easy to understand though, so I've provided my own below (for .NET 5):

using System;
using System.Text;
using System.Security.Cryptography;
using SecurityDriven.Inferno;
using SecurityDriven.Inferno.Extensions;
using System.Collections.Generic;

public class Program
{
    private class Server
    {
        public class DatabaseRecord
        {
            public byte[] EphemeralKey { get; init; }
            public byte[] Ciphertext { get; init; }
        }

        private List<DatabaseRecord> database = new();

        public Server(byte[] clientPublicKey)
        {
            ClientPublicKey = CngKeyExtensions.ToPublicKeyFromBlob(clientPublicKey);
        }

        public CngKey ClientPublicKey { get; }
        public IReadOnlyList<DatabaseRecord> Database => database;

        public void Encrypt(string plaintext)
        {
            var bundle = ClientPublicKey.GetSharedEphemeralDhmSecret();
            var ciphertext = SuiteB.Encrypt(masterKey: bundle.SharedSecret, plaintext: Encoding.UTF8.GetBytes(plaintext));
            database.Add(new DatabaseRecord { EphemeralKey = bundle.EphemeralDhmPublicKeyBlob, Ciphertext = ciphertext });
        }
    }

    private class Client
    {
        private CngKey privateKey;

        public Client()
        {
            privateKey = CngKeyExtensions.CreateNewDhmKey();
        }

        public byte[] PublicKey => privateKey.GetPublicBlob();

        public string Decrypt(byte[] ephemeralKey, byte[] ciphertext)
        {
            var sharedSecret = privateKey.GetSharedDhmSecret(CngKeyExtensions.ToPublicKeyFromBlob(ephemeralKey));
            var plaintext = SuiteB.Decrypt(masterKey: sharedSecret, ciphertext: ciphertext);
            return Encoding.UTF8.GetString(plaintext);
        }
    }

    public static void Main()
    {
        // One time setup
        var client = new Client();
        var server = new Server(client.PublicKey);

        // Client makes a request
        var plaintext = "Hello Asymmetric Encryption!";
        server.Encrypt(plaintext);

        // Client recovers a message
        var record = server.Database[0];
        var recoveredPlaintext = client.Decrypt(record.EphemeralKey, record.Ciphertext);
        Console.WriteLine(recoveredPlaintext);
    }
}
0

Recommending specific tools (including libraries) is out of scope for this site - things change too fast, and are recommendations are too often subjective - but I'll give you a few examples of things you can use.

  1. Use an existing hybrid cryptosystem, such as GNU Privacy Guard (gpg) or anything else implementing the OpenPGP standard. Once all nodes have their own keypair, with each recipient having the message sender's public key and the sender has the recipients' public keys, creating signed-and-encrypted messages is easy: gpg -s -e -r <recipient> <file>. Of course, that's made for command line use; for programmatic use you might want to P/Invoke the API or use a compatible .NET library such as BouncyCastle.

  2. Use an all-in-one library that handles the asymmetric and symmetric crypto for you, ideally taking care of things like message authentication. Libsodium (a portable clone and extension of NaCl) is one such. The documentation includes instructions on generating key pairs, using them to exchange symmetric keys, and using the symmetric keys secure messages.

  3. Use a lower-level library. This is NOT RECOMMENDED as there are lots of considerations that go into secure cryptosystems that are easy to get wrong. Nonetheless, the fundamentals of hybrid crypto are simple enough:

    1. Key pairs for all parties, everybody else knows the other parties' public keys
    2. Use key pair to securely exchange a symmetric key, either by:
      • Generating it on one end and encrypting it to the other end's public key
      • Using a Diffie-Hellman (or similar) key exchange signed by the private key
    3. Use symmetric key to encrypt (and authenticate) the message
    4. Send the encrypted-and-authenticated message to the recipient, along with the key exchange data (if needed)
    5. Recipient retrieves the symmetric key, either by:
      • Decrypting the encrypted symmetric key with their private key
      • Completing the key exchange (if that didn't happen earlier)
    6. Recipient uses the symmetric key to decrypt (and verify) the message

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .