Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error in 1.8.5.2 when upgrading from 1.8.5 #22

Closed
jjxtra opened this issue Dec 6, 2019 · 5 comments
Closed

Error in 1.8.5.2 when upgrading from 1.8.5 #22

jjxtra opened this issue Dec 6, 2019 · 5 comments

Comments

@jjxtra
Copy link

jjxtra commented Dec 6, 2019

It appears that something in version 1.8.5.2 broke ECPrivateKeyParameters. Here is working code from version 1.8.5:

byte[] privateKeyBytes = new byte[16]; // fails for pretty much any byte array length from 16 to 64
for (int i = 0; i < privateKeyBytes.Length; i++)
{
    privateKeyBytes[i] = 0xFF;
}

X9ECParameters curve = SecNamedCurves.GetByName("secp256k1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);

ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters(new BigInteger(privateKeyBytes), domain);

When called in version 1.8.5.2, I get an error on the last line of code that parameter d is out of bounds from 0 to n - 1. This error does not happen in version 1.8.5.

Following this tutorial: https://blog.todotnet.com/2018/02/public-private-keys-and-signing/

@bcgit
Copy link

bcgit commented Dec 6, 2019

The error does not happen in the earlier release as it was not validating private key values. It was wrong in the earlier release, it's just explicitly wrong now.

The reason for this is that the effective key is new BigInteger(privateKeyBytes).mod(curve.N) which in the example case is the same as BigInteger.ValueOf(-1).mod(curve.N). I won't ask why you are using a negative number to try and generate a private value.

I don't think the tutorial author's intention was to show people how to generate random keys but to calculate a public key from a given private value.

If it was their intention to show people how to generate random keys they are totally incorrect about how to do it safely. Following this approach you could easily generate a private value that was only a few bits in length thinking you'd generated a much bigger one.

Please use the ECKeyPairGenerator to generate private keys. It will do it correctly.

@jjxtra
Copy link
Author

jjxtra commented Dec 6, 2019

Good to know, I am no expert in this field. Is there documentation with a C# example that covers the proper way to generate a public and private key, sign a message with the private key and finally validate the signature using only the public key?

@jjxtra
Copy link
Author

jjxtra commented Dec 6, 2019

Leaving my notes for posterity, there is not a whole lot to find through Internet searches.

How to generate public and private keys in C#, sign messages with the private key and verify the message with only the public key.

How to generate public and private key pair:

int strength = 256; // can be 384 or 521 as well
ECKeyPairGenerator keyGenerator = new ECKeyPairGenerator("ECDSA");
keyGenerator.Init(new KeyGenerationParameters(new SecureRandom(), strength));
AsymmetricCipherKeyPair pair = keyGenerator.GenerateKeyPair();
ECPrivateKeyParameters privateKey = pair.Private as ECPrivateKeyParameters;
ECPublicKeyParameters publicKey = pair.Public as ECPublicKeyParameters;
byte[] privateKeyBytes = privateKey.D.ToByteArray();
byte[] publicKeyBytes = publicKey.Q.GetEncoded();

How to get public key from private key:

X9ECParameters curve = SecNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
BigInteger d = new BigInteger(privateKeyBytes);
Org.BouncyCastle.Math.EC.ECPoint q = domain.G.Multiply(d);
ECPublicKeyParameters publicKey = new ECPublicKeyParameters("ECDSA", q, domain);
byte[] publicKeyBytes = publicKey.Q.GetEncoded();

How to sign a message using a private key:

string signingAlgorithm = "SHA-512withECDSA";
X9ECParameters curve = SecNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters("ECDSA", new BigInteger(privateKeyBytes), domain);
ISigner signer = SignerUtilities.GetSigner(signingAlgorithm);
signer.Init(true, keyParameters);
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
byte[] signature = signer.GenerateSignature();

How to validate a signed message using just the public key:

string signingAlgorithm = "SHA-512withECDSA";
X9ECParameters curve = SecNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
Org.BouncyCastle.Math.EC.ECPoint q = curve.Curve.DecodePoint(publicKeyBytes);
ECPublicKeyParameters keyParameters = new ECPublicKeyParameters("ECDSA", q, domain);
ISigner signer = SignerUtilities.GetSigner(signingAlgorithm);
signer.Init(false, keyParameters);
signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
byte[] signatureBytes = Convert.FromBase64String(signature);
return signer.VerifySignature(signatureBytes);

@bcgit
Copy link

bcgit commented Dec 6, 2019

Yes, ECDSA uses a random value in signature calculations. There is a specific deterministic version as well, but that is not the one being used above.

Just a precaution, use BigInteger d = new BigInteger(1, privateKeyBytes) rather than BigInteger d = new BigInteger(privateKeyBytes). The 2 arg constructor with the 1 will treat privateKeyBytes as an encoding of an unsigned positive number.

@jjxtra
Copy link
Author

jjxtra commented Dec 6, 2019

Ok good. If there is documentation you want me to add this to, I'd be happy to help do that.

Out of curiosity, how do I create a deterministic signer?
Is ToByteArrayUnsigned safe to use to store the private key?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant