ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
Crypto.php
Go to the documentation of this file.
1<?php
2
3namespace SimpleSAML\Utils;
4
10class 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) {
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 ($keys !== null) {
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}
$metadata['__DYNAMIC:1__']
An exception for terminatinating execution or to throw for unit testing.
static getSecretSalt()
Retrieve the secret salt.
Definition: Config.php:49
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
Definition: Config.php:22
static pwHash($password, $algorithm, $salt=null)
This function hashes a password with a given algorithm.
Definition: Crypto.php:365
static loadPublicKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='')
Get public key or certificate from metadata.
Definition: Crypto.php:265
static der2pem($der, $type='CERTIFICATE')
Convert data from DER to PEM encoding.
Definition: Crypto.php:160
static aesEncrypt($data)
Encrypt data using AES-256-CBC and the system-wide secret salt as key.
Definition: Crypto.php:146
static loadPrivateKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='', $full_path=false)
Load a private key from metadata.
Definition: Crypto.php:195
static aesDecrypt($ciphertext)
Decrypt data using AES-256-CBC and the system-wide secret salt as key.
Definition: Crypto.php:81
static pem2der($pem)
Convert from PEM to DER encoding.
Definition: Crypto.php:327
$key
Definition: croninfo.php:18
$secret
Definition: demo.php:27
$end
Definition: saml1-acs.php:18
$keys
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
$ret
Definition: parser.php:6
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
$type
$password
Definition: pwgen.php:17
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file