Passed
Push — master ( bdfe7b...8f2aab )
by Alexander
01:12
created

Mac::getMessage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

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