Completed
Push — master ( 750179...0c950e )
by Michael
01:29
created

OpensslBridge::key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 3
1
<?php
2
3
/**
4
 * OpensslBridge.php
5
 *
6
 * PHP version 7
7
 *
8
 * @category Dcrypt
9
 * @package  Dcrypt
10
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
11
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
12
 * @link     https://github.com/mmeyer2k/dcrypt
13
 */
14
15
namespace Dcrypt;
16
17
/**
18
 * Provides functionality common to the dcrypt AES block ciphers. Extend this class to customize your cipher suite.
19
 *
20
 * @category Dcrypt
21
 * @package  Dcrypt
22
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
23
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
24
 * @link     https://github.com/mmeyer2k/dcrypt
25
 * @link     https://apigen.ci/github/mmeyer2k/dcrypt/namespace-Dcrypt.html
26
 */
27
class OpensslBridge
28
{
29
    /**
30
     * This string is used when hashing to ensure cross compatibility between
31
     * dcrypt\mcrypt and dcrypt\aes. Since v7, this is only needed for backwards
32
     * compatibility with older versions
33
     */
34
    const RIJNDA = 'rijndael-128';
35
36
    /**
37
     * Decrypt cyphertext
38
     *
39
     * @param string $data Cyphertext to decrypt
40
     * @param string $pass Password that should be used to decrypt input data
41
     * @param int    $cost Number of extra HMAC iterations to perform on key
42
     * @return string
43
     */
44
    public static function decrypt(string $data, string $pass, int $cost = 0): string
45
    {
46
        // Find the IV at the beginning of the cypher text
47
        $ivr = Str::substr($data, 0, self::ivsize());
48
49
        // Gather the checksum portion of the ciphertext
50
        $sum = Str::substr($data, self::ivsize(), self::cksize());
51
52
        // Gather message portion of ciphertext after iv and checksum
53
        $msg = Str::substr($data, self::ivsize() + self::cksize());
54
55
        // Derive key from password
56
        $key = self::key($pass, $ivr, $cost);
57
58
        // Calculate verification checksum
59
        $chk = self::checksum($msg, $ivr, $key);
60
61
        // Verify HMAC before decrypting
62
        self::checksumVerify($chk, $sum);
63
64
        // Decrypt message and return
65
        return OpensslWrapper::decrypt($msg, static::CIPHER, $key, $ivr);
66
    }
67
68
    /**
69
     * Encrypt plaintext
70
     *
71
     * @param string $data Plaintext string to encrypt.
72
     * @param string $pass Password used to encrypt data.
73
     * @param int    $cost Number of extra HMAC iterations to perform on key
74
     * @return string
75
     */
76
    public static function encrypt(string $data, string $pass, int $cost = 0): string
77
    {
78
        // Generate IV of appropriate size.
79
        $ivr = \random_bytes(self::ivsize());
80
81
        // Derive key from password
82
        $key = self::key($pass, $ivr, $cost);
83
84
        // Encrypt the plaintext
85
        $msg = OpensslWrapper::encrypt($data, static::CIPHER, $key, $ivr);
86
87
        // Create the cypher text prefix (iv + checksum)
88
        $pre = $ivr . self::checksum($msg, $ivr, $key);
89
90
        // Return prefix + cyphertext
91
        return $pre . $msg;
92
    }
93
94
    /**
95
     * Create a message authentication checksum.
96
     *
97
     * @param string $data Ciphertext that needs a checksum.
98
     * @param string $iv   Initialization vector.
99
     * @param string $key  HMAC key
100
     * @return string
101
     */
102
    private static function checksum(string $data, string $iv, string $key): string
103
    {
104
        // Prevent multiple potentially large string concats by hmac-ing the input data
105
        // by itself first...
106
        $sum = Hash::hmac($data, $key, static::CHKSUM);
107
108
        // Then add the other input elements together before performing the final hash
109
        $sum = $sum . $iv . self::mode() . self::RIJNDA;
110
111
        // ... then hash other elements with previous hmac and return
112
        return Hash::hmac($sum, $key, static::CHKSUM);
113
    }
114
115
    /**
116
     * Transform password into key and perform iterative HMAC (if specified)
117
     *
118
     * @param string $pass Encryption key
119
     * @param string $iv   Initialization vector
120
     * @param int    $cost Number of HMAC iterations to perform on key
121
     * @return string
122
     */
123
    private static function key(string $pass, string $iv, int $cost): string
124
    {
125
        $data = $iv . self::RIJNDA . self::mode();
126
127
        return Hash::ihmac($data, $pass, $cost, static::CHKSUM);
128
    }
129
130
    /**
131
     * Verify checksum during decryption step and throw error if mismatching.
132
     *
133
     * @param string $calculated
134
     * @param string $supplied
135
     * @throws \InvalidArgumentException
136
     */
137
    private static function checksumVerify(string $calculated, string $supplied)
138
    {
139
        if (!Str::equal($calculated, $supplied)) {
140
            $e = 'Decryption can not proceed due to invalid cyphertext checksum.';
141
            throw new \InvalidArgumentException($e);
142
        }
143
    }
144
145
    /**
146
     * Return the encryption mode string. This function is really only needed for backwards
147
     * compatibility.
148
     *
149
     * @return string
150
     */
151
    private static function mode(): string
152
    {
153
        // To prevent legacy blobs from not decoding, these ciphers (which were implemented before 8.3) have hard coded
154
        // return values. Luckily, this integrates gracefully with overloading.
155
        $legacy = [
156
            'bf-ofb' => 'ofb',
157
            'aes-256-cbc' => 'cbc',
158
            'aes-256-ctr' => 'ctr',
159
        ];
160
161
        $cipher = \strtolower(static::CIPHER);
162
163
        if (isset($legacy[$cipher])) {
164
            return $legacy[$cipher];
165
        }
166
167
        return $cipher;
168
    }
169
170
    /**
171
     * Calculate checksum size
172
     *
173
     * @return int
174
     */
175
    private static function cksize(): int
176
    {
177
        return Str::hashSize(static::CHKSUM);
178
    }
179
180
    /**
181
     * Get iv size
182
     *
183
     * @return int
184
     */
185
    private static function ivsize(): int
186
    {
187
        return \openssl_cipher_iv_length(static::CIPHER);
188
    }
189
}
190