I am working on signature verification using BouncyCastle using C# and .NET 4.7.
I was following an answer here on SO that explained the verification process using SHA-256withECDSA here.
As per that my code for verifying signature for that example using both approaches works well as per below code fragment.
public static void Main(string[] args)
{
//SHA256 With ECDSA - Baseline that works. Values received from BC answer on SO
String sigSHA256
= "e1f5cecccedfe5228d9331098e84b69a0675cdd9ac066ecfada7fea761f52a4cde902a0abd362883127230326fb556af14e894d39a3e14437aaa4134a3476c84";
String msgSHA256 = "00000000dcb320137ddd6f825660750ab655219fad66951c64f0420be8ac902975197ed2b0da54cd3d502d34dd04c8d74b2958a0b8792ae4730df6d25a6969bcad9f93a7d6229e5a0100000017cf5242732bba21a0b0e7dad7102cf7bdb2c8d7a665045816a886d7";
String pubSHA256 = "b679e27513e2fff8fdeb54409c242776f3517f370440d26885de574a0b0e5309a9de4ea055b0bf302d9f00875f80e28cd29bb95a48aa53746d7de9465123dbb7";
VerifySHA256Bouncy(HexStringToByteArray(msgSHA256), HexStringToByteArray(sigSHA256), HexStringToByteArray(pubSHA256));
Console.ReadLine();
}
public static void VerifySHA256Bouncy(byte[] message, byte[] signature, byte[] pubkey)
{
BigInteger x = new BigInteger(1, pubkey.Take(32).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(32).ToArray());
X9ECParameters ecParams = NistNamedCurves.GetByName("P-256");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
var G = ecParams.G;
Org.BouncyCastle.Math.EC.ECCurve curve = ecParams.Curve;
Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);
ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
// expected format is SEQUENCE {INTEGER r, INTEGER s}
var derSignature = new DerSequence(
// first 32 bytes is "r" number
new DerInteger(new BigInteger(1, signature.Take(32).ToArray())),
// last 32 bytes is "s" number
new DerInteger(new BigInteger(1, signature.Skip(32).ToArray())))
.GetDerEncoded();
var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
verifier.Init(false, pubkeyParam);
verifier.BlockUpdate(message, 0, message.Length);
bool result = verifier.VerifySignature(derSignature);
Console.WriteLine("result: " + result);
}
I am trying to adapt it to my use case which is verifying a SHA-384withECDSA signature. The only difference I found was the keylengths to be different. For SHA-256withECDSA the public key length used translated to a byte array of 64. Hence in the implementation I am doing:
BigInteger x = new BigInteger(1, pubkey.Take(32).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(32).ToArray());
For SHA-384withECDSA, I have tweaked it as below.
public static void Main(string[] args)
{
//SHA384 With ECDSA - Values received from Java integration example
String sig384
= "306402304f070f3cb570f92f573385880aaa58febc06b6842be59e8f56d196c63a5aacbb7124493bee84e0331c36eb9c4e3e27db0230628c89f28a53e4c2ed089abe2ada179cc64e3eb33204b0be07cdd34bd3cd5ed4d6f0aaf380cc0d436faee15509dadc14";
String msg384 = "{\"transaction\":{\"amount\":\"64.50\",\"id\":\"248686\",\"type\":\"SALE\",\"result\":\"APPROVED\",\"card\":\"XXXXXXXXXXXX1111\",\"csc\":\"999\",\"authorization-code\":\"TAS231\",\"batch-string-id\":\"44\",\"display-message\":\"Transaction approved\",\"result-code\":\"000\",\"exp-date\":\"1218\"},\"payloadType\":\"transaction\"}";
String pub384 = "307a301406072a8648ce3d020106092b240303020801010c0362000422ffee50bdb73df2698df79b8f62fa06c005acfb5d8e92c3088053620da94eb1f8978c769ace34231b51e41394b873b07a673dfb08e14e975fb26355a639f1be4339e787390ca4c8dd6463c76bc8421457906aafa8b9981445276fde833c136b";
VerifySHA384Bouncy(Encoding.ASCII.GetBytes(msg384), HexStringToByteArray(sig384), HexStringToByteArray(pub384));
Console.ReadLine();
}
public static void VerifySHA384Bouncy(byte[] message, byte[] signature, byte[] pubkey)
{
BigInteger x = new BigInteger(1, pubkey.Take(62).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(62).ToArray());
X9ECParameters ecParams = NistNamedCurves.GetByName("P-384");
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
var G = ecParams.G;
Org.BouncyCastle.Math.EC.ECCurve curve = ecParams.Curve;
Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);
ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
// expected format is SEQUENCE {INTEGER r, INTEGER s}
var derSignature = new DerSequence(
// first 32 bytes is "r" number
new DerInteger(new BigInteger(1, signature.Take(62).ToArray())),
// last 32 bytes is "s" number
new DerInteger(new BigInteger(1, signature.Skip(62).ToArray())))
.GetDerEncoded();
var verifier = SignerUtilities.GetSigner("SHA-384withECDSA");
verifier.Init(false, pubkeyParam);
verifier.BlockUpdate(message, 0, message.Length);
bool result = verifier.VerifySignature(derSignature);
Console.WriteLine("result: " + result);
}
The keylength for this example translates to a byte array of 124. Hence in my code I am doing
BigInteger x = new BigInteger(1, pubkey.Take(62).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(62).ToArray());
My code for verifying a SHA-384withECDSA signature throws an exception:
System.ArgumentException
HResult=0x80070057
Message=value invalid in Fp field element
Parameter name: x
Source=BouncyCastle.Crypto
StackTrace:
at Org.BouncyCastle.Math.EC.FpFieldElement..ctor(BigInteger q, BigInteger r, BigInteger x)
at Org.BouncyCastle.Math.EC.FpCurve.FromBigInteger(BigInteger x)
at Org.BouncyCastle.Math.EC.ECCurve.CreatePoint(BigInteger x, BigInteger y, Boolean withCompression)
at Org.BouncyCastle.Math.EC.ECCurve.CreatePoint(BigInteger x, BigInteger y)
at SignatureVerification.Program.VerifySHA384Bouncy(Byte[] message, Byte[] signature, Byte[] pubkey)
I am not sure if I am way off in my approach to the problem or I am comparing apples to oranges, I am not finding enough examples for SHA-384withECDSA to be able to figure this out. Any help would be greatly appreciated.
There are several issues:
An analysis with an ASN.1 Parser shows that the public key is given in X.509 format, s. e.g. here. I.e. the raw key x|y results as the last 2 * 48 = 96 bytes:
pub384 = pub384.Substring(pub384.Length - 96 * 2); // 96 * 2 due to the hex encoding
The determination of x and y coordinate is (x and y are 48 bytes each):
BigInteger x = new BigInteger(1, pubkey.Take(48).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(48).ToArray());
Furthermore, the analysis with the ASN.1 parser reveals that the public key belongs to the curve brainpoolp384t1:
X9ECParameters ecParams = ECNamedCurveTable.GetByName("brainpoolp384t1");
Also, the signature sig384 is already specified in ASN.1/DER format and not in r|s (IEEE P1363) format, so the determination of derSignature can be omitted:
bool result = verifier.VerifySignature(signature); // true
With these changes, the signature is successfully verified.
Full code:
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Linq;
using System.Text;
...
public static void Main(string[] args)
{
string sig384 = "306402304f070f3cb570f92f573385880aaa58febc06b6842be59e8f56d196c63a5aacbb7124493bee84e0331c36eb9c4e3e27db0230628c89f28a53e4c2ed089abe2ada179cc64e3eb33204b0be07cdd34bd3cd5ed4d6f0aaf380cc0d436faee15509dadc14";
string msg384 = "{\"transaction\":{\"amount\":\"64.50\",\"id\":\"248686\",\"type\":\"SALE\",\"result\":\"APPROVED\",\"card\":\"XXXXXXXXXXXX1111\",\"csc\":\"999\",\"authorization-code\":\"TAS231\",\"batch-string-id\":\"44\",\"display-message\":\"Transaction approved\",\"result-code\":\"000\",\"exp-date\":\"1218\"},\"payloadType\":\"transaction\"}";
string pub384 = "307a301406072a8648ce3d020106092b240303020801010c0362000422ffee50bdb73df2698df79b8f62fa06c005acfb5d8e92c3088053620da94eb1f8978c769ace34231b51e41394b873b07a673dfb08e14e975fb26355a639f1be4339e787390ca4c8dd6463c76bc8421457906aafa8b9981445276fde833c136b";
pub384 = pub384.Substring(pub384.Length - 96 * 2); // Fix 1
VerifySHA384Bouncy(Encoding.ASCII.GetBytes(msg384), HexStringToByteArray(sig384), HexStringToByteArray(pub384));
}
public static void VerifySHA384Bouncy(byte[] message, byte[] signature, byte[] pubkey)
{
BigInteger x = new BigInteger(1, pubkey.Take(48).ToArray()); // Fix 2
BigInteger y = new BigInteger(1, pubkey.Skip(48).ToArray());
X9ECParameters ecParams = ECNamedCurveTable.GetByName("brainpoolp384t1"); // Fix 3
ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
ECPoint G = ecParams.G;
ECCurve curve = ecParams.Curve;
ECPoint q = curve.CreatePoint(x, y);
ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
ISigner verifier = SignerUtilities.GetSigner("SHA-384withECDSA");
verifier.Init(false, pubkeyParam);
verifier.BlockUpdate(message, 0, message.Length);
bool result = verifier.VerifySignature(signature); // Fix 4
Console.WriteLine("result: " + result); // result: True
}
private static byte[] HexStringToByteArray(string str)
{
return Hex.Decode(str);
}
Related
I have this nodejs snippet which I want to implement in C#.
The SignTransaction function takes in a seedHex string (length: 64) and a TransactionHex string, a signature is created using secp256k1 algorithm and appended to the end of the TransactionHex.
const sha256 = require('sha256');
const EC = require('elliptic').ec;
const ec = new EC("secp256k1");
// Serialize a number into an 8-byte array. This is a copy/paste primitive, not worth
// getting into the details.
function uvarint64ToBuf (uint) {
const result = [];
while (uint >= 0x80) {
result.push((uint & 0xFF) | 0x80);
uint >>>= 7;
}
result.push(uint | 0);
return new Buffer(result);
}
// Sign transaction with seed
function signTransaction (seed, txnHex) {
const privateKey = ec.keyFromPrivate(seed);
const transactionBytes = new Buffer(txnHex, 'hex');
const transactionHash = new Buffer(sha256.x2(transactionBytes), 'hex');
const signature = privateKey.sign(transactionHash);
const signatureBytes = new Buffer(signature.toDER());
const signatureLength = uvarint64ToBuf(signatureBytes.length);
const signedTransactionBytes = Buffer.concat([
transactionBytes.slice(0, -1),
signatureLength,
signatureBytes
])
return signedTransactionBytes.toString('hex');
}
example transaction hex
01efa5060f5fdae5ed9d90e5f076b2c328a64e347d0c87e8e3317daec5a44fe7c8000102bd53e48625f49e60ff6b7a934e3871b54cc2a93a8737352e8320549e42e2322bab0405270000167b22426f6479223a2248656c6c6f20576f726c64227de807d461f4df9efad8a0f3f716002102bd53e48625f49e60ff6b7a934e3871b54cc2a93a8737352e8320549e42e2322b0000
example signed transaction hex returned from the javascript function
01efa5060f5fdae5ed9d90e5f076b2c328a64e347d0c87e8e3317daec5a44fe7c8000102bd53e48625f49e60ff6b7a934e3871b54cc2a93a8737352e8320549e42e2322bab0405270000167b22426f6479223a2248656c6c6f20576f726c64227de807d461f4df9efad8a0f3f716002102bd53e48625f49e60ff6b7a934e3871b54cc2a93a8737352e8320549e42e2322b00473045022100c36e2b80f2160304cf640b1296244e7a3873aacf2831098bca3727ad06f4c270022007d3697ceef266a05ad70219d92fbd570f6ec7a5731aaf02718213067d42d1cf
If you notice the signed hex is TransactionHex + 47 (length of signature) + 473045022100c36e2b80f2160304cf640b1296244e7a3873aacf2831098bca3727ad06f4c270022007d3697ceef266a05ad70219d92fbd570f6ec7a5731aaf02718213067d42d1cf (actual signature)
I am trying to use C# Bouncy Castle library in Unity3d to generate the signature part but no success.
This is the C# code (source)
public string GetSignature(string privateKey, string message)
{
var curve = SecNamedCurves.GetByName("secp256k1");
var domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
var keyParameters = new ECPrivateKeyParameters(new BigInteger(privateKey, 16), domain);
var signer = new ECDsaSigner(new HMacDsaKCalculator(new Sha256Digest()));
signer.Init(true, keyParameters);
var signature = signer.GenerateSignature(Encoding.UTF8.GetBytes(message));
var r = signature[0];
var s = signature[1];
var otherS = curve.Curve.Order.Subtract(s);
if (s.CompareTo(otherS) == 1)
{
s = otherS;
}
var derSignature = new DerSequence
(
new DerInteger(new BigInteger(1, r.ToByteArray())),
new DerInteger(new BigInteger(1, s.ToByteArray()))
)
.GetDerEncoded();
return Convert(derSignature);
}
public string Convert(byte[] input)
{
return string.Concat(input.Select(x => x.ToString("x2")));
}
public byte[] Convert(string input)
{
if (input.StartsWith("0x")) input = input.Remove(0, 2);
return Enumerable.Range(0, input.Length / 2).Select(x => System.Convert.ToByte(input.Substring(x * 2, 2), 16)).ToArray();
}
This does generate a signature but it is not identical to the one I mentioned above.
Signature generated from GetSignature
3044022018a06b1f2b8a1e2f5ea2a78b0d6d98ec483b9fa4345821cfef892be6c825ff4702207958160e533c801dff5e50600206e1cd938d6df74ebfa4b0347a95de67dda986
P.S. Here is the python equivalent of signing transactions for reference.
sha256.x2() in the NodeJS code hashes twice, while ECDsaSigner in the C# code does not hash at all. Therefore, in the C# code, GetSignature() must explicitly hash twice:
using Org.BouncyCastle.Crypto.Digests;
...
var messageBytes = Convert(message);
var hash = GetHash(GetHash(messageBytes));
var signature = signer.GenerateSignature(hash);
...
with
private static byte[] GetHash(byte[] data)
{
var digest = new Sha256Digest();
var hash = new byte[digest.GetDigestSize()];
digest.BlockUpdate(data, 0, data.Length);
digest.DoFinal(hash, 0);
return hash;
}
Since both codes use the deterministic ECDSA algorithm according to RFC6979, the hashes for the same input data are identical and can be compared.
The overall result is the concatenation of txnHex without the last byte, the hex encoded length of the signature and the signature in ASN.1/DER format; a possible implementation is:
var txnHex = "...";
var seed = "...";
string signature = GetSignature(seed, txnHex);
string signedTransactionBytes = txnHex.Substring(0, txnHex.Length - 2) + (signature.Length / 2).ToString("x") + signature;
Note that the uvarint64ToBuf() implementation is actually more complex, but can be simplified for the signature lengths used here.
I have an ASN.1 encoded detached CMS ECC signature - I used an online decoder to inspect it and can see the signature R and S values but I'm wondering how to actually get these 2 values.
SEQUENCE (1 elem)
Offset: 1250
Length: 2+9
(constructed)
Value:
(1 elem)
OBJECT IDENTIFIER 1.2.840.10045.4.1 ecdsaWithSHA1 (ANSI X9.62 ECDSA algorithm with SHA1)
OCTET STRING (71 byte) 30450220264E3B44C225A763A8C9134FC227398F8045F2269F43C849234E597230DCDF…
SEQUENCE (2 elem)
INTEGER (254 bit) 1732611137397374523048116562457205045056141747896936985930487632655856…
INTEGER (256 bit) 9776787720086444189183880768608824233892259816463822789589418099639594…
So far I have been trying with:
var requestSignatureBase64 = "MIAGCSqG ...";
var sigBytes = Convert.FromBase64String(requestSignatureBase64);
// var asn1 = Asn1Object.FromByteArray(sigBytes);
// var derSeq = (DerSequence)asn1;
var input = new Asn1InputStream(sigBytes);
var contendInfo = ContentInfo.GetInstance(input.ReadObject());
var sData = SignedData.GetInstance(contendInfo.Content);
var sig = new CmsSignedData(contendInfo);
Am I close? Thanks!
I am able to inspect in Watch what seems to be close to what I need (but not quite).
r and s can be determined most easily with an ASN.1 parser, e.g. with BouncyCastle:
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Utilities.Encoders;
...
byte[] der = Hex.Decode("3044021f4043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e290221009caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629");// 3043021f1acda88726e28dbdfcea74e403e7424ba9cba588777e810e42caa5d274135502207283b90135ef09e5847516a9163f4f6a4d403abb9d7632c77397e37165632619");// 3045022100bb3435689a49cb00a04b64fbd7c886d5c67eb406781b43b4ca18bb778df6d35e02201e54c928da6c4867e2b8aafb7703b00784971ef47837433bdf8a6ecb4cdbebcd");
Asn1Sequence seq = Asn1Sequence.GetInstance(der);
BigInteger[] rsBI = new BigInteger[] {
DerInteger.GetInstance(seq[0]).PositiveValue,
DerInteger.GetInstance(seq[1]).PositiveValue
};
Console.WriteLine("r: " + rsBI[0]); // r: 113546697132886645577275908676387668608557658993333016730332161870441106985
Console.WriteLine("s: " + rsBI[1]); // s: 70870178508439211896524481754448149175401343396061029910903220215172053128745
Instead of BigInteger both parts can also be represented as unsigned byte arrays or hex encoded:
byte[] rBytes = rsBI[0].ToByteArrayUnsigned();
byte[] sBytes = rsBI[1].ToByteArrayUnsigned();
Console.WriteLine("r (hex): " + Hex.ToHexString(rBytes)); // r (hex): 4043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e29
Console.WriteLine("s (hex): " + Hex.ToHexString(sBytes)); // s (hex): 9caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629
For the notation as r|s, r and s must be padded to the private key size (32 bytes in the example) since in general both parts can also be smaller than the key size:
using System.Linq;
...
byte[] rBytes32 = new byte[32 - rBytes.Length].Concat(rBytes).ToArray();
byte[] sBytes32 = new byte[32 - sBytes.Length].Concat(sBytes).ToArray();
byte[] rsBytes64 = rBytes32.Concat(sBytes32).ToArray();
Console.WriteLine("r|s: " + Hex.ToHexString(rsBytes64)); // r|s: 004043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e299caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629
For simplicity, in the last code snippet Linq was applied. But the same can be achieved by copying, e.g. with BlockCopy().
As of .NET5 there is the AsnReader class (and hex encoding is supported natively), so BC is not required.
Edit:
The signature can be loaded as follows:
var signedDataDer = Convert.FromBase64String("MIAGCSqG...");
var signers = new CmsSignedData(signedDataDer).GetSignerInfos().GetSigners();
IEnumerator signersEnumerator = signers.GetEnumerator();
signersEnumerator.MoveNext();
SignerInformation signerInfo = (SignerInformation)signersEnumerator.Current;
byte[] signatureDer = signerInfo.GetSignature();
Now r and s can be determined as described above.
I need to implement simple RSA program in C# which will encrypt and decrypt strings (I've only found working example with integers not strings). Here's what I have:
public static BigInteger euklides(BigInteger e, BigInteger phi)
{
BigInteger x = BigInteger.Zero;
BigInteger y = BigInteger.One;
BigInteger lastx = BigInteger.One;
BigInteger lasty = BigInteger.Zero;
BigInteger temp;
BigInteger phiOriginal = phi;
while (!phi.Equals(BigInteger.Zero))
{
BigInteger q = BigInteger.Divide(e, phi);
BigInteger r = e % phi;
e = phi;
phi = r;
temp = x;
x = BigInteger.Subtract(lastx, BigInteger.Multiply(q, x));
lastx = temp;
temp = y;
y = BigInteger.Subtract(lasty, BigInteger.Multiply(q, y));
lasty = temp;
}
if (lastx.CompareTo(BigInteger.Zero) > 0)
return lastx;
else
return BigInteger.Subtract(phiOriginal, BigInteger.Negate(lastx));
}
static void Main(string[] args)
{
string p = "7980724731397402975742029729905308770673214017611336839039040369204140805514764089662732987804985392082433047940750616286743290036112256089536100737587703";
string q = "11740715087896814955393784459915361965809210593358905908497824655235476226804879681331854108325808626683143689269719764318983014066100961329905775841321093";
//Convert string to BigInteger
BigInteger rsa_p = BigInteger.Parse(p);
BigInteger rsa_q = BigInteger.Parse(q);
BigInteger rsa_n = BigInteger.Multiply(rsa_p, rsa_q);
BigInteger rsa_fn = BigInteger.Multiply((rsa_p - 1), (rsa_q - 1));
BigInteger rsa_e = 13;
BigInteger rsa_d = euklides(rsa_e, rsa_fn);
string message = "hellojs";
byte[] buffer2 = Encoding.ASCII.GetBytes(message);
BigInteger m = new BigInteger(buffer2);
BigInteger C = BigInteger.ModPow(m, rsa_e, rsa_n);
Console.WriteLine("Encrypted: " + Convert.ToBase64String(C.ToByteArray()));
BigInteger M = BigInteger.ModPow(C, rsa_d, rsa_n);
byte[] decoded2 = M.ToByteArray();
if (decoded2[0] == 0)
{
decoded2 = decoded2.Where(b => b != 0).ToArray();
}
string message3 = ASCIIEncoding.ASCII.GetString(decoded2);
Console.WriteLine("Decrypted: " + message3);
Console.ReadKey();
}
My problem is decrypting - I get some random unreadable characters. It seems something is wrong with converting string to BigInteger before encryption and BigInteger to string after decryption. Do anyone have any idea how to fix it? Thanks :)
EDIT:
For values which were hardcoded, program didn't work. But for another values like:
p 8954937300280438804244939471563100247837695704192373673128222819901439388583431335500737707702230367892764837438054304217178172911151362857419350093873829
q 11326960495280396859963951277476202902476795535605385467047045654941363025487995731611833231534039530362375614741751500907989679189162164468639573092827123
it's working now. I have no idea why :)
I can't figure this out it should be super simple.
I have both the C# and BouncyCastle (also C#) crypto libraries.
I just need to give a byte array as private key, specify the curve used and get the public key.
My curve is SEC-P-256-K1/secp256k1, but really if you can just help me navigate the sea of options and classes I don't need or care about I can set that myself.
Martin's answer seems fine, but it might be done easier:
public Tuple<byte[],byte[]> GetPublicKey(byte[] privateKey)
{
BigInteger privKeyInt = new BigInteger(+1, privateKey);
var parameters = SecNamedCurves.GetByName("secp256k1");
ECPoint qa = parameters.G.Multiply(privKeyInt);
byte[] pubKeyX = qa.X.ToBigInteger().ToByteArrayUnsigned();
byte[] pubKeyY = qa.Y.ToBigInteger().ToByteArrayUnsigned();
return Tuple.Create(pubKeyX, pubKeyY);
}
This is the solution. I was confused by the Curve constructors taking a parameter 'q' which was actually supposed to be 'p' (the prime modulus of the field).
I also don't understand why I have to do so much myself such as the point multiplication to get public key. How will others who have not read EC math know to do that?
Why is there no "GetPubKey" method!?!
Oh well I hope this helps someone. User friendly is not what BouncyCastle is about I guess.
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Crypto.Parameters;
using System.Text.RegularExpressions;
public static Tuple<byte[], byte[]> GetSecp256k1PublicKey(byte[] privateKey)
{
//Secp256k1 curve variables - https://en.bitcoin.it/wiki/Secp256k1
var privKeyInt = new BigInteger(+1, privateKey);
var a = new BigInteger("0");
var b = new BigInteger("7");
var GX = new BigInteger(+1, HexStringToByteArray("79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798"));
var GY = new BigInteger(+1, HexStringToByteArray("483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8"));
var n = new BigInteger(+1, HexStringToByteArray("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141"));
var h = new BigInteger("1");
var p = new BigInteger(+1, HexStringToByteArray("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F"));
var q = h.Multiply(n).Mod(p); //Is this right???
//- http://en.wikipedia.org/wiki/Elliptic_curve_cryptography
ECCurve curve = new Org.BouncyCastle.Math.EC.FpCurve(p, a, b);
ECPoint G = new Org.BouncyCastle.Math.EC.FpPoint(curve, new FpFieldElement(p, GX), new FpFieldElement(p, GY));
var Qa = G.Multiply(privKeyInt);
byte[] PubKeyX = Qa.X.ToBigInteger().ToByteArrayUnsigned();
byte[] PubKeyY = Qa.Y.ToBigInteger().ToByteArrayUnsigned();
return Tuple.Create<byte[], byte[]>(PubKeyX, PubKeyY);
}
public static byte[] HexStringToByteArray(string hex)
{
if(String.IsNullOrWhiteSpace(hex))
return new byte[0];
hex = Regex.Replace(hex, "[\\s-\\{}]", "");
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits.");
if (!Regex.IsMatch(hex, "(^|\\A)[0-9A-Fa-f]*(\\Z|$)"))
throw new Exception("Not hex.");
byte[] arr = new byte[hex.Length >> 1];
hex = hex.ToUpper();
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
Using Vasiliy's answer I was struggling to get the same results as this tutorial but finally got the correct results after changing his code by adding .Normalize():
public Tuple<byte[],byte[]> GetPublicKey(byte[] privateKey){
BigInteger privKeyInt = new BigInteger(+1, privateKey);
var parameters = SecNamedCurves.GetByName("secp256k1");
ECPoint qa = parameters.G.Multiply(privKeyInt).Normalize();
byte[] pubKeyX = qa.XCoord.ToBigInteger().ToByteArrayUnsigned();
byte[] pubKeyY = qa.YCoord.ToBigInteger().ToByteArrayUnsigned();
return Tuple.Create(pubKeyX, pubKeyY);
}
I'd add this as a comment but alas not enough reputation.
Is there a existing method within .NET Framework (C#) to generate a 40 char (public?) fingerprint as shown below, when you have P, Q, G, Y and X?
Or would anybody know on how to achieve this?
Fingerprint: 81F68001 29D928AD BEE41B78 AA862106 CAEAC892
EDIT:
here is an example of what i'm trying to do:
string P = "00F35DBCD6D4C296D2FE9118B659D02608B76FAC94BB58B10283F20390E2B259BAC602466162E9EF3E6A1590702CAE49B681A75A878E266F1AFAE0FA89DA5CA44A1551B517A3F80A9D6C630F9E7D239B437F7402DF8055069735894CD9D4708F8777B5E4F3E6A8B2D4EEE50DB2C96BA16D3C81FEB923697D649A8B7771B10E5B3F";
string Q = "00B5AF039839043410E04C35BDDB30679969EBAC8B";
string G = "00F300A68E54DE33A09001E28EC09F2ABF5DAF208774F2514D878D5587D870C91C6DE42B4705078C6F4438765050039C2950B6DE85AFC0D12A7A5C521782CB760918DF68F385A7F177DF50AA6BA0284090454106E422FCAE5390ADC00B859A433430019E970BFA614374DE1FB40C600345EF19DC01A122E4676C614DC29D3DC2FE";
string Y = "00A5317849AF22BA6498F1EF973158C8BDA848BEB074CB141E629C927B18F29C8CE99815001BAAB2931F339B5C52A79BC3DCB0C5962C302707BA6FF1807EEB91D751BA723BB7512C20689AC5E67A1B656CDFD1BA2D4F6A44308509486AA8754B47784FC4C03E546897200388656BA5834A2CC0E18E58454FF60C1BA5411D6F50FD";
i'm missing the code for this intermediate piece. how do i convert P, Q, G, Y into the fingerprint. I tried different approaches, but i'm unsuccessful generating the fingerprint i see in the application that i'm trying to recreate.
/* convert public key (bigIntKey) into fingerprint */
var bigIntHash = new BigInteger(SHA1.Create().ComputeHash(key.ToByteArray()));
byte[] hash = bigIntHash.ToByteArray();
if (hash.Length != 20)
{
throw new IndexOutOfRangeException();
}
for (int i = 0; i < 5; i++)
{
int lf = BitConverter.ToInt32(hash, i * 4);
Debug.Write(lf.ToString("X") + " ");
}
EDIT2:
i tried this, but that is not working
// switch P, Q, G, Y and separately to make it work.
byte[] pArr = StringToByteArray(P);
pArr = Tools.Endian.ReverseBytes(pArr);
byte[] qArr = StringToByteArray(Q);
qArr = Tools.Endian.ReverseBytes(qArr);
byte[] gArr = StringToByteArray(G);
gArr = Tools.Endian.ReverseBytes(gArr);
byte[] yArr = StringToByteArray(Y);
yArr = Tools.Endian.ReverseBytes(yArr);
byte[] xArr = StringToByteArray(X);
xArr = Tools.Endian.ReverseBytes(xArr);
byte[] arr = Combine(pArr, qArr, gArr, yArr);
DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();
DSAParameters par = new DSAParameters();
par.P = pArr;
par.Q = qArr;
par.G = gArr;
par.Y = yArr;
par.X = xArr;
dsa.ImportParameters(par);
var xml = dsa.ToXmlString(true);
It will fail on the ImportParameter.
Thank you
You need to follow the OTR spec, which says the components of the key use MPI encoding, which it specifies as the length (32 bit big-endian) followed by the integer (big-endian, no leading zeros)
void Main()
{
string P = "00F35DBCD6D4C296D2FE9118B659D02608B76FAC94BB58B10283F20390E2B259BAC602466162E9EF3E6A1590702CAE49B681A75A878E266F1AFAE0FA89DA5CA44A1551B517A3F80A9D6C630F9E7D239B437F7402DF8055069735894CD9D4708F8777B5E4F3E6A8B2D4EEE50DB2C96BA16D3C81FEB923697D649A8B7771B10E5B3F";
string Q = "00B5AF039839043410E04C35BDDB30679969EBAC8B";
string G = "00F300A68E54DE33A09001E28EC09F2ABF5DAF208774F2514D878D5587D870C91C6DE42B4705078C6F4438765050039C2950B6DE85AFC0D12A7A5C521782CB760918DF68F385A7F177DF50AA6BA0284090454106E422FCAE5390ADC00B859A433430019E970BFA614374DE1FB40C600345EF19DC01A122E4676C614DC29D3DC2FE";
string Y = "00A5317849AF22BA6498F1EF973158C8BDA848BEB074CB141E629C927B18F29C8CE99815001BAAB2931F339B5C52A79BC3DCB0C5962C302707BA6FF1807EEB91D751BA723BB7512C20689AC5E67A1B656CDFD1BA2D4F6A44308509486AA8754B47784FC4C03E546897200388656BA5834A2CC0E18E58454FF60C1BA5411D6F50FD";
var publicKey =
ToMPI(HexToBytes(P))
.Concat(ToMPI(HexToBytes(Q)))
.Concat(ToMPI(HexToBytes(G)))
.Concat(ToMPI(HexToBytes(Y)))
.ToArray();
var fingerprint=BitConverter.ToString(SHA1.Create().ComputeHash(publicKey)).Replace("-","");
fingerprint.Dump();
}
byte[] ToMPI(byte[] data)
{
//Truncate leading 0 bytes
data = data.SkipWhile(b=>b==0).ToArray();
//Length prefix - 32 bit big-endian integer
var lenBytes=new byte[4];
lenBytes[0]=(byte)(data.Length>>24);
lenBytes[1]=(byte)(data.Length>>16);
lenBytes[2]=(byte)(data.Length>>8);
lenBytes[3]=(byte)(data.Length>>0);
return lenBytes.Concat(data).ToArray();
}
// from http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
public static byte[] HexToBytes(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
MSDN mentions what you need to do your job on this page.
Then, have a look at this answer from this SO page. The accepted answer gives the following code (and I quote):
var dsa = new DSACryptoServiceProvider();
var privateKey = dsa.ExportParameters(true); // private key
var publicKey = dsa.ExportParameters(false); // public key
I think you have everything you need to get you going.
CHEERS!
I've used this class to generate a OTR DSA key:
https://github.com/mohamedmansour/OTRLib/blob/master/Src/OTR/OTRUtilities/DSASigner.cs
Make the class public and call without constructor parameters.
var signer = new DSASigner();
var _des_key_object = signer.GetDSAKeyParameters();
Later reuse of the same key:
string _dsa_key_1_p = _des_key_object.GetHexParamP();
string _dsa_key_1_q = _des_key_object.GetHexParamQ();
string _dsa_key_1_g = _des_key_object.GetHexParamG();
string _dsa_key_1_x = _des_key_object.GetHexParamX();
// This can be a JSON for storing.
var keysArray = new string[] { _dsa_key_1_p, _dsa_key_1_q, _dsa_key_1_g, _dsa_key_1_x };
_des_key_object = new DSAKeyParams(_des_key_objectJson[0], _des_key_objectJson[1], _des_key_objectJson[2], _des_key_objectJson[3]);