Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
8 / 16
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MWCryptHash
50.00% covered (danger)
50.00%
8 / 16
50.00% covered (danger)
50.00%
2 / 4
19.12
0.00% covered (danger)
0.00%
0 / 1
 hashAlgo
30.00% covered (danger)
30.00%
3 / 10
0.00% covered (danger)
0.00%
0 / 1
9.49
 hashLength
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hash
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hmac
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7/**
8 * Utility functions for generating hashes
9 *
10 * This is based in part on Drupal code as well as what we used in our own code
11 * prior to introduction of this class, by way of MWCryptRand.
12 */
13class MWCryptHash {
14    /**
15     * The hash algorithm being used
16     */
17    protected static ?string $algo = null;
18
19    /**
20     * The number of bytes outputted by the hash algorithm
21     */
22    protected static int $hashLength;
23
24    /**
25     * Decide on the best acceptable hash algorithm we have available for hash()
26     * @return string A hash algorithm
27     */
28    public static function hashAlgo() {
29        $algorithm = self::$algo;
30        if ( $algorithm !== null ) {
31            return $algorithm;
32        }
33
34        $algos = hash_hmac_algos();
35        $preference = [ 'whirlpool', 'sha256' ];
36
37        foreach ( $preference as $algorithm ) {
38            if ( in_array( $algorithm, $algos, true ) ) {
39                self::$algo = $algorithm;
40                return $algorithm;
41            }
42        }
43
44        throw new DomainException( 'Could not find an acceptable hashing function.' );
45    }
46
47    /**
48     * Return the byte-length output of the hash algorithm we are
49     * using in self::hash and self::hmac.
50     *
51     * @param bool $raw True to return the length for binary data, false to
52     *   return for hex-encoded
53     * @return int Number of bytes the hash outputs
54     */
55    public static function hashLength( $raw = true ) {
56        self::$hashLength ??= strlen( self::hash( '', true ) );
57        // Optimisation: Skip computing the length of non-raw hashes.
58        // The algos in hashAlgo() all produce a digest that is a multiple
59        // of 8 bits, where hex is always twice the length of binary byte length.
60        return $raw ? self::$hashLength : self::$hashLength * 2;
61    }
62
63    /**
64     * Generate a cryptographic hash value (message digest) for a string,
65     * making use of the best hash algorithm that we have available.
66     *
67     * @param string $data
68     * @param bool $raw True to return binary data, false to return it hex-encoded
69     * @return string A hash of the data
70     */
71    public static function hash( $data, $raw = true ) {
72        return hash( self::hashAlgo(), $data, $raw );
73    }
74
75    /**
76     * Generate a keyed cryptographic hash value (HMAC) for a string,
77     * making use of the best hash algorithm that we have available.
78     *
79     * @param string $data
80     * @param string $key
81     * @param bool $raw True to return binary data, false to return it hex-encoded
82     * @return string An HMAC hash of the data + key
83     */
84    public static function hmac( $data, $key, $raw = true ) {
85        if ( !is_string( $key ) ) {
86            // hash_hmac tolerates non-string (would return null with warning)
87            throw new InvalidArgumentException( 'Invalid key type: ' . get_debug_type( $key ) );
88        }
89        return hash_hmac( self::hashAlgo(), $data, $key, $raw );
90    }
91
92}