1 | <?php |
||
48 | class EncryptService { |
||
49 | |||
50 | /** |
||
51 | * Supported cipher algorithms accompanied by their key/block sizes in bytes |
||
52 | * |
||
53 | * OpenSSL has no equivalent of mcrypt_get_key_size() and mcrypt_get_block_size() hence sizes stored here. |
||
54 | * |
||
55 | * @var array |
||
56 | */ |
||
57 | const SUPPORTED_ALGORITHMS = array( |
||
58 | 'aes-256-cbc' => array('name' => 'AES-256', 'keySize' => 32, 'blockSize' => 32), |
||
59 | 'bf' => array('name' => 'BF', 'keySize' => 16, 'blockSize' => 8), |
||
60 | 'des' => array('name' => 'DES', 'keySize' => 7, 'blockSize' => 8), |
||
61 | 'des-ede3' => array('name' => 'DES-EDE3', 'keySize' => 21, 'blockSize' => 8), // 3 different 56-bit keys |
||
62 | 'cast5' => array('name' => 'CAST5', 'keySize' => 16, 'blockSize' => 8), |
||
63 | ); |
||
64 | |||
65 | const OP_ENCRYPT = 'encrypt'; |
||
66 | const OP_DECRYPT = 'decrypt'; |
||
67 | |||
68 | // The fields of a credential which are encrypted |
||
69 | public $encrypted_credential_fields = array( |
||
70 | 'description', 'username', 'password', 'files', 'custom_fields', 'otp', 'email', 'tags', 'url' |
||
71 | ); |
||
72 | |||
73 | // Contains the server key |
||
74 | private $server_key; |
||
75 | |||
76 | /** |
||
77 | * @var string $cipher The openssl cipher to use for this instance |
||
78 | */ |
||
79 | protected $cipher = ''; |
||
80 | |||
81 | /** |
||
82 | * @var int $rounds The number of rounds to feed into PBKDF2 for key generation |
||
83 | */ |
||
84 | protected $rounds = 100; |
||
85 | |||
86 | /** |
||
87 | * Constructor! |
||
88 | * |
||
89 | * @param SettingsService $settings |
||
90 | */ |
||
91 | public function __construct(SettingsService $settings) { |
||
92 | $this->cipher = $settings->getAppSetting('server_side_encryption', 'aes-256-cbc'); |
||
93 | $password_salt = \OC::$server->getConfig()->getSystemValue('passwordsalt', ''); |
||
94 | $secret = \OC::$server->getConfig()->getSystemValue('secret', ''); |
||
95 | $this->server_key = $password_salt . $secret; |
||
96 | $this->rounds = $settings->getAppSetting('rounds_pbkdf2_stretching', 100); |
||
97 | } |
||
98 | |||
99 | /** |
||
100 | * Create an encryption key. Based on given parameters |
||
101 | * |
||
102 | * @param string $userKey The user key to use. This should be specific to this user. |
||
103 | * @param string $serverKey The server key |
||
104 | * @param string $userSuppliedKey A key from the credential (eg guid, name or tags) |
||
105 | * @return string |
||
106 | */ |
||
107 | |||
108 | public static function makeKey($userKey, $serverKey, $userSuppliedKey) { |
||
109 | $key = hash_hmac('sha512', $userKey, $serverKey); |
||
110 | $key = hash_hmac('sha512', $key, $userSuppliedKey); |
||
111 | return $key; |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Get the maximum key size for the selected cipher and mode of operation |
||
116 | * |
||
117 | * @return int Value is in bytes |
||
118 | */ |
||
119 | public function getKeySize() { |
||
120 | return EncryptService::SUPPORTED_ALGORITHMS[$this->cipher]['keySize']; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Decrypt the data with the provided key |
||
125 | * |
||
126 | * @param string $data_hex The encrypted datat to decrypt |
||
127 | * @param string $key The key to use for decryption |
||
128 | * |
||
129 | * @returns string|false The returned string if decryption is successful |
||
130 | * false if it is not |
||
131 | */ |
||
132 | public function decrypt($data_hex, $key) { |
||
133 | |||
134 | if (!function_exists('hex2bin')) { |
||
135 | function hex2bin($str) { |
||
136 | $sbin = ""; |
||
137 | $len = strlen($str); |
||
138 | for ($i = 0; $i < $len; $i += 2) { |
||
139 | $sbin .= pack("H*", substr($str, $i, 2)); |
||
140 | } |
||
141 | |||
142 | return $sbin; |
||
143 | } |
||
144 | } |
||
145 | |||
146 | $data = hex2bin($data_hex); |
||
147 | |||
148 | $salt = substr($data, 0, 128); |
||
149 | $enc = substr($data, 128, -64); |
||
150 | $mac = substr($data, -64); |
||
151 | |||
152 | list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key); |
||
153 | |||
154 | if (!$this->hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) { |
||
155 | return false; |
||
156 | } |
||
157 | |||
158 | $dec = openssl_decrypt($enc, $this->cipher, $cipherKey, true, $iv); |
||
159 | $data = $this->unpad($dec); |
||
160 | |||
161 | return $data; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Encrypt the supplied data using the supplied key |
||
166 | * |
||
167 | * @param string $data The data to encrypt |
||
168 | * @param string $key The key to encrypt with |
||
169 | * |
||
170 | * @returns string The encrypted data |
||
171 | */ |
||
172 | public function encrypt($data, $key) { |
||
173 | if (function_exists('random_bytes')) { |
||
174 | $salt = random_bytes(128); |
||
175 | } else { |
||
176 | $salt = openssl_random_pseudo_bytes(128); |
||
177 | } |
||
178 | list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key); |
||
179 | $data = $this->pad($data); |
||
180 | $enc = openssl_encrypt($data, $this->cipher, $cipherKey, true, $iv); |
||
181 | $mac = hash_hmac('sha512', $enc, $macKey, true); |
||
182 | $data = bin2hex($salt . $enc . $mac); |
||
183 | return $data; |
||
184 | |||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Generates a set of keys given a random salt and a master key |
||
189 | * |
||
190 | * @param string $salt A random string to change the keys each encryption |
||
191 | * @param string $key The supplied key to encrypt with |
||
192 | * |
||
193 | * @returns array An array of keys (a cipher key, a mac key, and a IV) |
||
194 | */ |
||
195 | protected function getKeys($salt, $key) { |
||
207 | |||
208 | protected function hash_equals($a, $b) { |
||
209 | if (function_exists('random_bytes')) { |
||
210 | $key = random_bytes(128); |
||
211 | } else { |
||
212 | $key = openssl_random_pseudo_bytes(128); |
||
213 | } |
||
214 | return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Stretch the key using the PBKDF2 algorithm |
||
219 | * |
||
220 | * @see http://en.wikipedia.org/wiki/PBKDF2 |
||
221 | * |
||
222 | * @param string $algo The algorithm to use |
||
223 | * @param string $key The key to stretch |
||
224 | * @param string $salt A random salt |
||
225 | * @param int $rounds The number of rounds to derive |
||
226 | * @param int $length The length of the output key |
||
227 | * |
||
228 | * @returns string The derived key. |
||
229 | */ |
||
230 | protected function pbkdf2($algo, $key, $salt, $rounds, $length) { |
||
231 | $size = strlen(hash($algo, '', true)); |
||
232 | $len = ceil($length / $size); |
||
233 | $result = ''; |
||
234 | for ($i = 1; $i <= $len; $i++) { |
||
235 | $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true); |
||
236 | $res = $tmp; |
||
237 | for ($j = 1; $j < $rounds; $j++) { |
||
238 | $tmp = hash_hmac($algo, $tmp, $key, true); |
||
239 | $res ^= $tmp; |
||
240 | } |
||
241 | $result .= $res; |
||
242 | } |
||
243 | return substr($result, 0, $length); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Pad the data with a random char chosen by the pad amount. |
||
248 | * |
||
249 | * @param $data |
||
250 | * @return string |
||
251 | */ |
||
252 | protected function pad($data) { |
||
253 | $length = $this->getKeySize(); |
||
254 | $padAmount = $length - strlen($data) % $length; |
||
255 | if ($padAmount === 0) { |
||
256 | $padAmount = $length; |
||
257 | } |
||
258 | return $data . str_repeat(chr($padAmount), $padAmount); |
||
259 | } |
||
260 | |||
261 | |||
262 | /** |
||
263 | * Unpad the the data |
||
264 | * |
||
265 | * @param $data |
||
266 | * @return bool|string |
||
267 | */ |
||
268 | protected function unpad($data) { |
||
269 | $length = $this->getKeySize(); |
||
270 | $last = ord($data[strlen($data) - 1]); |
||
271 | if ($last > $length) return false; |
||
272 | if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) { |
||
273 | return false; |
||
274 | } |
||
275 | return substr($data, 0, -1 * $last); |
||
276 | } |
||
277 | |||
278 | |||
279 | /** |
||
280 | * Encrypt a credential |
||
281 | * |
||
282 | * @param Credential|array $credential the credential to decrypt |
||
283 | * @return Credential|array |
||
284 | */ |
||
285 | public function decryptCredential($credential) { |
||
288 | |||
289 | /** |
||
290 | * Encrypt a credential |
||
291 | * |
||
292 | * @param Credential|array $credential the credential to encrypt |
||
293 | * @return Credential|array |
||
294 | * @throws \Exception |
||
295 | */ |
||
296 | public function encryptCredential($credential) { |
||
299 | |||
300 | |||
301 | private function extractKeysFromCredential($credential) { |
||
315 | |||
316 | /** |
||
317 | * Handles the encryption / decryption of a credential |
||
318 | * |
||
319 | * @param Credential|array $credential the credential to encrypt |
||
320 | * @return Credential|array |
||
321 | * @throws \Exception |
||
322 | */ |
||
323 | private function handleCredential($credential, $service_function) { |
||
341 | |||
342 | /** |
||
343 | * Encrypt a file |
||
344 | * |
||
345 | * @param File|array $file |
||
346 | * @return File|array |
||
347 | */ |
||
348 | |||
349 | public function encryptFile($file) { |
||
352 | |||
353 | /** |
||
354 | * Decrypt a file |
||
355 | * |
||
356 | * @param File|array $file |
||
357 | * @return File|array |
||
358 | */ |
||
359 | |||
360 | public function decryptFile($file) { |
||
363 | |||
364 | /** |
||
365 | * Handles the encryption / decryption of a File |
||
366 | * |
||
367 | * @param File|array $file the credential to encrypt |
||
368 | * @return File|array |
||
369 | * @throws \Exception |
||
370 | */ |
||
371 | private function handleFile($file, $service_function) { |
||
399 | } |