Why Is the Ecc-Dh Symmetric Key of This Site Different from Openssl

Why is the ECC-DH Symmetric Key Of This Site Different From OpenSSL

I've managed to work it out now with working symmetric keys on client (JSBN-EC) and on server OpenSSL Ruby

I found out that my problem actually lies in the code itself. After fixing it, I've ended up with a symmetric key on OpenSSL Ruby as follows:

#Ruby: OpenSSL
...
...
symm_key = ec.dh_compute_key(point)
symm_key.unpack('B*').first.to_i(2) #Converts to binary, then to integer
#--> 6922380353406615622038660570577625762884344085425862813095878420328

While on the client side using JSBN-EC

#Javascript: JSBN-EC
...
...
var curve = get_curve();
var P = new ECPointFp(curve,
curve.fromBigInteger(server_pub_key_x),
curve.fromBigInteger(server_pub_key_y));
var a = client_priv_key;
var S = P.multiply(a);

console.log('SYMM_KEY X: '+S.getX().toBigInteger().toString());
//--> 6922380353406615622038660570577625762884344085425862813095878420328
console.log('SYMM_KEY Y: '+S.getY().toBigInteger().toString());
//--> 14426877769799867628378883482085635535383864283889042780773103726343

Therefore from the looks of it, the symmetric key that matches the Ruby OpenSSL value is the X value of the JSBN-EC symmetric key

6922380353406615622038660570577625762884344085425862813095878420328
==
6922380353406615622038660570577625762884344085425862813095878420328

I don't know what the Y value is now for. Looks like I won't need it. Cheers! :)

How to Format OpenSSL Keys to Match Online Sample (JSBN-ECC)

Finally! I somehow managed to convert it properly but it's somehow weird.

#From above code
c.public_key.to_bn
#--> 499599043529551953518354858381998373780459818901085313561109939106744612770290

#irb:
require 'openssl'

key_int = '499599043529551953518354858381998373780459818901085313561109939106744612770290'
key_bn = OpenSSL::BN.new(key_int, 10) #Convert to OpenSSL::BN (Big Number, with 10=Decimal as base)
key_hex = key_bn.to_s(16) #Convert to Hex String (16=Hexadecimal)
#--> "04508B09B35FA8C21820BE19C16B38486C5239D4A932D081DD56B90F91120551F2"

#I don't really know why, but removing '04' above will finally convert it properly
key_hex = key_hex[2..-1] #Remove first 2 chars: '04'
#--> "508B09B35FA8C21820BE19C16B38486C5239D4A932D081DD56B90F91120551F2"

#Split key_hex into halves
key_hexarr = key_hex.chars.each_slice( (key_hex.length/2.0).round ).map(&:join)
#--> ["508B09B35FA8C21820BE19C16B38486C", "5239D4A932D081DD56B90F91120551F2"]

#Convert first value into BN (input: 16=hexadecimal), then convert to string(output: 10=decimal)
key_x_int = OpenSSL::BN.new(key_hexarr[0], 16).to_s(10)
#--> "107060165679262225845922473865530329196"

#Convert second value into BN (input: 16=hexadecimal), then convert to string(output: 10=decimal)
key_y_int = OpenSSL::BN.new(key_hexarr[1], 16).to_s(10)
#--> "109296969851421346147544217212275741170"

Finally, key_x_int and key_y_int now matches the result from the online link

Elliptic Curve Diffie Hellman public key size

In the C++ code that public key is not just DER encoded, it is PEM encoded as well. That adds a header and footer around the base 64 encoded DER structure. PEM is used for text-based interface where binary data may get distorted. It is therefore also sometimes called "ASCII armor" (or "ASCII armour" depending on your dialect).

If you want to take a look at the structures of the C++ / OpenSSL code then simply paste base 64 encoded DER structure into this site or use openssl asn1parse.


The Mickeysoft structure returned by serverECDH.PublicKey.ToByteArray() seems to consist of "ECK1" + 20 00 00 00 followed by the two statically sized encoded coordinates (which could be big or little endian). ECK1 is probably Elliptic Curve Key format 1 - whatever that is - and 20 00 00 00 is probably the key / coordinate size in octets as 32 * 8 = 256. Note that C# officially uses platform endianess, so the 20 00 00 00 is a 32 bit little endian value. Probably you need an external library to encode their keys so that any other runtime can understand them.


As for your tag-on question: yes, the ANS(I) X9.62 prime256r1 curve is identical to NIST P-256 and secp256r1 as (I think) originally defined by Certicom corp. The curve was first defined and then picked up & standardized by various organizations.

Generate a Java compatible Diffie-Hellman using Python

PyCryptodome does not seem to support ECDH at the moment, see Future plans. An alternative is the Cryptography library, see Elliptic Curve Key Exchange algorithm.

The library expects the private key as int and the public key in uncompressed format as bytes object. The uncompressed format consists of the concatenated x and y coordinates preceded by a 0x04 byte.

sjcl.ecc.curves.c256 defines secp256r1 (aka prime256v1 aka NIST P-256).

Then a possible implementation in Python with Cryptography is:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
import base64

curve = ec.SECP256R1()

clientKey = 'zIhDVlFUpWQiRP+bjyEIhSLq8rcB8+XInXGhm6JGcVI='
privateKeyInt = int.from_bytes(base64.b64decode(clientKey), byteorder='big', signed=False)
privateKey = ec.derive_private_key(privateKeyInt, curve, default_backend())

serverPubKey = 'WIUBDotrk02Rk/apL11jQPbmX0quyaYz2EIkGUlVf7t2JnYJt9JTLkqvzY4h93gCO6yrYr7+SMZnvKZypXCfaQ=='
publicKeyUncomp = b'\x04' + base64.b64decode(serverPubKey)
publicKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, publicKeyUncomp)

sharedSecret = privateKey.exchange(ec.ECDH(), publicKey)
print(base64.b64encode(sharedSecret).decode('utf8')) # ZBin/RV1qnfKoIuel+5fzv1y8rn3UZkMPO3pXva3VzQ=

which produces the same shared secret as the JavaScript code.

OpenSSL EdDSA Specify Key Size

ed25519 private keys are by definition 32-bytes in length. From section 5.1.5 of RFC8032:

The private key is 32 octets (256 bits, corresponding to b) of

cryptographically secure random data. See [RFC4086] for a discussion
about randomness.

I don't know where you get 64 characters in your question above. I might expect that you are looking at the encoded length - but that also doesn't make sense.

If I do this:

openssl genpkey -algorithm ed25519 -out private.pem

Then I get a PEM encoded private key which is 119 bytes in length. This is encoded according to section 7 of RFC8410. You can look at the contents like this:

openssl asn1parse -in private.pem
0:d=0 hl=2 l= 46 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :00
5:d=1 hl=2 l= 5 cons: SEQUENCE
7:d=2 hl=2 l= 3 prim: OBJECT :ED25519
12:d=1 hl=2 l= 34 prim: OCTET STRING [HEX DUMP]:0420F897797B25D84588192CE39F0E6311954034CB80F6D8CD648A3BCBFC2346A83E

The actual raw private key itself is encoded as an OCTET STRING inside the OCTET STRING shown above (in accordance with RFC 8410). As you can see above that octet string starts at offset 12, with a header length of 2 - so the data itself is at offset 14:

openssl asn1parse -in private.pem -offset 14
0:d=0 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:F897797B25D84588192CE39F0E6311954034CB80F6D8CD648A3BCBFC2346A83E

Which shows you a private key of 32 bytes in length as expected.

Some software may store keys in different formats not conformant with RFC8410 (e.g. by storing the private key and public key together) - so if you've loaded this key into something else then that might explain where the 64 is coming from. You can't use OpenSSL to generate those formats though.

However the bottom line is, ed25519 private keys are always 32-bytes and you can't change it. Its a fundamental property of the algorithm.

elliptic curve routines:o2i_ECPublicKey:passed a null parameter:ec_asn1.c:1271:

some bytes (prefix) the same: all public keys in X.509-format (more exactly, SubjectPublicKeyInfo) have at least an 'object identifier' (OID), which is the same for all keys of a given algorithm (like EC), and EC keys also have 'parameters' specifying a group/curve, which is the same for all keys on the same curve -- and keys for an ECDH agreement must be on the same curve. This identical data, combined with the fact that the actual pubkeys have different values but same size, results in the ASN.1 DER encodings starting with the same bytes.

The pubkey encodings you posted use the mostly-obsolete explicit curve specification, see rfc3279 sec 2.3.5 (equivalent to X9.62 or SEC1) which is much longer than the now-preferred and often required 'named' specification. At a guess, you used OpenSSL library below 1.1.0 to generate these keys and didn't set asn1_flag in the EC_GROUP object (or group subobject of EC_KEY) before serializing (aka encoding) with i2d (or PEM_write).
The wiki page you referenced sort of covers this in section 3 "ECDH and Named Curves" although it only mentions private keys when this also applies to public keys and certificates -- but public key and (then) certificate are derived from private key, so setting asn1_flag on private key is sufficient. And it doesn't say that 'named' is now the default in 1.1.0 and no longer needs to be explicitly set.

your newly-posted code: point_format is meaningful only when serializing (i2d or PEM_write) so setting it on a deserialized key that will only be used and free'd (not reserialized) is useless. OTOH setting the EC group to its existing value (which was set from deserialization) is useless, but setting it to any other value will cause chaos. EC public keys are points on a specific curve and different curves have entirely different points -- a value that is a point on one curve is not a point on another curve. Also, using a get1 function and not free'ing the result leaks memory.

derivation result different: that's quite wrong for (EC)DH, and I can't reproduce it. Below is your deriviation code with the few fixes indicated above and some tiny changes to match my coding style, plus the generation code from the wiki and a trivial main to drive them, and when I run this I get pubkeys with a common prefix (shorter due to using named form) but as expected the same derivation result:

$ cat SO48130343.c 
/* SO48130343 */
#include <stdio.h>
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/ec.h>
#include <openssl/err.h>

void hex (unsigned char *p, size_t n){ while(n--) printf("%02x", *p++); }

void err (const char * msg){ fprintf(stderr, "%s:\n", msg); ERR_print_errors_fp(stderr); exit(1); }

EVP_PKEY * gen (void) {
EVP_PKEY_CTX *pctx, *kctx;
EVP_PKEY *params = NULL, *pkey = NULL;
if( NULL == (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) ) err("CTX1_new");
if( 1 != EVP_PKEY_paramgen_init(pctx) ) err("pg_init");
if( 1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) ) err("pg_curve");
if( 1 != EVP_PKEY_paramgen(pctx, ¶ms) ) err("pg");
if( NULL == (kctx = EVP_PKEY_CTX_new(params, NULL)) ) err("CTX2_new");
if( 1 != EVP_PKEY_keygen_init(kctx) ) err("kg_init");
if( 1 != EVP_PKEY_keygen(kctx, &pkey) ) err("kg");
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
EC_KEY_set_asn1_flag (pkey->pkey.ec, OPENSSL_EC_NAMED_CURVE);
/* point format needed before 'sending' and this is convenient */
EC_KEY_set_conv_form (pkey->pkey.ec, POINT_CONVERSION_COMPRESSED);
#else
/* asn1_flag now default but point format still needed */
EC_KEY_set_conv_form (EVP_PKEY_get0_EC_KEY (pkey), POINT_CONVERSION_COMPRESSED);
#endif
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_CTX_free(kctx);
EVP_PKEY_free(params);
return pkey;
}
unsigned char * derive (EVP_PKEY * self,
const unsigned char * peer_ptr, size_t peer_len, size_t *len_ptr){
EVP_PKEY * peer = d2i_PUBKEY (NULL, &peer_ptr, peer_len);
/* DON'T change EC_GROUP; point_format not needed on 'receive' */

EVP_PKEY_CTX *ctx; unsigned char * buf_ptr;
if( !(ctx = EVP_PKEY_CTX_new (self, NULL)) ) err("CTX_new");
if( 1 != EVP_PKEY_derive_init(ctx) ) err("derive_init");
if( 1 != EVP_PKEY_derive_set_peer(ctx, peer) ) err("derive_peer");
if( 1 != EVP_PKEY_derive (ctx, NULL, len_ptr) ) err("derive1");
if( !(buf_ptr = OPENSSL_malloc (*len_ptr)) ) err("malloc");
if( 1 != EVP_PKEY_derive (ctx, buf_ptr, len_ptr) ) err("derive2");
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(peer);
return buf_ptr;
}

int main (void){
EVP_PKEY * pkey1 = gen(), * pkey2 = gen();
unsigned char pub1 [100], pub2 [100], *ptr1 = &pub1[0], *ptr2 = &pub2[0];
size_t publen1 = i2d_PUBKEY (pkey1, &ptr1), publen2 = i2d_PUBKEY (pkey2, &ptr2);
printf ("pub1="); hex(pub1, publen1); putchar('\n');
printf ("pub2="); hex(pub2, publen2); putchar('\n');

size_t len1, len2;
unsigned char * out1 = derive (pkey1, pub2, publen2, &len1);
unsigned char * out2 = derive (pkey2, pub1, publen1, &len2);
printf ("prv1/pub2="); hex(out1, len1); putchar('\n');
printf ("prv2/pub1="); hex(out2, len2); putchar('\n');
/* don't bother freeing for Q&D test code */
return 0;
}
$ gcc [details for my system redacted]
$ ./SO48130343.exe
pub1=3039301306072a8648ce3d020106082a8648ce3d03010703220003302c6f990445ddd27b2c0ecd3a0cd33109eec44dea0edd538c6bfc98796885e3
pub2=3039301306072a8648ce3d020106082a8648ce3d0301070322000311940ba32c0b4d71f8785a884f7ea74cebed17e841e93a0fb1ccbeac32b2eb3b
prv1/pub2=84b7a84249f1e88741a751a05d34a43e4cb131e012181967e4f465c1f4bf3b35
prv2/pub1=84b7a84249f1e88741a751a05d34a43e4cb131e012181967e4f465c1f4bf3b35


Related Topics



Leave a reply



Submit