Shh   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
wmc 21
lcom 2
cbo 1
dl 0
loc 160
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getPublicKeyAsResource() 0 9 3
A __construct() 0 6 2
A freeResource() 0 9 2
A encrypt() 0 12 2
A decrypt() 0 24 5
A generateKeyPair() 0 16 3
A changePassphrase() 0 11 2
A normalize() 0 4 2
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
    private function freeResource(): void
57
    {
58
        if (null === $this->resource) {
59
            return;
60
        }
61
62
        \openssl_free_key($this->resource);
63
        $this->resource = null;
64
    }
65
66
    /**
67
     * @param string $payload
68
     * @return string
69
     */
70
    public function encrypt(string $payload): string
71
    {
72
        $resource = $this->getPublicKeyAsResource();
73
        $success = \openssl_public_encrypt($payload, $encryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
74
        $this->freeResource();
75
76
        if (!$success) {
77
            throw new ShhException("Encryption failed. Ensure you are using a PUBLIC key.");
78
        }
79
80
        return \base64_encode($encryptedData);
81
    }
82
83
    /**
84
     * @param string $base64EncodedPayload
85
     * @return string
86
     */
87
    public function decrypt(string $base64EncodedPayload): string
88
    {
89
        if (null === $this->privateKey) {
90
            throw new ShhException('Unable to decrypt payload: no private key provided.');
91
        }
92
93
        $payload = \base64_decode($base64EncodedPayload);
94
95
        if (false === $payload) {
96
            throw new ShhException('Encrypted payload was not provided as Base64.');
97
        }
98
99
        $resource = \openssl_pkey_get_private($this->privateKey, $this->passphrase)
100
            or ShhException::throwFromLastOpenSSLError('Private key seems corrupted.');
101
102
        $success = \openssl_private_decrypt($payload, $decryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
103
        \openssl_free_key($resource);
104
105
        if (!$success) {
106
            throw new ShhException("Decryption failed. Ensure you are using (1) A PRIVATE key, and (2) the correct one.");
107
        }
108
109
        return $decryptedData;
110
    }
111
112
    /**
113
     * Generate a new private/public key pair.
114
     *
115
     * @param string|null $passphrase
116
     * @param array       $config
117
     * @return array - [publicKey, privateKey]
118
     */
119
    public static function generateKeyPair(?string $passphrase = null, array $config = []): array
120
    {
121
        $config += self::DEFAULT_OPENSSL_GENERATION_CONFIGURATION;
122
        $resource = \openssl_pkey_new($config)
123
            or ShhException::throwFromLastOpenSSLError('Unable to open resource.');
124
125
        $success = \openssl_pkey_export($resource, $privateKey, $passphrase);
126
127
        if (false === $success) {
128
            ShhException::throwFromLastOpenSSLError('Private key generation failed.');
129
        }
130
131
        $publicKey = \openssl_pkey_get_details($resource)['key'];
132
133
        return [$publicKey, $privateKey];
134
    }
135
136
    /**
137
     * Change passphrase and return a new private key.
138
     *
139
     * @param string      $privateKey
140
     * @param string|null $oldPassphrase
141
     * @param string|null $newPassphrase
142
     * @return string
143
     */
144
    public static function changePassphrase(string $privateKey, ?string $oldPassphrase, ?string $newPassphrase): string
145
    {
146
        $resource = \openssl_pkey_get_private(self::normalize($privateKey), $oldPassphrase);
147
        $success = @\openssl_pkey_export($resource, $newPrivateKey, $newPassphrase);
148
149
        if (false === $success) {
150
            throw new ShhException('Wrong passphrase or inexistent private key.');
151
        }
152
153
        return $newPrivateKey;
154
    }
155
156
    /**
157
     * @param string $key
158
     * @return string
159
     */
160
    private static function normalize(string $key): string
161
    {
162
        return (0 === \strpos($key, '/')) ? 'file://'.$key : $key;
163
    }
164
}
165