CryptoSys Home > PKI > More details on Elliptic Curve Cryptography (ECC)

More details on Elliptic Curve Cryptography (ECC)


Elliptic curve cryptography was added to CryptoSys PKI Pro in version 11.0. This is an expanded version of the manual page with sample C# code. Links are given to the relevant .NET method in the manual. Follow the links to the equivalent C/VB6/VBA functions to see an example in VB6/VBA. See also Reproducing a raw Bitcoin transaction.

Introduction | Creating new ECC key pair | Key file format | Read key from hex value | Change format of key file | Public key from private | Analyze an EC key | ECDSA Signatures | Deterministic Usage of ECDSA | References | Contact us

Introduction

You can use the elliptic curve cryptography functions in this toolkit to sign data using the ECDSA algorithm (see ECDSA Signatures below). You can create your own elliptic curve keys, and read, analyze and save keys in the standard key file formats, both encrypted and unencrypted. You can read a key file into an internal key string which is stored in encrypted form valid only for the current session. We support the following curves over prime fields (updated 2023-10-24):

Creating a new ECC key pair

To create a new elliptic curve key pair, use Ecc.MakeKeys (In C/VBA: ECC_MakeKeys) This creates two new files, an encrypted private key file and a public key file. You can use the ReadKey and SaveKey functions to read in and save these in different formats.

// Create a new pair of ECC keys using 'Bitcoin' curve, saving private key with default parameters
pubkeyfile = "myeckeyk256.pub";
prikeyfile = "myeckeyk256.p8";
n = Ecc.MakeKeys(pubkeyfile, prikeyfile, Ecc.CurveName.Secp256k1, "password");
Console.WriteLine("Ecc.MakeKeys returns " + n + " (expected 0)");
Debug.Assert(0 == n);

s = Asn1.Type(pubkeyfile);
Console.WriteLine("'" + pubkeyfile + "'-->" + s);
s = Asn1.Type(prikeyfile);
Console.WriteLine("'" + prikeyfile + "'-->" + s);

// Create a new pair of ECC keys using P-192 curve, saving private key with stronger encryption"
pubkeyfile = "myeckeyp192.pub";
prikeyfile = "myeckeyp192.p8";
n = Ecc.MakeKeys(pubkeyfile, prikeyfile, Ecc.CurveName.Prime192v1, "password", 
  Ecc.PbeScheme.Pbe_Pbkdf2_aes128_CBC, "count=3999;prf=hmacWithSha256", Ecc.Format.PEM);
Console.WriteLine("Ecc.MakeKeys returns " + n + " (expected 0)");
Debug.Assert(0 == n);
Ecc.MakeKeys returns 0 (expected 0)
'myeckeyk256.pub'-->PUBLIC KEY INFO
'myeckeyk256.p8'-->PKCS8 ENCRYPTED PRIVATE KEY

[Go to top]

Key file format

Public key files: In this toolkit, EC public key files are always stored as DER-encoded SubjectPublicKeyInfo types [RFC5480]. In a PEM-encoded file, this should begin with -----BEGIN PUBLIC KEY.

Private key files: The three supported types (all DER-encoded) for an EC private key file are:

  1. PKCS#8 EncryptedPrivateKeyInfo [RFC5208] encrypted with a password. This is the (only) output when using Ecc.MakeKeys (ECC_MakeKeys) and Ecc.SaveEncKey (ECC_SaveEncKey). In a PEM-encoded file, this should begin with -----BEGIN ENCRYPTED PRIVATE KEY.
  2. ECPrivateKey [RFC5915]. This is the default output for a private key using Ecc.SaveKey (ECC_SaveKey). In a PEM-encoded file, this should begin with -----BEGIN EC PRIVATE KEY.
  3. Unencrypted PKCS#8 PrivateKeyInfo [RFC5208] (more recently updated to OneAsymmetricKey [RFC5958]). This is an optional output for a private key using Ecc.SaveKey (ECC_SaveKey) with the Ecc.KeyType.Pkcs8PrivateKeyInfo (PKI_KEY_TYPE_PKCS8) option. In a PEM-encoded file, this should begin with -----BEGIN PRIVATE KEY.

Key files can be saved as binary (default) or PEM-encoded. To save as PEM-encoded use the Ecc.Format.PEM (PKI_KEY_FORMAT_PEM) option. These encodings are detected automatically when reading a key file.

[Go to top]

Read key from hex value

Use Ecc.ReadKeyByCurve (ECC_ReadKeyByCurve) to read in a key in a hex format string, then you can save it as a file in a supported key format. (Public keys in compressed representation are not supported.)

hexKey = "6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4";
curveName = Ecc.CurveName.P_192;
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE:  " + curveName.ToString());
intKey = Ecc.ReadKeyByCurve(hexKey, curveName);
// A NIST P-192 public key in X9.63 uncompressed format"
hexKey = "0496C248BE456192FA1380CCF615D171452F41FF31B92BA733524FD77168DEA4425A3EA8FD79B98DC7AFE83C86DCC39A96";
curveName = Ecc.CurveName.Prime192v1;   // A synonym for P-192
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE: " + curveName);
// Read into internal key string
internalKey = Ecc.ReadKeyByCurve(hexKey, curveName);
// Find the key size in bits
query = "keyBits";
s = Ecc.QueryKey(internalKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
Debug.Assert(Convert.ToInt32(s) == 192);
// Is it a private or public key?
query = "isPrivate";
s = Ecc.QueryKey(internalKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
Debug.Assert(Convert.ToInt32(s) == 0);
KEYHEX: 0496C248BE456192FA1380CCF615D171452F41FF31B92BA733524FD77168DEA4425A3EA8
FD79B98DC7AFE83C86DCC39A96
CURVE: Prime192v1
QueryKey('keyBits')=192
QueryKey('isPrivate')=0

If your key is in base58 encoding, use CNV_Base58ToBytes to decode, then CNV_HexStrFromBytes to obtain the hex form string (in .NET just use Cnv.ToHex(Cnv.FromBase58(b58str))).

// A Bitcoin private key in base58 form"
b58Key = "6ACCbmy9qwiFcuVgvxNNwMPfoghobzznWrLs3v7t3RmN";
curveName = Ecc.CurveName.Secp256k1;
Console.WriteLine("KEYB58: " + b58Key);
// Convert base58 to a hex string
hexKey = Cnv.ToHex(Cnv.FromBase58(b58Key));
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE: " + curveName);
// Read into internal key string
internalKey = Ecc.ReadKeyByCurve(hexKey, curveName);
KEYB58: 6ACCbmy9qwiFcuVgvxNNwMPfoghobzznWrLs3v7t3RmN
KEYHEX: 4CA55366ADE9E9BC12319DFFC246F64EB7FA07755925B92CEFC92D740DBC51ED
CURVE: Secp256k1

[Go to top]

Change format of key file

To change the format of an EC key file, read the file into an "internal" string using Ecc.ReadPrivateKey (ECC_ReadPrivateKey) or Ecc.ReadPublicKey ECC_ReadPublicKey, then save it as a file again using Ecc.SaveKey (ECC_SaveKey) or Ecc.SaveEncKey (ECC_SaveEncKey).

// Read in the private key to an internal key string, then save it without any encryption (!)
sbIntKey = Ecc.ReadPrivateKey(prikeyfile, "password");
Debug.Assert(sbIntKey.Length > 0);
query = "curveName";
s = Ecc.QueryKey(sbIntKey.ToString(), query);
Console.WriteLine("QueryKey('" + query + "')=" + s);

// Save private key without any encryption"
fname = "myeckey521plain.key";
n = Ecc.SaveKey(fname, sbIntKey.ToString(), 0, Ecc.Format.PEM);
Console.WriteLine("Ecc.SaveKey(default) returns " + n + " (expected 0)");
Debug.Assert(0 == n);
s = Asn1.Type(fname);
Console.WriteLine("'" + fname + "'-->" + s);

[Go to top]

Public key from private

To obtain the public key from a private key, read the private key into an internal string and then use Ecc.PublicKeyFromPrivate (ECC_PublicKeyFromPrivate).

// Read in a 'Bitcoin' private key in hex form"
hexKey = "0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13e5e86e6a808d";
curveName = Ecc.CurveName.Secp256k1;
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE: " + curveName);

// Read into an internal key string
intPriKey = Ecc.ReadKeyByCurve(hexKey, curveName);
query = "isPrivate";
s = Ecc.QueryKey(intPriKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);

// Get the public key from the private key"
intPubKey = Ecc.PublicKeyFromPrivate(intPriKey);
query = "isPrivate";
s = Ecc.QueryKey(intPubKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
KEYHEX: 0ecd20654c2e2be708495853e8da35c664247040c00bd10b9b13e5e86e6a808d
CURVE: Secp256k1
QueryKey('keyBits')=256
QueryKey('curveName')=secp256k1
QueryKey('isPrivate')=1
2. Get the public key from the private key
QueryKey('isPrivate')=0

You can also derive the public key in hex form from the private key by using the "publicKey" query in Ecc.QueryKey (ECC_QueryKey). See the example in Analyze an EC key below.

[Go to top]

Analyze an EC key

To analyze an EC key file, read it into an internal string and use Ecc.QueryKey (ECC_QueryKey).

// A Bitcoin private key in base58 form"
b58Key = "6ACCbmy9qwiFcuVgvxNNwMPfoghobzznWrLs3v7t3RmN";
curveName = Ecc.CurveName.Secp256k1;
Console.WriteLine("KEYB58: " + b58Key);
// Convert base58 to a hex string
hexKey = Cnv.ToHex(Cnv.FromBase58(b58Key));
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE: " + curveName);
// Read into internal key string
internalKey = Ecc.ReadKeyByCurve(hexKey, curveName);
// Find the key size in bits
query = "keyBits";
s = Ecc.QueryKey(internalKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
Debug.Assert(Convert.ToInt32(s) == 256);
// Is it a private or public key?
query = "isPrivate";
s = Ecc.QueryKey(internalKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
Debug.Assert(Convert.ToInt32(s) == 1);

// Extract the public key in hex form from the private key
query = "publicKey";
s = Ecc.QueryKey(internalKey, query);
Console.WriteLine("QueryKey('" + query + "')=" + s);
Debug.Assert(s.Length > 0);
2. A Bitcoin private key in base58 form
KEYB58: 6ACCbmy9qwiFcuVgvxNNwMPfoghobzznWrLs3v7t3RmN
KEYHEX: 4CA55366ADE9E9BC12319DFFC246F64EB7FA07755925B92CEFC92D740DBC51ED
CURVE: Secp256k1
QueryKey('keyBits')=256
QueryKey('isPrivate')=1
QueryKey('publicKey')=04654bacc2fc7a3bde0f8eb95dc5aac9ba1df732255cf7f2eb7e1e8e6e
dbb1f4188ff3752ac4bdf1e3a31a488747745dddcbabd33a10c3b52d737c092851da13c0

[Go to top]

ECDSA Signatures

The following ECDSA signature algorithms are supported:

To compute a signature value over data in a byte array, use Sig.SignData (SIG_SignData); and use Sig.VerifyData (SIG_VerifyData) to verify the signature against the data.

To compute a signature value over over binary data in a file, use Sig.SignFile (SIG_SignFile); and use Sig.VerifyFile (SIG_VerifyFile) to verify the signature against the file.

To compute a signature directly over the message digest value of the data, use Sig.SignDigest (SIG_SignData + PKI_SIG_USEDIGEST); and use Sig.VerifyDigest (SIG_VerifyData + PKI_SIG_USEDIGEST) to verify the signature against the digest value.

The default form for an ECDSA signature is the simple concatenation r||s described in section E3.1 of [IEEE1363]. Include the option sig.SigOptions.Asn1DERStructure (PKI_SIG_ASN1DER) to use a DER-encoded ASN.1 structure instead.

The default signature encoding is base64. You can also specify hexadecimal (base16) encoding or the URL-safe base64url encoding as used by JSON JWS.

The Sig.Verify* methods will automatically detect the encoding and structure of the signature value (base64/base64url/hex/asn1der). You still need to specify the signature algorithm used.

string prikeyfile = "prime256v1.p8";
string pubkeyfile = "prime256v1.pub";
SigAlgorithm sigAlg = SigAlgorithm.Ecdsa_Sha256;
StringBuilder sbPassword = new StringBuilder("password");
StringBuilder sb;
HashAlgorithm hashAlg;

// 1. Make a new 256-bit keypair using P-256 curve (`Prime256v1` is a synonym)
n = Ecc.MakeKeys(pubkeyfile, prikeyfile, Ecc.CurveName.Prime256v1, sbPassword.ToString());
Console.WriteLine("Ecc.MakeKeys returns " + n + " (expected 0)");

// 1a. Query the keys we just made, just to check
sb = Ecc.ReadPrivateKey(prikeyfile, sbPassword.ToString());
s = Ecc.QueryKey(sb.ToString(), "curveName");
Console.WriteLine("CurveName: " + s);
s = Ecc.QueryKey(sb.ToString(), "keyBits");
Console.WriteLine("KeyBits:" + s);
Wipe.String(sb);

// A. Sign the DATA
Console.WriteLine("SigAlgorithm: " + sigAlg.ToString());

// 2. Prepare input data as an array of bytes
b = System.Text.Encoding.Default.GetBytes("abc");
Console.WriteLine("DATA: " + System.Text.Encoding.Default.GetString(b));

// 3. Sign DATA using the private key we made above
sig = Sig.SignData(b, prikeyfile, sbPassword.ToString(), sigAlg);
// NB signature value will be different each time
Console.WriteLine("SIG: " + sig);

// 4. Verify that the signature over the DATA
n = Sig.VerifyData(sig, b, pubkeyfile, sigAlg);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// B. Sign the DIGEST of the data

// Get the hash algorithm to use
hashAlg = Sig.GetHashAlgFromSigAlg(sigAlg);
Console.WriteLine("HashAlgorithm: " + hashAlg.ToString());
// Compute the digest value
digest = Hash.BytesFromBytes(b, hashAlg);
Console.WriteLine("DIGEST: " + Cnv.ToHex(digest));

// Sign the DIGEST
sig = Sig.SignDigest(digest, prikeyfile, sbPassword.ToString(), sigAlg);
Console.WriteLine("SIG: " + sig);

// And verify the signature using the digest value
n = Sig.VerifyDigest(sig, digest, pubkeyfile, sigAlg);
Console.WriteLine("Sig.VerifyDigest returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// C. Sign a FILE containing the data

fname = "abc.txt";
Console.WriteLine("FILE: " + fname);
MakeABinaryFile(fname, b);

// Sign the FILE
sig = Sig.SignFile(fname, prikeyfile, sbPassword.ToString(), sigAlg);
Console.WriteLine("SIG: " + sig);

// And verify the signature over the file
n = Sig.VerifyFile(sig, fname, pubkeyfile, sigAlg);
Console.WriteLine("Sig.VerifyFile returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// Finally, wipe the password
Wipe.String(sbPassword);
TESTING SIG ECC...
Ecc.MakeKeys returns 0 (expected 0)
CurveName: secp256r1
KeyBits:256
SigAlgorithm: Ecdsa_Sha256
DATA: abc
SIG: KtfvdkED+Vcl/BRAbqlyKP+jncc/C8SPYSoapE0Eo5IoacRev3jxiTuHgo6gBJ1clCSwvI4Vzz+
uVH+JzLQ6pg==
Sig.VerifyData returns 0 (expected 0)
HashAlgorithm: Sha256
DIGEST: BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD
SIG: iQpoiwAbBzKgOUsmzdp6LPlXHVDi1qkexRh5aDz4PUFkqA2BcWLIhXrOfkEieAgTfvAbBlqK4Ki
HLxFsCBzQIA==
Sig.VerifyDigest returns 0 (expected 0)
FILE: abc.txt
SIG: 9w2nnidPO63jSTcPL+n/IoY1NrGvfFl3DQ7nA7pATTf5oUwer7cn0H55aY0ZZLwTu4p6jQhcL6P
dyRlXwluAtQ==
Sig.VerifyFile returns 0 (expected 0)

[Go to top]

Deterministic Usage of the Elliptic Curve Digital Signature Algorithm (ECDSA)

The default behaviour of the ECDSA signature scheme is to use a random k value each time. This means that the signature of the same data will be different each time. The deterministic method described in [RFC6979] gives a unique signature for a given data input.

Ecc.CurveName curveName;
string sigdetok = "0f2141a0ebbc44d2e1af90a50ebcfce5e197b3b7d4de036deb18bc9e1f3d7387500cb99cf5f7c157070a8961e38700b7";

Console.WriteLine("\nTESTING SIG ECC DETERMINISTIC...");

// Ref: [RFC6979] "Deterministic Usage of the DSA and ECDSA"
// A.2.3.  ECDSA, 192 Bits (Prime Field)

// 1. READ IN PRIVATE KEY IN (HEX,CURVENAME) FORMAT
hexKey = "6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4";
curveName = Ecc.CurveName.P_192;
Console.WriteLine("KEYHEX: " + hexKey);
Console.WriteLine("CURVE:  " + curveName.ToString());
intKey = Ecc.ReadKeyByCurve(hexKey, curveName);
//Console.WriteLine("INTERNAL KEY (different each time!):\n[" + intKey + "]");

// 2. PREPARE INPUT DATA AS AN ARRAY OF BYTES
b = System.Text.Encoding.Default.GetBytes("test");

// 3. SIGN IT USING DETERMINISTIC ALGORITHM FROM [RFC6979]
//    with output in hex encoding
//    -- deterministic method always gives the same signature value for same input
sig = Sig.SignData(b, intKey, "", SigAlgorithm.Ecdsa_Sha1, 
  Sig.SigOptions.UseDeterministic, Sig.Encoding.Base16);
Console.WriteLine("SIG(hex): " + sig);
Console.WriteLine("SIG(OK) : " + sigdetok);
Debug.Assert(String.Compare(sig, sigdetok, true) == 0, "Sig.SignData failed");

// 4. VERIFY IT WITH THE PUBLIC KEY
intPubKey = Ecc.PublicKeyFromPrivate(intKey);
n = Sig.VerifyData(Cnv.ToBase64(Cnv.FromHex(sig)), b, intPubKey, SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// SIGN IT AGAIN WITH SIG IN ASN1DER FORM
sig = Sig.SignData(b, intKey, "", SigAlgorithm.Ecdsa_Sha1,
  Sig.SigOptions.UseDeterministic | Sig.SigOptions.Asn1DERStructure, Sig.Encoding.Base16);
Console.WriteLine("SIG(asn1der): " + sig);
// VERIFY IT AGAIN
n = Sig.VerifyData(sig, b, intPubKey, SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// SIGN IT AGAIN WITH SIG IN ASN1DER FORM AND ENCODE IN DEFAULT BASE64
sig = Sig.SignData(b, intKey, "", SigAlgorithm.Ecdsa_Sha1,
  Sig.SigOptions.UseDeterministic | Sig.SigOptions.Asn1DERStructure, 0);
Console.WriteLine("SIG(asn1der): " + sig);
// VERIFY IT AGAIN
n = Sig.VerifyData(sig, b, intPubKey, SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// SIGN IT AGAIN WITH SIG ENCODED IN "URL_SAFE" BASE64URL
sig = Sig.SignData(b, intKey, "", SigAlgorithm.Ecdsa_Sha1,
  Sig.SigOptions.UseDeterministic | Sig.SigOptions.Asn1DERStructure, Sig.Encoding.Base64url);
Console.WriteLine("SIG(base64url): " + sig);
// VERIFY IT AGAIN (note the encoding is detected automatically by Sig.Verifydata)
n = Sig.VerifyData(sig, b, intPubKey, SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);

// SIGN IT USING DEFAULT NON-DETERMINISTIC METHOD (different each time)
sig = Sig.SignData(b, intKey, "", SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("SIG(random-k): " + sig);
// VERIFY IT AGAIN
n = Sig.VerifyData(sig, b, intPubKey, SigAlgorithm.Ecdsa_Sha1);
Console.WriteLine("Sig.VerifyData returns " + n + " (expected 0)");
Debug.Assert(0 == n);
TESTING SIG ECC DETERMINISTIC...
KEYHEX: 6FAB034934E4C0FC9AE67F5B5659A9D7D1FEFD187EE09FD4
CURVE:  P_192
SIG(hex): 0f2141a0ebbc44d2e1af90a50ebcfce5e197b3b7d4de036deb18bc9e1f3d7387500cb9
9cf5f7c157070a8961e38700b7
SIG(OK) : 0f2141a0ebbc44d2e1af90a50ebcfce5e197b3b7d4de036deb18bc9e1f3d7387500cb9
9cf5f7c157070a8961e38700b7
Sig.VerifyData returns 0 (expected 0)
SIG(asn1der): 303502180f2141a0ebbc44d2e1af90a50ebcfce5e197b3b7d4de036d021900eb18
bc9e1f3d7387500cb99cf5f7c157070a8961e38700b7
Sig.VerifyData returns 0 (expected 0)
SIG(asn1der): MDUCGA8hQaDrvETS4a+QpQ68/OXhl7O31N4DbQIZAOsYvJ4fPXOHUAy5nPX3wVcHCo
lh44cAtw==
Sig.VerifyData returns 0 (expected 0)
SIG(base64url): MDUCGA8hQaDrvETS4a-QpQ68_OXhl7O31N4DbQIZAOsYvJ4fPXOHUAy5nPX3wVcH
Colh44cAtw
Sig.VerifyData returns 0 (expected 0)
SIG(random-k): JIGvo3vyUK5/pxn3pG3OBdzJOQkE73TkyvBmNQnO1AgIt8TgX5KixY34O6qBhOiJ
Sig.VerifyData returns 0 (expected 0)

[Go to top]

References

[Go to top]

Contact

For more information, please send us a message.

This page last updated 25 October 2023

[Go to top]