EC keys #
In elliptic curve cryptography (ECC), keys are defined by a number of parameters. Domain parameters describe a particular curve used for operations. It is critical to use a curve with validated security properties, therefore usually one of the wellknown respectable curves is chosen.
As of Themis 0.13 released in 2020, the following elliptic curves are supported:
 NIST P256, aka secp256r1, aka prime256v1 (current default)
 NIST P384, aka secp384r1
 NIST P521, aka secp521r1
All of them have been recommended by the NSA Suite B.
Migration to more recent recommendations of the CNSA Suite is pending.
As for variable parameters of the key, a scalar integer k is used as a private key. A point on the curve P, obtained by multiplying the base point of the curve by k, is the corresponding public key.
Layout #
EC keys are stored in Soter containers. The tags for the supported curves are as follows:
Curve  Public key tag  Private key tag 

P256  UEC2 
REC2 
P384  UEC3 
REC3 
P521  UEC5 
REC5 
The payload of Soter container stores the key data encoded according to ANSI X9.62. All fields are stored in bigendian (network) byte order.
Public key layout (for the P256 curve):
0 1 33
++++++++++
z x 
++++++++++
where
 z (1 byte) is an indicator of the y coordinate value
 x (32 bytes) is the x coordinate of the public point P
z is equal to 02
if the y coordinate of the public point is even, or 03
if it is odd.
No other values are permitted.
This is known as compressed representation of an elliptic curve point,
possible due to the fact that there are at most two points on a curve with a given x coordinate.
Private key layout (for the P256 curve):
0 1 33
++++++++++
0 k 
++++++++++
where
 k (32 bytes) is the private key k, a scalar integer
Note: Due to historical reasons, private keys in Themis have the same length as public keys. They include an additional octet in the beginning, always set to zero, which is preserved for compatibility.
The size of the fields depends on the curve in use. Total key size includes the length of Soter container header.
Curve  z  x, k  Total key size 

P256  1  32  45 bytes 
P384  1  48  61 bytes 
P521  1  66  79 bytes 
Example #
Example public key:
00000000 55 45 43 32 00 00 00 2d 6c d5 6e f8 03 a4 b1 f7 UEC2...l.n.....
00000010 28 43 ca 03 61 e1 81 1b d0 b4 a1 2d 9a c4 81 3a (C..a.........:
00000020 2c 60 5d b2 45 51 b8 a4 71 a8 69 ae 8d ,`].EQ..q.i..
where you can see the data fields:
Field  Offset  Data  Meaning 

header  0x00  55 45 43... (12 bytes) 
UEC2 – EC public key for the P256 curve 
z  0x0C  03 
the y coordinate of the public point is odd 
x  0x0D  A4 B1 F7... (32 bytes) 
the x coordinate value 
Example private key, corresponding to the above public key:
00000000 52 45 43 32 00 00 00 2d 57 d9 cd ac 00 d2 80 f9 REC2...W.......
00000010 a3 ce c1 71 2a f9 53 d0 48 aa 63 d3 b2 d9 3b bc ...q*.S.H.c...;.
00000020 20 14 29 a6 54 4a 84 4f fc ba 68 ab c9  .).TJ.O..h..
where you can see the data fields:
Field  Offset  Data  Meaning 

header  0x00  52 45 43... (12 bytes) 
REC2 – EC private key for the P256 curve 
padding  0x0C  00 
a padding byte which must be set to zero 
k  0x0D  D2 80 F9... (32 bytes) 
the k value 
You can verify that the keys match each other on the P256 curve and that the y coordinate value is indeed odd using the following snippet in Go:
package main
import (
"crypto/elliptic"
"encoding/base64"
"fmt"
"math/big"
)
func main() {
xB64 := "pLH3KEPKA2HhgRvQtKEtmsSBOixgXbJFUbikcahpro0="
kB64 := "0oD5o87BcSr5U9BIqmPTstk7vCAUKaZUSoRP/Lpoq8k="
x, _ := base64.StdEncoding.DecodeString(xB64)
k, _ := base64.StdEncoding.DecodeString(kB64)
curve := elliptic.P256()
xComputed, yComputed := curve.ScalarBaseMult(k)
// Computed x coordinate must match the one from the public key
if xComputed.Cmp(new(big.Int).SetBytes(x)) == 0 {
fmt.Println("✅ P = k × G (x coordinate correct)")
} else {
fmt.Println("❌ P = k × G (x coordinate correct)")
}
// Computed y coordinate must be odd, as indicated by the public key
rem := new(big.Int).Mod(yComputed, big.NewInt(2))
if rem.Cmp(big.NewInt(1)) == 0 {
fmt.Println("✅ P = k × G (y coordinate is odd)")
} else {
fmt.Println("❌ P = k × G (y coordinate is odd)")
}
}
Generating EC keys #
The recommended way to generate EC keypairs is to use a cryptography library.
See also our key management guide for advice on securing the keys after you have generated them.
Here is a snippet in Go, illustrating how to generate EC keypair using P256 curve:
package main
import (
"crypto/elliptic" // Use a cryptography library.
"crypto/rand" // Use a cryptographic RNG.
"fmt"
)
func main() {
// A good choice of a wellknown secure curve is essential.
curve := elliptic.P256()
k, x, y, err := elliptic.GenerateKey(curve, rand.Reader)
if err != nil {
// Not handling RNG failure is a common critical error.
// If not handled, you might return an invalid key.
panic("failed to generate a key")
}
// In Themis, private keys have an extra zero padding at the beginning.
// The public keys are stored in ANSI X9.62 compressed form.
privateKey := append([]byte{0}, k...)
publicKey := elliptic.MarshalCompressed(curve, x, y)
fmt.Printf("✅ private key: %x\n", privateKey)
fmt.Printf("✅ public key: %x\n", publicKey)
}
The following description is provided for illustrative purposes.
Consult your nearby friendly cryptographer on how EC keys should be generated and what are the common implementation pitfalls that you need to avoid.
The process of generating an ellliptic keypair is relatively simple:

Select a secure elliptic curve which you will be using.

Securely choose a random integer k from a range of [1, n) where n is the order of the selected curve. This is your private key.
It is essential to use a good, cryptographically secure pseudorandom number generator so that the generated key is unpredictable.

Compute the point P = k × G where G is the base point of the curve (the generator). This is your public key.

Encode k and P according to ANSI X9.62 and pack them into appropriate Soter containers.