Class: OpenSSL::PKey::DH

Inherits:
PKey
  • Object
show all
Includes:
Marshal
Defined in:
ext/openssl/ossl_pkey_dh.c,
lib/openssl/pkey.rb,
ext/openssl/ossl_pkey_dh.c

Overview

An implementation of the Diffie-Hellman key exchange protocol based on discrete logarithms in finite fields, the same basis that DSA is built on.

Accessor methods for the Diffie-Hellman parameters

DH#p

The prime (an OpenSSL::BN) of the Diffie-Hellman parameters.

DH#g

The generator (an OpenSSL::BN) g of the Diffie-Hellman parameters.

DH#pub_key

The per-session public key (an OpenSSL::BN) matching the private key. This needs to be passed to DH#compute_key.

DH#priv_key

The per-session private key, an OpenSSL::BN.

Example of a key exchange

# you may send the parameters (der) and own public key (pub1) publicly
# to the participating party
dh1 = OpenSSL::PKey::DH.new(2048)
der = dh1.to_der
pub1 = dh1.pub_key

# the other party generates its per-session key pair
dhparams = OpenSSL::PKey::DH.new(der)
dh2 = OpenSSL::PKey.generate_key(dhparams)
pub2 = dh2.pub_key

symm_key1 = dh1.compute_key(pub2)
symm_key2 = dh2.compute_key(pub1)
puts symm_key1 == symm_key2 # => true

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Marshal

#_dump, included

Methods inherited from PKey

#compare?, #decrypt, #derive, #encrypt, #inspect, #oid, #private_to_der, #private_to_pem, #public_to_der, #public_to_pem, #raw_private_key, #raw_public_key, #sign, #sign_raw, #to_text, #verify, #verify_raw, #verify_recover

Constructor Details

#newObject #new(string) ⇒ Object #new(size[, generator]) ⇒ Object

Creates a new instance of OpenSSL::PKey::DH.

If called without arguments, an empty instance without any parameter or key components is created. Use #set_pqg to manually set the parameters afterwards (and optionally #set_key to set private and public key components). This form is not compatible with OpenSSL 3.0 or later.

If a String is given, tries to parse it as a DER- or PEM- encoded parameters. See also OpenSSL::PKey.read which can parse keys of any kinds.

The DH.new(size [, generator]) form is an alias of DH.generate.

string

A String that contains the DER or PEM encoded key.

size

See DH.generate.

generator

See DH.generate.

Examples:

# Creating an instance from scratch
# Note that this is deprecated and will result in ArgumentError when
# using OpenSSL 3.0 or later.
dh = OpenSSL::PKey::DH.new
dh.set_pqg(bn_p, nil, bn_g)

# Generating a parameters and a key pair
dh = OpenSSL::PKey::DH.new(2048) # An alias of OpenSSL::PKey::DH.generate(2048)

# Reading DH parameters from a PEM-encoded string
dh_params = OpenSSL::PKey::DH.new(File.read('parameters.pem')) # loads parameters only
dh = OpenSSL::PKey.generate_key(dh_params) # generates a key pair


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'ext/openssl/ossl_pkey_dh.c', line 75

static VALUE
ossl_dh_initialize(int argc, VALUE *argv, VALUE self)
{
    EVP_PKEY *pkey;
    int type;
    DH *dh;
    BIO *in = NULL;
    VALUE arg;

    TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey);
    if (pkey)
        rb_raise(rb_eTypeError, "pkey already initialized");

    /* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */
    if (rb_scan_args(argc, argv, "01", &arg) == 0) {
#ifdef OSSL_HAVE_IMMUTABLE_PKEY
        rb_raise(rb_eArgError, "OpenSSL::PKey::DH.new cannot be called " \
                 "without arguments; pkeys are immutable with OpenSSL 3.0");
#else
        dh = DH_new();
        if (!dh)
            ossl_raise(ePKeyError, "DH_new");
        goto legacy;
#endif
    }

    arg = ossl_to_der_if_possible(arg);
    in = ossl_obj2bio(&arg);

    /*
     * On OpenSSL <= 1.1.1 and current versions of LibreSSL, the generic
     * routine does not support DER-encoded parameters
     */
    dh = d2i_DHparams_bio(in, NULL);
    if (dh)
        goto legacy;
    OSSL_BIO_reset(in);

    pkey = ossl_pkey_read_generic(in, Qnil);
    BIO_free(in);
    if (!pkey)
        ossl_raise(ePKeyError, "could not parse pkey");

    type = EVP_PKEY_base_id(pkey);
    if (type != EVP_PKEY_DH) {
        EVP_PKEY_free(pkey);
        rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type));
    }
    RTYPEDDATA_DATA(self) = pkey;
    return self;

  legacy:
    BIO_free(in);
    pkey = EVP_PKEY_new();
    if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) {
        EVP_PKEY_free(pkey);
        DH_free(dh);
        ossl_raise(ePKeyError, "EVP_PKEY_assign_DH");
    }
    RTYPEDDATA_DATA(self) = pkey;
    return self;
}

Class Method Details

.generate(size, generator = 2, &blk) ⇒ Object

:call-seq:

DH.generate(size, generator = 2) -> dh

Creates a new DH instance from scratch by generating random parameters and a key pair.

See also OpenSSL::PKey.generate_parameters and OpenSSL::PKey.generate_key.

size

The desired key size in bits.

generator

The generator.



133
134
135
136
137
138
139
# File 'lib/openssl/pkey.rb', line 133

def generate(size, generator = 2, &blk)
  dhparams = OpenSSL::PKey.generate_parameters("DH", {
    "dh_paramgen_prime_len" => size,
    "dh_paramgen_generator" => generator,
  }, &blk)
  OpenSSL::PKey.generate_key(dhparams)
end

.new(*args, &blk) ⇒ Object

Handle DH.new(size, generator) form here; new(str) and new() forms are handled by #initialize



143
144
145
146
147
148
149
# File 'lib/openssl/pkey.rb', line 143

def new(*args, &blk) # :nodoc:
  if args[0].is_a?(Integer)
    generate(*args, &blk)
  else
    super
  end
end

Instance Method Details

#compute_key(pub_bn) ⇒ Object

:call-seq:

dh.compute_key(pub_bn) -> string

Returns a String containing a shared secret computed from the other party’s public value.

This method is provided for backwards compatibility, and calls #derive internally.

Parameters

  • pub_bn is a OpenSSL::BN, not the DH instance returned by DH#public_key as that contains the DH parameters only.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/openssl/pkey.rb', line 64

def compute_key(pub_bn)
  # FIXME: This is constructing an X.509 SubjectPublicKeyInfo and is very
  # inefficient
  obj = OpenSSL::ASN1.Sequence([
    OpenSSL::ASN1.Sequence([
      OpenSSL::ASN1.ObjectId("dhKeyAgreement"),
      OpenSSL::ASN1.Sequence([
        OpenSSL::ASN1.Integer(p),
        OpenSSL::ASN1.Integer(g),
      ]),
    ]),
    OpenSSL::ASN1.BitString(OpenSSL::ASN1.Integer(pub_bn).to_der),
  ])
  derive(OpenSSL::PKey.read(obj.to_der))
end

#exportaString #to_pemaString #to_saString Also known as: to_pem, to_s

Serializes the DH parameters to a PEM-encoding.

Note that any existing per-session public/private keys will not get encoded, just the Diffie-Hellman parameters will be encoded.

PEM-encoded parameters will look like:

-----BEGIN DH PARAMETERS-----
[...]
-----END DH PARAMETERS-----

See also #public_to_pem (X.509 SubjectPublicKeyInfo) and #private_to_pem (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for serialization with the private or public key components.

Overloads:

  • #exportaString

    Returns:

    • (aString)
  • #to_pemaString

    Returns:

    • (aString)
  • #to_saString

    Returns:

    • (aString)


243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'ext/openssl/ossl_pkey_dh.c', line 243

static VALUE
ossl_dh_export(VALUE self)
{
    OSSL_3_const DH *dh;
    BIO *out;
    VALUE str;

    GetDH(self, dh);
    if (!(out = BIO_new(BIO_s_mem()))) {
        ossl_raise(ePKeyError, NULL);
    }
    if (!PEM_write_bio_DHparams(out, dh)) {
        BIO_free(out);
        ossl_raise(ePKeyError, NULL);
    }
    str = ossl_membio2str(out);

    return str;
}

#generate_key!Object

:call-seq:

dh.generate_key! -> self

Generates a private and public key unless a private key already exists. If this DH instance was generated from public DH parameters (e.g. by encoding the result of DH#public_key), then this method needs to be called first in order to generate the per-session keys before performing the actual key exchange.

Deprecated in version 3.0. This method is incompatible with OpenSSL 3.0.0 or later.

See also OpenSSL::PKey.generate_key.

Example:

# DEPRECATED USAGE: This will not work on OpenSSL 3.0 or later
dh0 = OpenSSL::PKey::DH.new(2048)
dh = dh0.public_key # #public_key only copies the DH parameters (contrary to the name)
dh.generate_key!
puts dh.private? # => true
puts dh0.pub_key == dh.pub_key #=> false

# With OpenSSL::PKey.generate_key
dh0 = OpenSSL::PKey::DH.new(2048)
dh = OpenSSL::PKey.generate_key(dh0)
puts dh0.pub_key == dh.pub_key #=> false


106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/openssl/pkey.rb', line 106

def generate_key!
  if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
    raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \
    "use OpenSSL::PKey.generate_key instead"
  end

  unless priv_key
    tmp = OpenSSL::PKey.generate_key(self)
    set_key(tmp.pub_key, tmp.priv_key)
  end
  self
end

#initialize_copy(other) ⇒ Object

:nodoc:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'ext/openssl/ossl_pkey_dh.c', line 140

static VALUE
ossl_dh_initialize_copy(VALUE self, VALUE other)
{
    EVP_PKEY *pkey;
    DH *dh, *dh_other;
    const BIGNUM *pub, *priv;

    TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey);
    if (pkey)
        rb_raise(rb_eTypeError, "pkey already initialized");
    GetDH(other, dh_other);

    dh = DHparams_dup(dh_other);
    if (!dh)
        ossl_raise(ePKeyError, "DHparams_dup");

    DH_get0_key(dh_other, &pub, &priv);
    if (pub) {
        BIGNUM *pub2 = BN_dup(pub);
        BIGNUM *priv2 = BN_dup(priv);

        if (!pub2 || (priv && !priv2)) {
            BN_clear_free(pub2);
            BN_clear_free(priv2);
            ossl_raise(ePKeyError, "BN_dup");
        }
        DH_set0_key(dh, pub2, priv2);
    }

    pkey = EVP_PKEY_new();
    if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) {
        EVP_PKEY_free(pkey);
        DH_free(dh);
        ossl_raise(ePKeyError, "EVP_PKEY_assign_DH");
    }
    RTYPEDDATA_DATA(self) = pkey;
    return self;
}

#paramsObject

:call-seq:

dh.params -> hash

Stores all parameters of key to a Hash.

The hash has keys ‘p’, ‘q’, ‘g’, ‘pub_key’, and ‘priv_key’.



46
47
48
49
50
# File 'lib/openssl/pkey.rb', line 46

def params
  %w{p q g pub_key priv_key}.map { |name|
    [name, send(name)]
  }.to_h
end

#params_ok?Boolean

Validates the Diffie-Hellman parameters associated with this instance. It checks whether a safe prime and a suitable generator are used. If this is not the case, false is returned.

See also the man page EVP_PKEY_param_check(3).

Returns:

  • (Boolean)


306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'ext/openssl/ossl_pkey_dh.c', line 306

static VALUE
ossl_dh_check_params(VALUE self)
{
    int ret;
#ifdef HAVE_EVP_PKEY_CHECK
    EVP_PKEY *pkey;
    EVP_PKEY_CTX *pctx;

    GetPKey(self, pkey);
    pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
    if (!pctx)
        ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
    ret = EVP_PKEY_param_check(pctx);
    EVP_PKEY_CTX_free(pctx);
#else
    DH *dh;
    int codes;

    GetDH(self, dh);
    ret = DH_check(dh, &codes) == 1 && codes == 0;
#endif

    if (ret == 1)
        return Qtrue;
    else {
        /* DH_check_ex() will put error entry on failure */
        ossl_clear_error();
        return Qfalse;
    }
}

#private?Boolean

Indicates whether this DH instance has a private key associated with it or not. The private key may be retrieved with DH#priv_key.

Returns:

  • (Boolean)


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'ext/openssl/ossl_pkey_dh.c', line 206

static VALUE
ossl_dh_is_private(VALUE self)
{
    OSSL_3_const DH *dh;
    const BIGNUM *bn;

    GetDH(self, dh);
    DH_get0_key(dh, NULL, &bn);

#if !defined(OPENSSL_NO_ENGINE)
    return (bn || DH_get0_engine((DH *)dh)) ? Qtrue : Qfalse;
#else
    return bn ? Qtrue : Qfalse;
#endif
}

#public?Boolean

Indicates whether this DH instance has a public key associated with it or not. The public key may be retrieved with DH#pub_key.

Returns:

  • (Boolean)


187
188
189
190
191
192
193
194
195
196
197
# File 'ext/openssl/ossl_pkey_dh.c', line 187

static VALUE
ossl_dh_is_public(VALUE self)
{
    OSSL_3_const DH *dh;
    const BIGNUM *bn;

    GetDH(self, dh);
    DH_get0_key(dh, &bn, NULL);

    return bn ? Qtrue : Qfalse;
}

#public_keyObject

:call-seq:

dh.public_key -> dhnew

Returns a new DH instance that carries just the DH parameters.

Contrary to the method name, the returned DH object contains only parameters and not the public key.

This method is provided for backwards compatibility. In most cases, there is no need to call this method.

For the purpose of re-generating the key pair while keeping the parameters, check OpenSSL::PKey.generate_key.

Example:

# OpenSSL::PKey::DH.generate by default generates a random key pair
dh1 = OpenSSL::PKey::DH.generate(2048)
p dh1.priv_key #=> #<OpenSSL::BN 1288347...>
dhcopy = dh1.public_key
p dhcopy.priv_key #=> nil


36
37
38
# File 'lib/openssl/pkey.rb', line 36

def public_key
  DH.new(to_der)
end

#set_key(pub_key, priv_key) ⇒ self

Sets pub_key and priv_key for the DH instance. priv_key may be nil.

Returns:

  • (self)

#set_pqg(p, q, g) ⇒ self

Sets p, q, g to the DH instance.

Returns:

  • (self)

#to_deraString

Serializes the DH parameters to a DER-encoding

Note that any existing per-session public/private keys will not get encoded, just the Diffie-Hellman parameters will be encoded.

See also #public_to_der (X.509 SubjectPublicKeyInfo) and #private_to_der (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for serialization with the private or public key components.

Returns:

  • (aString)


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'ext/openssl/ossl_pkey_dh.c', line 276

static VALUE
ossl_dh_to_der(VALUE self)
{
    OSSL_3_const DH *dh;
    unsigned char *p;
    long len;
    VALUE str;

    GetDH(self, dh);
    if((len = i2d_DHparams(dh, NULL)) <= 0)
        ossl_raise(ePKeyError, NULL);
    str = rb_str_new(0, len);
    p = (unsigned char *)RSTRING_PTR(str);
    if(i2d_DHparams(dh, &p) < 0)
        ossl_raise(ePKeyError, NULL);
    ossl_str_adjust(str, p);

    return str;
}