Passed
Push — master ( 09c209...7211d4 )
by Roeland
11:17 queued 12s
created

Crypto   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 100
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 100
rs 10
c 0
b 0
f 0
wmc 13

4 Methods

Rating   Name   Duplication   Size   Complexity  
A calculateHMAC() 0 11 2
A encrypt() 0 14 2
B decrypt() 0 35 8
A __construct() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Andreas Fischer <[email protected]>
9
 * @author Christoph Wurst <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC\Security;
31
32
use OCP\IConfig;
33
use OCP\Security\ICrypto;
34
use OCP\Security\ISecureRandom;
35
use phpseclib\Crypt\AES;
36
use phpseclib\Crypt\Hash;
37
38
/**
39
 * Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
40
 * it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
41
 *
42
 * Usage:
43
 * $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
44
 * $encryptWithCustompassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
45
 *
46
 * @package OC\Security
47
 */
48
class Crypto implements ICrypto {
49
	/** @var AES $cipher */
50
	private $cipher;
51
	/** @var int */
52
	private $ivLength = 16;
53
	/** @var IConfig */
54
	private $config;
55
56
	/**
57
	 * @param IConfig $config
58
	 * @param ISecureRandom $random
59
	 */
60
	public function __construct(IConfig $config) {
61
		$this->cipher = new AES();
62
		$this->config = $config;
63
	}
64
65
	/**
66
	 * @param string $message The message to authenticate
67
	 * @param string $password Password to use (defaults to `secret` in config.php)
68
	 * @return string Calculated HMAC
69
	 */
70
	public function calculateHMAC(string $message, string $password = ''): string {
71
		if ($password === '') {
72
			$password = $this->config->getSystemValue('secret');
73
		}
74
75
		// Append an "a" behind the password and hash it to prevent reusing the same password as for encryption
76
		$password = hash('sha512', $password . 'a');
77
78
		$hash = new Hash('sha512');
79
		$hash->setKey($password);
80
		return $hash->hash($message);
81
	}
82
83
	/**
84
	 * Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
85
	 * @param string $plaintext
86
	 * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
87
	 * @return string Authenticated ciphertext
88
	 */
89
	public function encrypt(string $plaintext, string $password = ''): string {
90
		if ($password === '') {
91
			$password = $this->config->getSystemValue('secret');
92
		}
93
		$this->cipher->setPassword($password);
94
95
		$iv = \random_bytes($this->ivLength);
96
		$this->cipher->setIV($iv);
97
98
		$ciphertext = bin2hex($this->cipher->encrypt($plaintext));
99
		$iv = bin2hex($iv);
100
		$hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password));
101
102
		return $ciphertext.'|'.$iv.'|'.$hmac.'|2';
103
	}
104
105
	/**
106
	 * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
107
	 * @param string $authenticatedCiphertext
108
	 * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
109
	 * @return string plaintext
110
	 * @throws \Exception If the HMAC does not match
111
	 * @throws \Exception If the decryption failed
112
	 */
113
	public function decrypt(string $authenticatedCiphertext, string $password = ''): string {
114
		if ($password === '') {
115
			$password = $this->config->getSystemValue('secret');
116
		}
117
		$this->cipher->setPassword($password);
118
119
		$parts = explode('|', $authenticatedCiphertext);
120
		$partCount = \count($parts);
121
		if ($partCount < 3 || $partCount > 4) {
122
			throw new \Exception('Authenticated ciphertext could not be decoded.');
123
		}
124
125
		$ciphertext = hex2bin($parts[0]);
126
		$iv = $parts[1];
127
		$hmac = hex2bin($parts[2]);
128
129
		if ($partCount === 4) {
130
			$version = $parts[3];
131
			if ($version === '2') {
132
				$iv = hex2bin($iv);
133
			}
134
		}
135
136
		$this->cipher->setIV($iv);
137
138
		if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $password), $hmac)) {
139
			throw new \Exception('HMAC does not match.');
140
		}
141
142
		$result = $this->cipher->decrypt($ciphertext);
143
		if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
144
			throw new \Exception('Decryption failed');
145
		}
146
147
		return $result;
148
	}
149
}
150