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
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):
P-192
, also known as secp192r1
and prime192v1
P-256
, also known as secp256r1
and prime256v1
P-224
, also known as secp224r1
P-384
, also known as secp384r1
P-521
, also known as secp521r1
secp256k1
(the Bitcoin curve) brainpoolP256r1
[RFC5639]brainpoolP384r1
[RFC5639]brainpoolP512r1
[RFC5639]X25519
[RFC7748]X448
[RFC7748]Ed25519
[RFC8032]Ed448
[RFC8032]
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
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:
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
.
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
.
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.
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
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);
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.
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
The following ECDSA signature algorithms are supported:
Ecdsa_Sha1
("ecdsaWithSHA1"
)Ecdsa_Sha224
("ecdsaWithSHA224"
)Ecdsa_Sha256
("ecdsaWithSHA256"
)Ecdsa_Sha384
("ecdsaWithSHA384"
)Ecdsa_Sha512
("ecdsaWithSHA512"
)
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)
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)
For more information, please send us a message.
This page last updated 25 October 2023