Failed Conditions
Push — v7 ( 64e5e5...d305ca )
by Florent
01:51
created

AESCBCHS::checkKeyLength()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Encryption\Algorithm\ContentEncryption;
15
16
use Jose\Component\Encryption\Algorithm\ContentEncryptionAlgorithmInterface;
17
18
abstract class AESCBCHS implements ContentEncryptionAlgorithmInterface
19
{
20
    /**
21
     * {@inheritdoc}
22
     */
23
    public function encryptContent(string $data, string $cek, string $iv, ?string $aad, string $encoded_protected_header, ?string &$tag): string
24
    {
25
        $keyLength = mb_strlen($cek, '8bit');
26
        $this->checkKeyLength($keyLength);
27
        $k = mb_substr($cek, $keyLength / 2, null, '8bit');
28
29
        $cyphertext = openssl_encrypt($data, $this->getMode($keyLength), $k, OPENSSL_RAW_DATA, $iv);
30
        if (false === $cyphertext) {
31
            throw new \RuntimeException('Unable to encrypt.');
32
        }
33
34
        $tag = $this->calculateAuthenticationTag($cyphertext, $cek, $iv, $aad, $encoded_protected_header);
35
36
        return $cyphertext;
37
    }
38
39
    /**
40
     * @param string      $data
41
     * @param string      $cek
42
     * @param string      $iv
43
     * @param string      $aad
44
     * @param string      $encoded_protected_header
45
     * @param string|null $aad
46
     * @param string      $tag
47
     *
48
     * @return string
49
     */
50
    public function decryptContent(string $data, string $cek, string $iv, ?string $aad, string $encoded_protected_header, string $tag): string
51
    {
52
        $keyLength = mb_strlen($cek, '8bit');
53
        $this->checkKeyLength($keyLength);
54
55
        if (!$this->isTagValid($data, $cek, $iv, $aad, $encoded_protected_header, $tag)) {
56
            throw new \InvalidArgumentException('Unable to verify the tag.');
57
        }
58
        $k = mb_substr($cek, $keyLength / 2, null, '8bit');
59
60
        $plaintext = openssl_decrypt($data, self::getMode($keyLength), $k, OPENSSL_RAW_DATA, $iv);
61
        if (false === $plaintext) {
62
            throw new \RuntimeException('Unable to decrypt.');
63
        }
64
65
        return $plaintext;
66
    }
67
68
    /**
69
     * @param string      $encrypted_data
70
     * @param string      $cek
71
     * @param string      $iv
72
     * @param null|string $aad
73
     * @param string      $encoded_header
74
     *
75
     * @return string
76
     */
77
    protected function calculateAuthenticationTag(string $encrypted_data, string $cek, string $iv, ?string $aad, string $encoded_header): string
78
    {
79
        $calculated_aad = $encoded_header;
80
        if (null !== $aad) {
81
            $calculated_aad .= '.'.$aad;
82
        }
83
        $mac_key = mb_substr($cek, 0, mb_strlen($cek, '8bit') / 2, '8bit');
84
        $auth_data_length = mb_strlen($encoded_header, '8bit');
85
86
        $secured_input = implode('', [
87
            $calculated_aad,
88
            $iv,
89
            $encrypted_data,
90
            pack('N2', ($auth_data_length / 2147483647) * 8, ($auth_data_length % 2147483647) * 8), // str_pad(dechex($auth_data_length), 4, "0", STR_PAD_LEFT)
91
        ]);
92
        $hash = hash_hmac($this->getHashAlgorithm(), $secured_input, $mac_key, true);
93
94
        return  mb_substr($hash, 0, mb_strlen($hash, '8bit') / 2, '8bit');
95
    }
96
97
    /**
98
     * @param string      $authentication_tag
99
     * @param string      $encoded_header
100
     * @param string      $encrypted_data
101
     * @param string      $cek
102
     * @param string      $iv
103
     * @param string|null $aad
104
     *
105
     * @return bool
106
     */
107
    protected function isTagValid(string $encrypted_data, string $cek, string $iv, ?string $aad, string $encoded_header, string $authentication_tag): bool
108
    {
109
        return $authentication_tag === $this->calculateAuthenticationTag($encrypted_data, $cek, $iv, $aad, $encoded_header);
110
    }
111
112
    /**
113
     * @return string
114
     */
115
    abstract protected function getHashAlgorithm(): string;
116
117
    /**
118
     * @return int
119
     */
120
    public function getIVSize(): int
121
    {
122
        return 128;
123
    }
124
125
    /**
126
     * @param int $keyLength
127
     *
128
     * @return string
129
     */
130
    private function getMode(int $keyLength): string
131
    {
132
        return sprintf('aes-%d-cbc', 8 * $keyLength/2);
133
    }
134
135
    /**
136
     * @param int $keyLength
137
     */
138
    private function checkKeyLength(int $keyLength)
139
    {
140
        if (!in_array($keyLength, [32, 48, 64])) {
141
            throw new \InvalidArgumentException('Invalid key length. Allowed sizes are 256, 384 and 512 bits.');
142
        }
143
    }
144
}
145