Completed
Push — master ( e4d297...14927f )
by Tony Karavasilev (Тони
16:15
created

AbstractHardwareResistantDerivation::__debugInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * Abstraction for the strong/slow digestion algorithm objects that are resistant to hardware computational attacks.
5
 */
6
7
namespace CryptoManana\Core\Abstractions\MessageDigestion;
8
9
use \CryptoManana\Core\Abstractions\MessageDigestion\AbstractPasswordBasedDerivationFunction as PasswordDerivation;
10
use \CryptoManana\Core\StringBuilder as StringBuilder;
11
12
/**
13
 * Class AbstractHardwareResistantDerivation - The hardware resistant digestion algorithm abstraction representation.
14
 *
15
 * @package CryptoManana\Core\Abstractions\MessageDigestion
16
 */
17
abstract class AbstractHardwareResistantDerivation extends PasswordDerivation
18
{
19
    /**
20
     * The internal maximum length in bytes of the raw output digest for the algorithm.
21
     */
22
    const ALGORITHM_MAXIMUM_OUTPUT = PHP_INT_MAX;
23
24
    /**
25
     * The digest output format property storage.
26
     *
27
     * @var int The output format integer code value.
28
     */
29
    protected $digestFormat = self::DIGEST_OUTPUT_RAW;
30
31
    /**
32
     * Internal method for converting the digest's output format representation via the chosen format.
33
     *
34
     * @param string $digest The output digest.
35
     *
36
     * @return string The input data with proper salting.
37
     */
38 32
    protected function changeOutputFormat($digest)
39
    {
40 32
        switch ($this->digestFormat) {
41 32
            case self::DIGEST_OUTPUT_HEX_LOWER:
42 8
                $digest = bin2hex($digest);
43 8
                break;
44 28
            case self::DIGEST_OUTPUT_HEX_UPPER:
45 4
                $digest = StringBuilder::stringToUpper(bin2hex($digest));
46 4
                break;
47 28
            case self::DIGEST_OUTPUT_BASE_64:
48 4
                $digest = base64_encode($digest);
49 4
                break;
50 28
            case self::DIGEST_OUTPUT_BASE_64_URL:
51 4
                $digest = base64_encode($digest);
52 4
                $digest = StringBuilder::stringReplace(['+', '/', '='], ['-', '_', ''], $digest);
53 4
                break;
54
            default: // case self::DIGEST_OUTPUT_RAW:
55 28
                break;
56
        }
57
58 32
        return $digest;
59
    }
60
61
    /**
62
     * Internal method for converting a formatted digest to raw bytes.
63
     *
64
     * @param string $digest The digest string.
65
     *
66
     * @return string The raw bytes digest representation.
67
     */
68 12
    protected function convertFormattedDigest($digest)
69
    {
70 12
        $hexCasePattern = '/^[a-f0-9]+$/';
71 12
        $base64Pattern = '%^[a-zA-Z0-9/+]*={0,2}$%';
72 12
        $base64UrlFriendlyPattern = '/^[a-zA-Z0-9_-]+$/';
73
74 12
        if (preg_match($hexCasePattern, StringBuilder::stringToLower($digest))) {
75 4
            $digest = hex2bin(StringBuilder::stringToLower($digest));
76 12
        } elseif (preg_match($base64Pattern, $digest) && StringBuilder::stringLength($digest) % 4 === 0) {
77 4
            $digest = base64_decode($digest);
78 12
        } elseif (preg_match($base64UrlFriendlyPattern, $digest)) {
79 4
            $digest = StringBuilder::stringReplace(['-', '_'], ['+', '/'], $digest);
80 4
            $times = StringBuilder::stringLength($digest) % 4;
81
82
            // Instead of str_pad for encoding friendly appending
83 4
            for ($i = 0; $i < $times; $i++) {
84 4
                $digest .= '=';
85
            }
86
87 4
            $digest = base64_decode($digest);
88
        }
89
90 12
        return $digest;
91
    }
92
93
    /**
94
     * Fetch the correctly formatted internal variation for digestion.
95
     *
96
     * @return int|string The chosen variation for password hashing.
97
     */
98
    abstract protected function fetchAlgorithmVariation();
99
100
    /**
101
     * Fetch the correctly formatted internal parameters for digestion.
102
     *
103
     * @return array The chosen parameters for password hashing.
104
     */
105
    abstract protected function fetchAlgorithmParameters();
106
107
    /**
108
     * Password-based key derivation algorithm constructor.
109
     */
110 70
    public function __construct()
111
    {
112 70
        parent::__construct();
113 70
    }
114
115
    /**
116
     * Calculates a hash value for the given data.
117
     *
118
     * @param string $data The input string.
119
     *
120
     * @return string The digest.
121
     * @throws \Exception Validation errors.
122
     */
123 36
    public function hashData($data)
124
    {
125 36
        if (!is_string($data)) {
126 4
            throw new \InvalidArgumentException('The data for hashing must be a string or a binary string.');
127
        }
128
129 32
        $data = $this->addSaltString($data);
130
131 32
        $digest = password_hash($data, $this->fetchAlgorithmVariation(), $this->fetchAlgorithmParameters());
1 ignored issue
show
Bug introduced by
It seems like $this->fetchAlgorithmVariation() can also be of type string; however, parameter $algo of password_hash() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
        $digest = password_hash($data, /** @scrutinizer ignore-type */ $this->fetchAlgorithmVariation(), $this->fetchAlgorithmParameters());
Loading history...
132
133 32
        $digest = $this->changeOutputFormat($digest);
134
135 32
        return $digest;
136
    }
137
138
    /**
139
     * Securely compares and verifies if a digestion value is for the given input data.
140
     *
141
     * @param string $data The input string.
142
     * @param string $digest The digest string.
143
     *
144
     * @return bool The result of the secure comparison.
145
     * @throws \Exception Validation errors.
146
     */
147 20
    public function verifyHash($data, $digest)
148
    {
149 20
        if (!is_string($data)) {
150 4
            throw new \InvalidArgumentException('The data for hashing must be a string or a binary string.');
151 16
        } elseif (!is_string($digest)) {
1 ignored issue
show
introduced by
The condition is_string($digest) is always true.
Loading history...
152 4
            throw new \InvalidArgumentException('The digest must be a string or a binary string.');
153
        }
154
155 12
        $data = $this->addSaltString($data);
156
157 12
        $digest = $this->convertFormattedDigest($digest);
158
159 12
        return password_verify($data, $digest);
160
    }
161
162
    /**
163
     * Get debug information for the class instance.
164
     *
165
     * @return array Debug information.
166
     */
167 4
    public function __debugInfo()
168
    {
169
        return [
170 4
            'standard' => static::ALGORITHM_NAME,
171 4
            'type' => 'key stretching or password-based key derivation',
172 4
            'salt' => $this->salt,
173 4
            'mode' => $this->saltingMode,
174 4
            'algorithm variation version' => $this->fetchAlgorithmVariation(),
175 4
            'digestion parameters' => $this->fetchAlgorithmParameters(),
176
        ];
177
    }
178
}
179