Subversion Repositories oidplus

Rev

Rev 1101 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
 
3
/**
4
 * EC Private Key
5
 *
6
 * @author    Jim Wigginton <terrafrost@php.net>
7
 * @copyright 2015 Jim Wigginton
8
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
9
 * @link      http://phpseclib.sourceforge.net
10
 */
11
 
12
namespace phpseclib3\Crypt\EC;
13
 
14
use phpseclib3\Common\Functions\Strings;
15
use phpseclib3\Crypt\Common;
16
use phpseclib3\Crypt\EC;
17
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
18
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
19
use phpseclib3\Crypt\EC\Curves\Curve25519;
20
use phpseclib3\Crypt\EC\Curves\Ed25519;
21
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
22
use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
23
use phpseclib3\Crypt\Hash;
24
use phpseclib3\Exception\UnsupportedOperationException;
25
use phpseclib3\Math\BigInteger;
26
 
27
/**
28
 * EC Private Key
29
 *
30
 * @author  Jim Wigginton <terrafrost@php.net>
31
 */
1101 daniel-mar 32
final class PrivateKey extends EC implements Common\PrivateKey
827 daniel-mar 33
{
34
    use Common\Traits\PasswordProtected;
35
 
36
    /**
37
     * Private Key dA
38
     *
39
     * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of
40
     * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by
41
     * a certain amount whereas a BigInteger isn't.
42
     *
43
     * @var object
44
     */
45
    protected $dA;
46
 
47
    /**
1042 daniel-mar 48
     * @var string
49
     */
50
    protected $secret;
51
 
52
    /**
827 daniel-mar 53
     * Multiplies an encoded point by the private key
54
     *
55
     * Used by ECDH
56
     *
57
     * @param string $coordinates
58
     * @return string
59
     */
60
    public function multiply($coordinates)
61
    {
62
        if ($this->curve instanceof MontgomeryCurve) {
63
            if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) {
64
                return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates);
65
            }
66
 
67
            $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))];
68
            $point = $this->curve->multiplyPoint($point, $this->dA);
69
            return strrev($point[0]->toBytes(true));
70
        }
71
        if (!$this->curve instanceof TwistedEdwardsCurve) {
72
            $coordinates = "\0$coordinates";
73
        }
74
        $point = PKCS1::extractPoint($coordinates, $this->curve);
75
        $point = $this->curve->multiplyPoint($point, $this->dA);
76
        if ($this->curve instanceof TwistedEdwardsCurve) {
77
            return $this->curve->encodePoint($point);
78
        }
79
        if (empty($point)) {
80
            throw new \RuntimeException('The infinity point is invalid');
81
        }
82
        return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true);
83
    }
84
 
85
    /**
86
     * Create a signature
87
     *
88
     * @see self::verify()
89
     * @param string $message
90
     * @return mixed
91
     */
92
    public function sign($message)
93
    {
94
        if ($this->curve instanceof MontgomeryCurve) {
95
            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
96
        }
97
 
98
        $dA = $this->dA;
99
        $order = $this->curve->getOrder();
100
 
101
        $shortFormat = $this->shortFormat;
102
        $format = $this->sigFormat;
103
        if ($format === false) {
104
            return false;
105
        }
106
 
107
        if ($this->curve instanceof TwistedEdwardsCurve) {
108
            if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
109
                $result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium'));
110
                return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result;
111
            }
112
 
113
            // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not.
114
            // quoting https://tools.ietf.org/html/rfc8032#section-8.5 ,
115
            // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used"
116
            $A = $this->curve->encodePoint($this->QA);
117
            $curve = $this->curve;
118
            $hash = new Hash($curve::HASH);
119
 
1042 daniel-mar 120
            $secret = substr($hash->hash($this->secret), $curve::SIZE);
827 daniel-mar 121
 
122
            if ($curve instanceof Ed25519) {
123
                $dom = !isset($this->context) ? '' :
124
                    'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
125
            } else {
126
                $context = isset($this->context) ? $this->context : '';
127
                $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
128
            }
129
            // SHA-512(dom2(F, C) || prefix || PH(M))
130
            $r = $hash->hash($dom . $secret . $message);
131
            $r = strrev($r);
132
            $r = new BigInteger($r, 256);
133
            list(, $r) = $r->divide($order);
134
            $R = $curve->multiplyPoint($curve->getBasePoint(), $r);
135
            $R = $curve->encodePoint($R);
136
            $k = $hash->hash($dom . $R . $A . $message);
137
            $k = strrev($k);
138
            $k = new BigInteger($k, 256);
139
            list(, $k) = $k->divide($order);
140
            $S = $k->multiply($dA)->add($r);
141
            list(, $S) = $S->divide($order);
142
            $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0");
143
            return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S;
144
        }
145
 
146
        if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
147
            $signature = '';
148
            // altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long
149
            // supported signing / verification
150
            // we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve;
151
            // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even
152
            // has curve-specific optimizations
1460 daniel-mar 153
            $result = openssl_sign($message, $signature, $this->withPassword()->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
827 daniel-mar 154
 
155
            if ($result) {
156
                if ($shortFormat == 'ASN1') {
157
                    return $signature;
158
                }
159
 
160
                extract(ASN1Signature::load($signature));
161
 
162
                return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
163
            }
164
        }
165
 
166
        $e = $this->hash->hash($message);
167
        $e = new BigInteger($e, 256);
168
 
169
        $Ln = $this->hash->getLength() - $order->getLength();
170
        $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
171
 
172
        while (true) {
173
            $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one));
174
            list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
175
            $x = $x->toBigInteger();
176
            list(, $r) = $x->divide($order);
177
            if ($r->equals(self::$zero)) {
178
                continue;
179
            }
180
            $kinv = $k->modInverse($order);
181
            $temp = $z->add($dA->multiply($r));
182
            $temp = $kinv->multiply($temp);
183
            list(, $s) = $temp->divide($order);
184
            if (!$s->equals(self::$zero)) {
185
                break;
186
            }
187
        }
188
 
189
        // the following is an RFC6979 compliant implementation of deterministic ECDSA
190
        // it's unused because it's mainly intended for use when a good CSPRNG isn't
191
        // available. if phpseclib's CSPRNG isn't good then even key generation is
192
        // suspect
193
        /*
194
        // if this were actually being used it'd probably be better if this lived in load() and createKey()
195
        $this->q = $this->curve->getOrder();
196
        $dA = $this->dA->toBigInteger();
197
        $this->x = $dA;
198
 
199
        $h1 = $this->hash->hash($message);
200
        $k = $this->computek($h1);
201
        list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
202
        $x = $x->toBigInteger();
203
        list(, $r) = $x->divide($this->q);
204
        $kinv = $k->modInverse($this->q);
205
        $h1 = $this->bits2int($h1);
206
        $temp = $h1->add($dA->multiply($r));
207
        $temp = $kinv->multiply($temp);
208
        list(, $s) = $temp->divide($this->q);
209
        */
210
 
211
        return $shortFormat == 'SSH2' ? $format::save($r, $s, $this->getCurve()) : $format::save($r, $s);
212
    }
213
 
214
    /**
215
     * Returns the private key
216
     *
217
     * @param string $type
218
     * @param array $options optional
219
     * @return string
220
     */
221
    public function toString($type, array $options = [])
222
    {
223
        $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
224
 
1042 daniel-mar 225
        return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options);
827 daniel-mar 226
    }
227
 
228
    /**
229
     * Returns the public key
230
     *
231
     * @see self::getPrivateKey()
232
     * @return mixed
233
     */
234
    public function getPublicKey()
235
    {
236
        $format = 'PKCS8';
237
        if ($this->curve instanceof MontgomeryCurve) {
238
            $format = 'MontgomeryPublic';
239
        }
240
 
241
        $type = self::validatePlugin('Keys', $format, 'savePublicKey');
242
 
243
        $key = $type::savePublicKey($this->curve, $this->QA);
244
        $key = EC::loadFormat($format, $key);
245
        if ($this->curve instanceof MontgomeryCurve) {
246
            return $key;
247
        }
248
        $key = $key
249
            ->withHash($this->hash->getHash())
250
            ->withSignatureFormat($this->shortFormat);
251
        if ($this->curve instanceof TwistedEdwardsCurve) {
252
            $key = $key->withContext($this->context);
253
        }
254
        return $key;
255
    }
256
}