Elliptic cryptography, having high strength and widespread use, has always caused a lot of controversy and speculation about possible bookmarks for different curves and signature schemes. However, no one could give an example of such a bookmark or prove their absence. Therefore, unlike symmetric cryptography, where the leadership unconditionally belongs to AES, asymmetric cryptography is used in different types, depending on preferences, technical or legal requirements. Additional types of address signatures in I2P provide more choice and flexibility for applications. GOST is supported in openssl via the EVP interface, but in version 1.1 it is excluded from the standard delivery; in addition, the existing implementation involves storing and transmitting public keys and signatures in the DER format, and I2P works directly with numbers, determining the necessary parameters from the signature type.
Currently in I2P there is 9 types of signatures, where the signature type is specified, then the hash type and, if necessary, the curve or its set of parameters.
Also, old addresses use type 0 - DSA_SHA1, which is considered obsolete.
It is recommended to use types 1 and 7.
For GOST, at my request, we have been allocated two types:
9 — GOSTR3410_GOSTR3411_256_CRYPTO_PRO_A
10 — GOSTR3410_GOSTR3411_512_TC26_A
for 256 and 512-bit keys respectively.
The length of the public key for 9 is 64 bytes (32 bytes for each point coordinate) and 128 bytes for 10.
It is assumed that a hash of 256 or 512 bits is signed and verified. Described in detail and with examples Here.
A regular elliptic curve is used, so functions from the cryptographic library can be used to work with it. In particular, these are EC_GROUP_* and EC_POINT_* from openssl, the main one of which is:
used both to multiply a base point by a number and an arbitrary point, depending on the parameters.
For the curve, 6 parameters are set: p - modulus, a and b - coefficients of the curve equation, P(x,y) - base point, q - prime number, multiplying by which the base point gives zero.
p and q must be large and close to the maximum, since p limits the range of all numbers, and q limits the range of the secret keys. Base point and q are calculated simultaneously.
As a rule, well-known and well-tested parameter sets are used.
In our implementation we will use 2 sets of parameters:
Unlike a curve, GOST has its own signature scheme, so the ECDSA_sign and ECDSA_verify functions cannot be used and you should implement signature and signature verification in your code.
For the signature (r,s), a random number k is selected, and the point R=k*P is calculated, the x coordinate of which becomes the r component of the signature. Component s = r*d + h*k, where d is the secret key, h is the hash of the message signature in Big Endian.
To check the signature, multiply both sides of the equality by the base point P.
Indeed, s*P = r*d*P + h*k*P = r*Q + h*R, where Q is the public key. In this equation we do not know the point R, and although it is possible to reconstruct the y coordinate from r, this is an extremely slow operation. Therefore, we rewrite the equality in the form h*R = s*P - r*Q, then
R = (s*P -r *Q)/h and compare only the x coordinate.
Although to sign a message you can use any function that calculates a hash of a suitable size, for example SHA256/SHA512, we will use the one prescribed by the GOST R 34.11-2012 standard, including for compatibility with existing implementations. Unlike a signature, a hash is much simpler.
Detailed description and examples. Let's note the main points:
If the address connects to the router using the protocol I2CP, then the router does not need to know the secret signing key.
Instead, the router sends a RequestLeaseSetMessage (or RequestVariableLeaseSetMessage), expecting in return a CreateLeaseSetMessage containing a LeaseSet signed with the address's secret key. As you can see from the protocol description, in older versions of I2P it was required to transmit this key in a message, this is no longer required.
Thus, an application that implements an I2P address can use the API of one of the existing crypto providers with GOST for signing, allowing you to effectively integrate an I2P solution into the existing infrastructure.
Currently i2pd fully supports signature types 9 and 10. Any client addresses will work with addresses on i2pd. example use. For server addresses to work, support from floodfills is required, or a network independent of the main I2P can be built with a netid different from 2. In the main network, you need to wait for this to happen implemented in Java or additional floodfill parameter.
Types of signatures in I2P
Currently in I2P there is 9 types of signatures, where the signature type is specified, then the hash type and, if necessary, the curve or its set of parameters.
- ECDSA_SHA256_P256
- ECDSA_SHA384_P384
- ECDSA_SHA512_P521
- RSA_SHA256_2048
- RSA_SHA384_3072
- RSA_SHA512_4096
- EdDSA_SHA512_Ed25519
- EdDSA_SHA512_Ed25519ph
Also, old addresses use type 0 - DSA_SHA1, which is considered obsolete.
It is recommended to use types 1 and 7.
For GOST, at my request, we have been allocated two types:
9 — GOSTR3410_GOSTR3411_256_CRYPTO_PRO_A
10 — GOSTR3410_GOSTR3411_512_TC26_A
for 256 and 512-bit keys respectively.
The length of the public key for 9 is 64 bytes (32 bytes for each point coordinate) and 128 bytes for 10.
Implementation of GOST R signature 34.10
It is assumed that a hash of 256 or 512 bits is signed and verified. Described in detail and with examples Here.
A regular elliptic curve is used, so functions from the cryptographic library can be used to work with it. In particular, these are EC_GROUP_* and EC_POINT_* from openssl, the main one of which is:
int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx)
used both to multiply a base point by a number and an arbitrary point, depending on the parameters.
For the curve, 6 parameters are set: p - modulus, a and b - coefficients of the curve equation, P(x,y) - base point, q - prime number, multiplying by which the base point gives zero.
p and q must be large and close to the maximum, since p limits the range of all numbers, and q limits the range of the secret keys. Base point and q are calculated simultaneously.
As a rule, well-known and well-tested parameter sets are used.
In our implementation we will use 2 sets of parameters:
- GostR3410_2001_CryptoPro_A_ParamSet (OID 1.2.643.2.2.35.1) for 256-bit keys, borrowed from GOST R 34.10-2001;
- id-tc26-gost-3410-12-512-paramSetA (OID 1.2.643.7.1.2.1.2.1) for 512-bit keys, developed by TK26 (tc26.ru) specifically for GOST R 34.10-2012.
Unlike a curve, GOST has its own signature scheme, so the ECDSA_sign and ECDSA_verify functions cannot be used and you should implement signature and signature verification in your code.
For the signature (r,s), a random number k is selected, and the point R=k*P is calculated, the x coordinate of which becomes the r component of the signature. Component s = r*d + h*k, where d is the secret key, h is the hash of the message signature in Big Endian.
To check the signature, multiply both sides of the equality by the base point P.
Indeed, s*P = r*d*P + h*k*P = r*Q + h*R, where Q is the public key. In this equation we do not know the point R, and although it is possible to reconstruct the y coordinate from r, this is an extremely slow operation. Therefore, we rewrite the equality in the form h*R = s*P - r*Q, then
R = (s*P -r *Q)/h and compare only the x coordinate.
In i2pd code it looks like this
bool GOSTR3410Curve::Verify (const EC_POINT * pub, const BIGNUM * digest, const BIGNUM * r, const BIGNUM * s)
{
BN_CTX * ctx = BN_CTX_new ();
BN_CTX_start (ctx);
BIGNUM * q = BN_CTX_get (ctx);
EC_GROUP_get_order(m_Group, q, ctx);
BIGNUM * h = BN_CTX_get (ctx);
BN_mod (h, digest, q, ctx); // h = digest % q
BN_mod_inverse (h, h, q, ctx); // 1/h mod q
BIGNUM * z1 = BN_CTX_get (ctx);
BN_mod_mul (z1, s, h, q, ctx); // z1 = s/h
BIGNUM * z2 = BN_CTX_get (ctx);
BN_sub (z2, q, r); // z2 = -r
BN_mod_mul (z2, z2, h, q, ctx); // z2 = -r/h
EC_POINT * C = EC_POINT_new (m_Group);
EC_POINT_mul (m_Group, C, z1, pub, z2, ctx); // z1*P + z2*pub
BIGNUM * x = BN_CTX_get (ctx);
GetXY (C, x, nullptr); // Cx
BN_mod (x, x, q, ctx); // Cx % q
bool ret = !BN_cmp (x, r); // Cx = r ?
EC_POINT_free (C);
BN_CTX_end (ctx);
BN_CTX_free (ctx);
return ret;
}
Hash function GOST R 34.11-2012 (Stribog)
Although to sign a message you can use any function that calculates a hash of a suitable size, for example SHA256/SHA512, we will use the one prescribed by the GOST R 34.11-2012 standard, including for compatibility with existing implementations. Unlike a signature, a hash is much simpler.
Detailed description and examples. Let's note the main points:
- The original message is divided into blocks of 64 bytes. If the message is not a multiple of 64, then the first block is padded on the left with zero and one bytes.
- Each block is hashed separately based on the hash of the previous block. For the very first block, the initialization vector(iv) is set as the previous hash. The blocks are passed through in reverse order — from last to first.
- Each block is "encrypted" 12 rounds with given keys, each round is the application of three transformations S, P, L using given tables and XOR with the round key.
- S represents a byte-by-byte replacement, P is a transpose of the hash as an 8x8 matrix, L is a multiplication by a given matrix.
- 256-the bit hash is the left half of the 512-bit hash, but a different iv is used, so they cannot be calculated at the same time.
Signing by an external crypto provider using the I2CP protocol
If the address connects to the router using the protocol I2CP, then the router does not need to know the secret signing key.
Instead, the router sends a RequestLeaseSetMessage (or RequestVariableLeaseSetMessage), expecting in return a CreateLeaseSetMessage containing a LeaseSet signed with the address's secret key. As you can see from the protocol description, in older versions of I2P it was required to transmit this key in a message, this is no longer required.
Thus, an application that implements an I2P address can use the API of one of the existing crypto providers with GOST for signing, allowing you to effectively integrate an I2P solution into the existing infrastructure.
Implementation
Currently i2pd fully supports signature types 9 and 10. Any client addresses will work with addresses on i2pd. example use. For server addresses to work, support from floodfills is required, or a network independent of the main I2P can be built with a netid different from 2. In the main network, you need to wait for this to happen implemented in Java or additional floodfill parameter.