1<?php
2
3/**
4 * DSA Private Key
5 *
6 * @category  Crypt
7 * @package   DSA
8 * @author    Jim Wigginton <terrafrost@php.net>
9 * @copyright 2015 Jim Wigginton
10 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
11 * @link      http://phpseclib.sourceforge.net
12 */
13
14namespace phpseclib3\Crypt\DSA;
15
16use phpseclib3\Crypt\Common;
17use phpseclib3\Crypt\DSA;
18use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
19use phpseclib3\Math\BigInteger;
20
21/**
22 * DSA Private Key
23 *
24 * @package DSA
25 * @author  Jim Wigginton <terrafrost@php.net>
26 * @access  public
27 */
28class PrivateKey extends DSA implements Common\PrivateKey
29{
30    use Common\Traits\PasswordProtected;
31
32    /**
33     * DSA secret exponent x
34     *
35     * @var \phpseclib3\Math\BigInteger
36     * @access private
37     */
38    protected $x;
39
40    /**
41     * Returns the public key
42     *
43     * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
44     * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
45     * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
46     * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
47     * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
48     * by getting a DSA PKCS8 public key:
49     *
50     * "openssl dsa -in private.dsa -pubout -outform PEM"
51     *
52     * ie. just swap out rsa with dsa in the rsa command above.
53     *
54     * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
55     * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
56     * without the parameters and the PKCS1 DSA public key format does not include the parameters.
57     *
58     * @see self::getPrivateKey()
59     * @access public
60     * @return mixed
61     */
62    public function getPublicKey()
63    {
64        $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
65
66        if (!isset($this->y)) {
67            $this->y = $this->g->powMod($this->x, $this->p);
68        }
69
70        $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
71
72        return DSA::loadFormat('PKCS8', $key)
73            ->withHash($this->hash->getHash())
74            ->withSignatureFormat($this->shortFormat);
75    }
76
77    /**
78     * Create a signature
79     *
80     * @see self::verify()
81     * @access public
82     * @param string $message
83     * @return mixed
84     */
85    public function sign($message)
86    {
87        $format = $this->sigFormat;
88
89        if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
90            $signature = '';
91            $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash());
92
93            if ($result) {
94                if ($this->shortFormat == 'ASN1') {
95                    return $signature;
96                }
97
98                extract(ASN1Signature::load($signature));
99
100                return $format::save($r, $s);
101            }
102        }
103
104        $h = $this->hash->hash($message);
105        $h = $this->bits2int($h);
106
107        while (true) {
108            $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
109            $r = $this->g->powMod($k, $this->p);
110            list(, $r) = $r->divide($this->q);
111            if ($r->equals(self::$zero)) {
112                continue;
113            }
114            $kinv = $k->modInverse($this->q);
115            $temp = $h->add($this->x->multiply($r));
116            $temp = $kinv->multiply($temp);
117            list(, $s) = $temp->divide($this->q);
118            if (!$s->equals(self::$zero)) {
119                break;
120            }
121        }
122
123        // the following is an RFC6979 compliant implementation of deterministic DSA
124        // it's unused because it's mainly intended for use when a good CSPRNG isn't
125        // available. if phpseclib's CSPRNG isn't good then even key generation is
126        // suspect
127        /*
128        $h1 = $this->hash->hash($message);
129        $k = $this->computek($h1);
130        $r = $this->g->powMod($k, $this->p);
131        list(, $r) = $r->divide($this->q);
132        $kinv = $k->modInverse($this->q);
133        $h1 = $this->bits2int($h1);
134        $temp = $h1->add($this->x->multiply($r));
135        $temp = $kinv->multiply($temp);
136        list(, $s) = $temp->divide($this->q);
137        */
138
139        return $format::save($r, $s);
140    }
141
142    /**
143     * Returns the private key
144     *
145     * @param string $type
146     * @param array $options optional
147     * @return string
148     */
149    public function toString($type, array $options = [])
150    {
151        $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
152
153        if (!isset($this->y)) {
154            $this->y = $this->g->powMod($this->x, $this->p);
155        }
156
157        return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options);
158    }
159}
160