Completed
Push — master ( 8c4990...a1ad7c )
by Michael
02:15 queued 43s
created

OpensslStatic::decrypt()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
c 0
b 0
f 0
rs 9.216
cc 3
nc 4
nop 4
1
<?php
2
3
/**
4
 * OpensslStatic.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
class OpensslStatic
18
{
19
    public static function decrypt(string $data, string $pass, string $cipher, string $algo): string
20
    {
21
        // Calculate the hash checksum size in bytes for the specified algo
22
        $hsz = Str::hashSize($algo);
23
24
        // Find the tag size for this cipher mode
25
        // Unless using GCM/CCM this will be zero
26
        $tsz = self::tagRequired($cipher) ? 4 : 0;
27
28
        // Ask openssl for the IV size needed for specified cipher
29
        $isz = OpensslWrapper::ivsize($cipher);
30
31
        // Find the IV at the beginning of the cypher text
32
        $ivr = Str::substr($data, 0, $isz);
33
34
        // Gather the checksum portion of the ciphertext
35
        $sum = Str::substr($data, $isz, $hsz);
36
37
        // Gather the GCM/CCM authentication tag
38
        $tag = Str::substr($data, $isz + $hsz, $tsz);
39
40
        // Gather the iterations portion of the cipher text as packed/encrytped unsigned long
41
        $itr = Str::substr($data, $isz + $hsz + $tsz, 4);
42
43
        // Gather message portion of ciphertext after iv and checksum
44
        $msg = Str::substr($data, $isz + $hsz + $tsz + 4);
45
46
        // Calculate verification checksum
47
        $chk = \hash_hmac($algo, ($msg . $itr . $ivr), $pass, true);
48
49
        // Verify HMAC before decrypting
50
        if (!Str::equal($chk, $sum)) {
51
            throw new \InvalidArgumentException('Decryption can not proceed due to invalid cyphertext checksum.');
52
        }
53
54
        // Decrypt and unpack the cost parameter to match what was used during encryption
55
        $cost = \unpack('N', $itr ^ \hash_hmac($algo, $ivr, $pass, true))[1];
56
57
        // Derive key from password
58
        $key = \hash_pbkdf2($algo, ($pass . $cipher), $ivr, $cost, 0, true);
59
60
        // Decrypt message and return
61
        return OpensslWrapper::decrypt($msg, $cipher, $key, $ivr, $tag);
62
    }
63
64
    public static function encrypt(string $data, string $pass, string $cipher, string $algo, int $cost = 1): string
65
    {
66
        // Generate IV of appropriate size.
67
        $ivr = \random_bytes(OpensslWrapper::ivsize($cipher));
68
69
        // Derive key from password with hash_pbkdf2 function.
70
        // Append CIPHER to password beforehand so that cross-method decryptions will fail at checksum step
71
        $key = \hash_pbkdf2($algo, ($pass . $cipher), $ivr, $cost, 0, true);
72
73
        // Create a placeholder for the authentication tag to be passed by reference
74
        $tag = '';
75
76
        // Encrypt the plaintext data
77
        $msg = OpensslWrapper::encrypt($data, $cipher, $key, $ivr, $tag);
78
79
        // Convert cost integer into 4 byte string and XOR it with a newly derived key
80
        $itr = \pack('N', $cost) ^ \hash_hmac($algo, $ivr, $pass, true);
81
82
        // Generate the ciphertext checksum to prevent bit tampering
83
        $chk = \hash_hmac($algo, ($msg . $itr . $ivr), $pass, true);
84
85
        // Return iv + checksum + iterations + cyphertext + tag
86
        return $ivr . $chk . $tag . $itr . $msg;
87
    }
88
89
    /**
90
     * Determines if the provided cipher requires a tag
91
     *
92
     * @param string $cipher
93
     * @return bool
94
     */
95
    public static function tagRequired(string $cipher): bool
96
    {
97
        $cipher = strtolower($cipher);
98
99
        $needle_tips = [
100
            '-gcm',
101
            '-ccm',
102
        ];
103
104
        foreach ($needle_tips as $needle) {
105
            if (strpos($cipher, $needle)) {
106
                return true;
107
            }
108
        }
109
110
        return false;
111
    }
112
}