Mac   A
last analyzed

Complexity

Total Complexity 7

Size/Duplication

Total Lines 88
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 7
eloc 18
c 2
b 0
f 0
dl 0
loc 88
ccs 19
cts 19
cp 1
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A sign() 0 8 2
A __construct() 0 3 1
A getMessage() 0 19 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Security;
6
7
use Yiisoft\Strings\StringHelper;
8
9
/**
10
 * Provides ability to sign a message with a MAC (message authentication). Signed message contains both original
11
 * message and a MAC hash. When obtaining original message from signed message using a key an exception is thrown
12
 * if message was altered.
13
 */
14
final class Mac
15
{
16
    /**
17
     * @var string Hash algorithm for message authentication. Recommend sha256, sha384 or sha512.
18
     *
19
     * @see http://php.net/manual/en/function.hash-algos.php
20
     */
21
    private string $algorithm;
22
23
    /**
24
     * @param string $algorithm Hash algorithm for message authentication. Recommend sha256, sha384 or sha512.
25
     *
26
     * @see http://php.net/manual/en/function.hash-algos.php
27
     */
28 51
    public function __construct(string $algorithm = 'sha256')
29
    {
30 51
        $this->algorithm = $algorithm;
31
    }
32
33
    /**
34
     * Prefixes data with a keyed sign value so that it can later be detected if it is tampered.
35
     *
36
     * There is no need to sign inputs or outputs of {@see Crypt::encryptByKey()} or {@see Crypt::encryptByPassword()}
37
     * as those methods perform the task.
38
     *
39
     * @param string $data The data to be protected.
40
     * @param string $key The secret key to be used for generating sign. Should be a secure
41
     * cryptographic key.
42
     * @param bool $rawHash Whether the generated sign value is in raw binary format. If false, lowercase
43
     * hex digits will be generated.
44
     *
45
     * @throws \RuntimeException When HMAC generation fails.
46
     *
47
     * @return string The data prefixed with the keyed sign.
48
     *
49
     * @see validate()
50
     * @see generateBytes()
51
     * @see hkdf()
52
     * @see pbkdf2()
53
     */
54 14
    public function sign(string $data, string $key, bool $rawHash = false): string
55
    {
56 14
        $hash = hash_hmac($this->algorithm, $data, $key, $rawHash);
57 14
        if (!$hash) {
58 1
            throw new \RuntimeException("Failed to generate HMAC with hash algorithm: {$this->algorithm}.");
59
        }
60
61 13
        return $hash . $data;
62
    }
63
64
    /**
65
     * Get original message from signed message.
66
     *
67
     * @param string $data The data to be validated. The data must be previously
68
     * generated by {@see sign()}.
69
     * @param string $key The secret key that was previously used to generate the sign for the data in {@see sign()}.
70
     * function to see the supported hashing algorithms on your system. This must be the same
71
     * as the value passed to {@see sign()} when generating the hash signature for the data.
72
     * @param bool $rawHash This should take the same value as when you generate the data using {@see sign()}.
73
     * It indicates whether the sign value in the data is in binary format. If false, it means the hash value consists
74
     * of lowercase hex digits only.
75
     *
76
     * @throws \RuntimeException When HMAC generation fails.
77
     * @throws DataIsTamperedException If the given data is tampered.
78
     *
79
     * @return string The real data with the signature stripped off.
80
     *
81
     * @see hash()
82
     */
83 49
    public function getMessage(string $data, string $key, bool $rawHash = false): string
84
    {
85 49
        $test = hash_hmac($this->algorithm, '', '', $rawHash);
86 49
        if (!$test) {
87 1
            throw new \RuntimeException("Failed to generate HMAC with hash algorithm: {$this->algorithm}.");
88
        }
89 48
        $hashLength = StringHelper::byteLength($test);
90 48
        if (StringHelper::byteLength($data) >= $hashLength) {
91 47
            $hash = StringHelper::byteSubstring($data, 0, $hashLength);
92 47
            $pureData = StringHelper::byteSubstring($data, $hashLength, null);
93
94 47
            $calculatedHash = hash_hmac($this->algorithm, $pureData, $key, $rawHash);
95
96 47
            if (hash_equals($hash, $calculatedHash)) {
97 42
                return $pureData;
98
            }
99
        }
100
101 6
        throw new DataIsTamperedException();
102
    }
103
}
104