ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Crypto.php
Go to the documentation of this file.
1 <?php
2 
3 namespace SimpleSAML\Utils;
4 
10 class Crypto
11 {
12 
25  private static function _aesDecrypt($ciphertext, $secret)
26  {
27  if (!is_string($ciphertext)) {
28  throw new \InvalidArgumentException(
29  'Input parameter "$ciphertext" must be a string with more than 48 characters.'
30  );
31  }
33  $len = mb_strlen($ciphertext, '8bit');
34  if ($len < 48) {
35  throw new \InvalidArgumentException(
36  'Input parameter "$ciphertext" must be a string with more than 48 characters.'
37  );
38  }
39  if (!function_exists("openssl_decrypt")) {
40  throw new \SimpleSAML_Error_Exception("The openssl PHP module is not loaded.");
41  }
42 
43  // derive encryption and authentication keys from the secret
44  $key = openssl_digest($secret, 'sha512');
45 
46  $hmac = mb_substr($ciphertext, 0, 32, '8bit');
47  $iv = mb_substr($ciphertext, 32, 16, '8bit');
48  $msg = mb_substr($ciphertext, 48, $len - 48, '8bit');
49 
50  // authenticate the ciphertext
51  if (self::secureCompare(hash_hmac('sha256', $iv.$msg, substr($key, 64, 64), true), $hmac)) {
52  $plaintext = openssl_decrypt(
53  $msg,
54  'AES-256-CBC',
55  substr($key, 0, 64),
56  defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1,
57  $iv
58  );
59 
60  if ($plaintext !== false) {
61  return $plaintext;
62  }
63  }
64 
65  throw new \SimpleSAML_Error_Exception("Failed to decrypt ciphertext.");
66  }
67 
68 
81  public static function aesDecrypt($ciphertext)
82  {
83  return self::_aesDecrypt($ciphertext, Config::getSecretSalt());
84  }
85 
86 
99  private static function _aesEncrypt($data, $secret)
100  {
101  if (!is_string($data)) {
102  throw new \InvalidArgumentException('Input parameter "$data" must be a string.');
103  }
104 
105  if (!function_exists("openssl_encrypt")) {
106  throw new \SimpleSAML_Error_Exception('The openssl PHP module is not loaded.');
107  }
108 
109  // derive encryption and authentication keys from the secret
110  $key = openssl_digest($secret, 'sha512');
111 
112  // generate a random IV
113  $iv = openssl_random_pseudo_bytes(16);
114 
115  // encrypt the message
117  $ciphertext = openssl_encrypt(
118  $data,
119  'AES-256-CBC',
120  substr($key, 0, 64),
121  defined('OPENSSL_RAW_DATA') ? OPENSSL_RAW_DATA : 1,
122  $iv
123  );
124 
125  if ($ciphertext === false) {
126  throw new \SimpleSAML_Error_Exception("Failed to encrypt plaintext.");
127  }
128 
129  // return the ciphertext with proper authentication
130  return hash_hmac('sha256', $iv.$ciphertext, substr($key, 64, 64), true).$iv.$ciphertext;
131  }
132 
133 
146  public static function aesEncrypt($data)
147  {
148  return self::_aesEncrypt($data, Config::getSecretSalt());
149  }
150 
151 
160  public static function der2pem($der, $type = 'CERTIFICATE')
161  {
162  return "-----BEGIN ".$type."-----\n".
163  chunk_split(base64_encode($der), 64, "\n").
164  "-----END ".$type."-----\n";
165  }
166 
167 
195  public static function loadPrivateKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '', $full_path = false)
196  {
197  if (!is_bool($required) || !is_string($prefix) || !is_bool($full_path)) {
198  throw new \InvalidArgumentException('Invalid input parameters.');
199  }
200 
201  $file = $metadata->getString($prefix.'privatekey', null);
202  if ($file === null) {
203  // no private key found
204  if ($required) {
205  throw new \SimpleSAML_Error_Exception('No private key found in metadata.');
206  } else {
207  return null;
208  }
209  }
210 
211  if (!$full_path) {
212  $file = Config::getCertPath($file);
213  }
214 
215  $data = @file_get_contents($file);
216  if ($data === false) {
217  throw new \SimpleSAML_Error_Exception('Unable to load private key from file "'.$file.'"');
218  }
219 
220  $ret = array(
221  'PEM' => $data,
222  );
223 
224  if ($metadata->hasValue($prefix.'privatekey_pass')) {
225  $ret['password'] = $metadata->getString($prefix.'privatekey_pass');
226  }
227 
228  return $ret;
229  }
230 
231 
265  public static function loadPublicKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
266  {
267  if (!is_bool($required) || !is_string($prefix)) {
268  throw new \InvalidArgumentException('Invalid input parameters.');
269  }
270 
271  $keys = $metadata->getPublicKeys(null, false, $prefix);
272  if (!empty($keys)) {
273  foreach ($keys as $key) {
274  if ($key['type'] !== 'X509Certificate') {
275  continue;
276  }
277  if ($key['signing'] !== true) {
278  continue;
279  }
280  $certData = $key['X509Certificate'];
281  $pem = "-----BEGIN CERTIFICATE-----\n".
282  chunk_split($certData, 64).
283  "-----END CERTIFICATE-----\n";
284  $certFingerprint = strtolower(sha1(base64_decode($certData)));
285 
286  return array(
287  'certData' => $certData,
288  'PEM' => $pem,
289  'certFingerprint' => array($certFingerprint),
290  );
291  }
292  // no valid key found
293  } elseif ($metadata->hasValue($prefix.'certFingerprint')) {
294  // we only have a fingerprint available
295  $fps = $metadata->getArrayizeString($prefix.'certFingerprint');
296 
297  // normalize fingerprint(s) - lowercase and no colons
298  foreach ($fps as &$fp) {
299  assert(is_string($fp));
300  $fp = strtolower(str_replace(':', '', $fp));
301  }
302 
303  /*
304  * We can't build a full certificate from a fingerprint, and may as well return an array with only the
305  * fingerprint(s) immediately.
306  */
307  return array('certFingerprint' => $fps);
308  }
309 
310  // no public key/certificate available
311  if ($required) {
312  throw new \SimpleSAML_Error_Exception('No public key / certificate found in metadata.');
313  } else {
314  return null;
315  }
316  }
317 
318 
327  public static function pem2der($pem)
328  {
329  $pem = trim($pem);
330  $begin = "-----BEGIN ";
331  $end = "-----END ";
332  $lines = explode("\n", $pem);
333  $last = count($lines) - 1;
334 
335  if (strpos($lines[0], $begin) !== 0) {
336  throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
337  }
338  unset($lines[0]);
339  if (strpos($lines[$last], $end) !== 0) {
340  throw new \InvalidArgumentException("pem2der: input is not encoded in PEM format.");
341  }
342  unset($lines[$last]);
343 
344  return base64_decode(implode($lines));
345  }
346 
347 
365  public static function pwHash($password, $algorithm, $salt = null)
366  {
367  if (!is_string($algorithm) || !is_string($password)) {
368  throw new \InvalidArgumentException('Invalid input parameters.');
369  }
370 
371  // hash w/o salt
372  if (in_array(strtolower($algorithm), hash_algos(), true)) {
373  $alg_str = '{'.str_replace('SHA1', 'SHA', $algorithm).'}'; // LDAP compatibility
374  $hash = hash(strtolower($algorithm), $password, true);
375  return $alg_str.base64_encode($hash);
376  }
377 
378  // hash w/ salt
379  if ($salt === null) { // no salt provided, generate one
380  // default 8 byte salt, but 4 byte for LDAP SHA1 hashes
381  $bytes = ($algorithm == 'SSHA1') ? 4 : 8;
382  $salt = openssl_random_pseudo_bytes($bytes);
383  }
384 
385  if ($algorithm[0] == 'S' && in_array(substr(strtolower($algorithm), 1), hash_algos(), true)) {
386  $alg = substr(strtolower($algorithm), 1); // 'sha256' etc
387  $alg_str = '{'.str_replace('SSHA1', 'SSHA', $algorithm).'}'; // LDAP compatibility
388  $hash = hash($alg, $password.$salt, true);
389  return $alg_str.base64_encode($hash.$salt);
390  }
391 
392  throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($algorithm).'\' is not supported');
393  }
394 
395 
407  public static function secureCompare($known, $user)
408  {
409  if (function_exists('hash_equals')) {
410  // use hash_equals() if available (PHP >= 5.6)
411  return hash_equals($known, $user);
412  }
413 
414  // compare manually in constant time
415  $len = mb_strlen($known, '8bit'); // see mbstring.func_overload
416  if ($len !== mb_strlen($user, '8bit')) {
417  return false; // length differs
418  }
419  $diff = 0;
420  for ($i = 0; $i < $len; $i++) {
421  $diff |= ord($known[$i]) ^ ord($user[$i]);
422  }
423  // if all the bytes in $a and $b are identical, $diff should be equal to 0
424  return $diff === 0;
425  }
426 
427 
440  public static function pwValid($hash, $password)
441  {
442  if (!is_string($hash) || !is_string($password)) {
443  throw new \InvalidArgumentException('Invalid input parameters.');
444  }
445 
446  // match algorithm string (e.g. '{SSHA256}', '{MD5}')
447  if (preg_match('/^{(.*?)}(.*)$/', $hash, $matches)) {
448  // LDAP compatibility
449  $alg = preg_replace('/^(S?SHA)$/', '${1}1', $matches[1]);
450 
451  // hash w/o salt
452  if (in_array(strtolower($alg), hash_algos(), true)) {
453  return self::secureCompare($hash, self::pwHash($password, $alg));
454  }
455 
456  // hash w/ salt
457  if ($alg[0] === 'S' && in_array(substr(strtolower($alg), 1), hash_algos(), true)) {
458  $php_alg = substr(strtolower($alg), 1);
459 
460  // get hash length of this algorithm to learn how long the salt is
461  $hash_length = strlen(hash($php_alg, '', true));
462  $salt = substr(base64_decode($matches[2]), $hash_length);
463  return self::secureCompare($hash, self::pwHash($password, $alg, $salt));
464  }
465  } else {
466  return $hash === $password;
467  }
468 
469  throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($alg).'\' is not supported');
470  }
471 }
static pwHash($password, $algorithm, $salt=null)
This function hashes a password with a given algorithm.
Definition: Crypto.php:365
$type
static aesEncrypt($data)
Encrypt data using AES-256-CBC and the system-wide secret salt as key.
Definition: Crypto.php:146
static der2pem($der, $type='CERTIFICATE')
Convert data from DER to PEM encoding.
Definition: Crypto.php:160
hasValue($name)
Check whether a key in the configuration exists or not.
static aesDecrypt($ciphertext)
Decrypt data using AES-256-CBC and the system-wide secret salt as key.
Definition: Crypto.php:81
static getSecretSalt()
Retrieve the secret salt.
Definition: Config.php:49
$keys
$metadata['__DYNAMIC:1__']
static loadPrivateKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='', $full_path=false)
Load a private key from metadata.
Definition: Crypto.php:195
input
Definition: langcheck.php:166
static pem2der($pem)
Convert from PEM to DER encoding.
Definition: Crypto.php:327
static loadPublicKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='')
Get public key or certificate from metadata.
Definition: Crypto.php:265
getArrayizeString($name, $default=self::REQUIRED_OPTION)
This function retrieves a configuration option with a string or an array of strings.
$password
Definition: cron.php:14
getPublicKeys($use=null, $required=false, $prefix='')
Get public key from metadata.
getString($name, $default=self::REQUIRED_OPTION)
This function retrieves a string configuration option.
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
Definition: Config.php:22
$ret
Definition: parser.php:6
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
$key
Definition: croninfo.php:18
$data
Definition: bench.php:6