Completed
Push — master ( f2605c...7239af )
by BENOIT
06:31 queued 10s
created

Shh::changePassphrase()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace BenTools\Shh;
4
5
final class Shh
6
{
7
    private const DEFAULT_OPENSSL_GENERATION_CONFIGURATION = [
8
        'digest_alg'       => 'sha512',
9
        'private_key_bits' => 4096,
10
        'private_key_type' => \OPENSSL_KEYTYPE_RSA,
11
    ];
12
13
    /**
14
     * @var string
15
     */
16
    private $publicKey;
17
18
    /**
19
     * @var string|null
20
     */
21
    private $privateKey;
22
23
    /**
24
     * @var string|null
25
     */
26
    private $passphrase;
27
28
    /**
29
     * @var resource
30
     */
31
    private $resource;
32
33
    /**
34
     * Shh constructor.
35
     */
36
    public function __construct(string $publicKey, ?string $privateKey = null, ?string $passphrase = null)
37
    {
38
        $this->publicKey = self::normalize($publicKey);
39
        $this->privateKey = null === $privateKey ? null : self::normalize($privateKey);
40
        $this->passphrase = $passphrase;
41
    }
42
43
    /**
44
     * @return resource
45
     */
46
    private function getPublicKeyAsResource()
47
    {
48
        if (null === $this->resource) {
49
            $this->resource = \openssl_pkey_get_public($this->publicKey)
50
                or ShhException::throwFromLastOpenSSLError('Unable to open resource.');
51
        }
52
53
        return $this->resource;
54
    }
55
56
    /**
57
     * @param string $payload
58
     * @return string
59
     */
60
    public function encrypt(string $payload): string
61
    {
62
        $resource = $this->getPublicKeyAsResource();
63
        $success = \openssl_public_encrypt($payload, $encryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
64
        \openssl_free_key($resource);
65
        if (!$success) {
66
            throw new ShhException("Encryption failed. Ensure you are using a PUBLIC key.");
67
        }
68
69
        return \base64_encode($encryptedData);
70
    }
71
72
    /**
73
     * @param string $base64EncodedPayload
74
     * @return string
75
     */
76
    public function decrypt(string $base64EncodedPayload): string
77
    {
78
        if (null === $this->privateKey) {
79
            throw new ShhException('Unable to decrypt payload: no private key provided.');
80
        }
81
82
        $payload = \base64_decode($base64EncodedPayload);
83
84
        if (false === $payload) {
85
            throw new ShhException('Encrypted payload was not provided as Base64.');
86
        }
87
88
        $resource = \openssl_pkey_get_private($this->privateKey, $this->passphrase)
89
            or ShhException::throwFromLastOpenSSLError('Private key seems corrupted.');
90
91
        $success = \openssl_private_decrypt($payload, $decryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
92
        \openssl_free_key($resource);
93
94
        if (!$success) {
95
            throw new ShhException("Decryption failed. Ensure you are using (1) A PRIVATE key, and (2) the correct one.");
96
        }
97
98
        return $decryptedData;
99
    }
100
101
    /**
102
     * Generate a new private/public key pair.
103
     *
104
     * @param string|null $passphrase
105
     * @param array       $config
106
     * @return array - [privateKey, publicKey]
107
     */
108
    public static function generateKeyPair(?string $passphrase = null, array $config = self::DEFAULT_OPENSSL_GENERATION_CONFIGURATION): array
109
    {
110
        $resource = \openssl_pkey_new($config)
111
            or ShhException::throwFromLastOpenSSLError('Unable to open resource.');
112
113
        $success = \openssl_pkey_export($resource, $privateKey, $passphrase);
114
115
        if (false === $success) {
116
            ShhException::throwFromLastOpenSSLError('Private key generation failed.');
117
        }
118
119
        $publicKey = \openssl_pkey_get_details($resource)['key'];
120
121
        return [$publicKey, $privateKey];
122
    }
123
124
    /**
125
     * Change passphrase and return a new private key.
126
     *
127
     * @param string      $privateKey
128
     * @param string|null $oldPassphrase
129
     * @param string|null $newPassphrase
130
     * @return string
131
     */
132
    public static function changePassphrase(string $privateKey, ?string $oldPassphrase, ?string $newPassphrase): string
133
    {
134
        $resource = \openssl_pkey_get_private(self::normalize($privateKey), $oldPassphrase);
135
        $success = @\openssl_pkey_export($resource, $newPrivateKey, $newPassphrase);
136
137
        if (false === $success) {
138
            throw new ShhException('Wrong passphrase or inexistent private key.');
139
        }
140
141
        return $newPrivateKey;
142
    }
143
144
    /**
145
     * @param string $key
146
     * @return string
147
     */
148
    private static function normalize(string $key): string
149
    {
150
        return (0 === \strpos($key, '/')) ? 'file://'.$key : $key;
151
    }
152
}
153