0

I can use the following code to

  • generate keypairs for two users
  • the first user encrypts a message using their private key and the second user's public key
  • the second user decrypts the message using their private key and the first user's public key

This works great for 1 to 1 encrypted communication but I want to know how to extend this so that the first user can encrypt a single message for both the second user and the third user. The message should be decryptable using the second user's private key or the third user's private key.

public class EncryptionService
{
    private IBlockCipher engine = new DesEngine(); //the cipher engine for encryption
    private IAsymmetricCipherKeyPairGenerator keyGen = new X25519KeyPairGenerator(); //keypair generator for X25519 key agreement scheme
    private AsymmetricKeyParameter myPrivateKey;
    private AsymmetricKeyParameter myPublicKey;
    private AsymmetricKeyParameter opponentPrivateKey;
    private AsymmetricKeyParameter opponentPublicKey;

    public void RunExchange()
    {
        var plainTextMessage = AsciiStringToBytes("Hello Sir,");

        GenerateKeys();

        var sharedSecret = GetSharedSecret(myPrivateKey, opponentPublicKey);
        Console.WriteLine($"Shared secret: {BytesToAsciiString(sharedSecret)}");

        var encryptedMessage = Encrypt(sharedSecret, plainTextMessage);
        Console.WriteLine($"Encrypted message: {BytesToAsciiString(encryptedMessage)}");

        var decryptedMessage = Decrypt(sharedSecret, encryptedMessage);
        Console.WriteLine($"Decrypted message: {BytesToAsciiString(decryptedMessage)}");

        var opponentSharedSecret = GetSharedSecret(opponentPrivateKey, myPublicKey);
        Console.WriteLine($"Opponent shared secret: {BytesToAsciiString(opponentSharedSecret)}");

        var opponentDecryptedMessage = Decrypt(opponentSharedSecret, encryptedMessage);
        Console.WriteLine($"Opponent decrypted message: {BytesToAsciiString(opponentDecryptedMessage)}");
    }

    private byte[] GetSharedSecret(AsymmetricKeyParameter myPrivateKey, AsymmetricKeyParameter opponentPublicKey)
    {
        var keyAgreement = new X25519Agreement();
        keyAgreement.Init(myPrivateKey);
        byte[] sharedSecret = new byte[keyAgreement.AgreementSize];
        keyAgreement.CalculateAgreement(opponentPublicKey, sharedSecret, 0);

        Console.WriteLine(Hex.ToHexString(sharedSecret));

        return sharedSecret;
    }

    private (AsymmetricKeyParameter privateKey, AsymmetricKeyParameter publicKey) GenerateKeyPair()
    {
        keyGen.Init(new KeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(), 256));
        var pair = keyGen.GenerateKeyPair();
        return (pair.Private, pair.Public);
    }

    private void GenerateKeys()
    {
        (myPrivateKey, myPublicKey) = GenerateKeyPair();
        (opponentPrivateKey, opponentPublicKey) = GenerateKeyPair();
    }

    private byte[] Encrypt(byte[] key, byte[] plainTextBytes)
    {
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(engine));
        cipher.Init(true, new KeyParameter(key));
        byte[] rv = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
        int tam = cipher.ProcessBytes(plainTextBytes, 0, plainTextBytes.Length, rv, 0);
        try
        {
            cipher.DoFinal(rv, tam);
        }
        catch (Exception ce)
        {
            Console.WriteLine(ce.StackTrace);
        }
        return rv;
    }

    private byte[] Decrypt(byte[] key, byte[] cipherText)
    {
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(engine));
        cipher.Init(false, new KeyParameter(key));
        byte[] rv = new byte[cipher.GetOutputSize(cipherText.Length)];
        int tam = cipher.ProcessBytes(cipherText, 0, cipherText.Length, rv, 0);
        try
        {
            cipher.DoFinal(rv, tam);
        }
        catch (Exception ce)
        {
            Console.WriteLine(ce.StackTrace);
        }
        return rv;
    }

    private static byte[] AsciiStringToBytes(string text)
    {
        return System.Text.Encoding.ASCII.GetBytes(text);
    }

    private static string BytesToAsciiString(byte[] bytes)
    {
        return System.Text.Encoding.ASCII.GetString(bytes);
    }
}
Toxic Tom
  • 174
  • 12
  • You can't. That's the whole point of private and public key pairs. If you encrypt something with a public key only the respective private key can decrypt it. You have to encrypt it separately for each recipient – derpirscher Jul 17 '22 at 18:53
  • 1
    This is easy with hybrid encryption. Let's say A wants to encrypt a message to B and let's say E_pub_B(x) represents the encryption of value x with B's public key. Also, let AES_k(M) represent the encryption of message M with AES key k. Then A can encrypted message M to be by sending E_pub_B(k) + AES_k(M). To decrypt, B uses their private key to recover k first, then uses k to decrypt the message. This extends easily to multiple recipients. To send to B and C, A sends E_pub_B(k) + E_pub_C(k) + AES_k(M). B can recover k from E_pub_B(k) and C can recover k from E_pub_C(k). – President James K. Polk Jul 17 '22 at 20:57
  • Encrypt a data key using multiple shared secrets, or use XOR to derive a value that can be XOR'ed again to get the data key. [Other **secret sharing**](https://en.wikipedia.org/wiki/Secret_sharing) techniques are of course possible. – Maarten Bodewes Jul 18 '22 at 03:37
  • @PresidentJamesK.Polk Sorry I didn't follow your solution. Could you explain it in a different way? – Toxic Tom Jul 18 '22 at 10:50
  • @MaartenBodewes That sounds like a good solution. I can get a shared secret for user 1 and user 2, and another shared secret for user 1 and user 3. How can I then "encrypt a data key using multiple shared secrets"? – Toxic Tom Jul 18 '22 at 10:52
  • 1
    Uh, so you derive a key for each, then create a message consisting of your public key (assuming you keep using DH), and a wrapped data key, encrypted by the derived key from the DH key agreement, followed of course by the message encrypted with the data key. That will still require you to send N keys ID's, but keys are only 16 bytes or so and an identifier doesn't take that much space either (the receivers must recognize which key is supposed to be for them). – Maarten Bodewes Jul 18 '22 at 12:41
  • @MaartenBodewes Thanks for the explanation! I am going to try this implementation. So the message will contain: a) data (encrypted with data key), b) data key (encrypted with shared secret 1), c) data key (encrypted with shared secret 2), ... – Toxic Tom Jul 18 '22 at 15:59
  • 1
    Yes, you got it, and your public key if you're using DH key agreement of course. – Maarten Bodewes Jul 18 '22 at 22:33

0 Answers0