How to use BouncyCastle's Diffie-Hellman in C#?
Asked Answered
M

2

5

I'm writing an app that'll exchange data between a phone and a Windows PC, and I want to protect the data sent with key generated with a Diffie-Hellman exchange.

I'm trying to use BouncyCastle for that, but the almost non-existant documentation for the C# implementation has me stumped.

What I want to know is: what's the workflow for generating a DH key and computing a shared key when the other side's key is received? (I'm assuming I can send my key as a string and I can work with the other side's key as a string.) What objects/methods do I use in C# for that?

Mollusc answered 17/3, 2015 at 0:24 Comment(0)
M
11

Alright, after a lot of trial, I got it working. Posting answer in case someone else needs it.

I'll assume the reader (1) knows what Diffie-Hellman is and what it's useful for (read here for details) and (2) already imported Bouncycastle to a .NET project via NuGet.

Imports you'll need:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;

How to generate g and p:

public DHParameters GenerateParameters()
{
    var generator = new DHParametersGenerator();
    generator.Init(BitSize, DefaultPrimeProbability, new SecureRandom());
    return generator.GenerateParameters();
}

Wanna get g and p as strings?

public string GetG(DHParameters parameters)
    {
        return parameters.G.ToString();
    }

public string GetP(DHParameters parameters)
    {
        return parameters.P.ToString();
    }

How to generate a and A:

public AsymmetricCipherKeyPair GenerateKeys(DHParameters parameters)
    {
    var keyGen = GeneratorUtilities.GetKeyPairGenerator("DH");
    var kgp = new DHKeyGenerationParameters(new SecureRandom(), parameters);
    keyGen.Init(kgp);
    return keyGen.GenerateKeyPair();
}

Wanna read a and A as a string?

// This returns A
public string GetPublicKey(AsymmetricCipherKeyPair keyPair)
{
    var dhPublicKeyParameters = _generatedKey.Public as DHPublicKeyParameters;
    if (dhPublicKeyParameters != null)
    {
        return dhPublicKeyParameters.Y.ToString();
    }
    throw new NullReferenceException("The key pair provided is not a valid DH keypair.");
}

// This returns a
public string GetPrivateKey(AsymmetricCipherKeyPair keyPair)
{
    var dhPrivateKeyParameters = _generatedKey.Private as DHPrivateKeyParameters;
    if (dhPrivateKeyParameters != null)
    {
        return dhPrivateKeyParameters.X.ToString();
    }
    throw new NullReferenceException("The key pair provided is not a valid DH keypair.");
}

To import the parameters from strings just do:

var importedParameters = new DHParameters(p, g);

To generate b and B just use GenerateKeys() with importedParameters instead of the generated parameters.

Let's say you generated b and B and already got p, g and A. To compute the shared secret:

public BigInteger ComputeSharedSecret(string A, AsymmetricKeyParameter bPrivateKey, DHParameters internalParameters)
{
    var importedKey = new DHPublicKeyParameters(new BigInteger(A), internalParameters);
    var internalKeyAgree = AgreementUtilities.GetBasicAgreement("DH");
    internalKeyAgree.Init(bPrivateKey);
    return internalKeyAgree.CalculateAgreement(importedKey);
}

Repeat for A and now you have a shared secret between 2 clients, ready to be used to encrypt communications.

Hope this is useful.

Mollusc answered 1/5, 2015 at 1:41 Comment(6)
congratulations on having some of the only decent stuff out there for someone trying to get some basic stuff going with so little documentation.... you're a gentleman and a scholar for adding this detail to your own question :-)Igorot
How do you generate the equivalent to openssl dhparam -out dhparam.pem 2048?Ptolemaist
What to use as Bitsize? If I use 1024 it takes about 2 -10 sec to generate parameters... And 2048 ist not returning after 60 sec.Remainderman
@Remainderman you'll have to do some measuring to determine the optimal bitsize. It highly depends on your hardware's capabilities.Senescent
@Mollusc but what is secure? Here on Stackoverflow I find suggestions from 256 to 3072?Remainderman
@Remainderman It's actually a tradeoff between bitsize and time. You should use the biggest bitsize that takes a reasonable time to generate; that increases your security expectations. That's why you should measure on the machine that'll run the algorithm.Senescent
N
1

An implementation of the Diffie-Hellman key agreement based on PEM file format BouncyCastle.Diffie-hellman

Example
// Public Key Alice
var pubAlice = @"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOkLo3q6MN3XS5xlY3OowqMkvPrYz
j4hLVJ2Wkuob3KQb1QidaAQsJ6Azy0yTuBanL4iy+dewA3YjejBMZEoh6w==
-----END PUBLIC KEY-----
";
// EC-Private Key Alice
var priAlice = @"-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC9LMxwIwKThjtaUAJbJBCU0vFa+H8G98p/Z9JLYmEehoAoGCCqGSM49
AwEHoUQDQgAEOkLo3q6MN3XS5xlY3OowqMkvPrYzj4hLVJ2Wkuob3KQb1QidaAQs
J6Azy0yTuBanL4iy+dewA3YjejBMZEoh6w==
-----END EC PRIVATE KEY-----
";

// Public Key Bob
var pubBob = @"-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnDMGlBFH7jbHHAYgdPR7247xqzRF
Y1HFy4HfejSgUKBxgj6biZUwSbNKuino7ObZnqrnJayWJZ7f4Eb6XuT6yQ==
-----END PUBLIC KEY-----
";

// EC-Private Key Bob
var priBob = @"-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGqA4f7o5oBF5FgEQtNmz6fWKg/OcPPUORMX3uRc7sduoAoGCCqGSM49
AwEHoUQDQgAEnDMGlBFH7jbHHAYgdPR7247xqzRFY1HFy4HfejSgUKBxgj6biZUw
SbNKuino7ObZnqrnJayWJZ7f4Eb6XuT6yQ==
-----END EC PRIVATE KEY-----
";

var secretAlice = DiffieHellmanKeyAgreementUtil.GetPairKey(priAlice, pubBob);
var secretBob = DiffieHellmanKeyAgreementUtil.GetPairKey(priBob, pubAlice);

Console.WriteLine($"secretAlice: {secretAlice}");
Console.WriteLine($"secretBob: {secretBob}");

Output secretAlice: RGZcMLnsXJqbQ/JGIIl61l/XpIgSL43Ync+16YKyMuQ= secretBob: RGZcMLnsXJqbQ/JGIIl61l/XpIgSL43Ync+16YKyMuQ=

Negris answered 13/10, 2021 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.