Failed Conditions
Push — v4.0 ( 5df1a0 )
by Florent
02:32
created

AESKW::checkInitialValue()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 34
rs 5.3846
cc 8
eloc 17
nc 7
nop 3
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace AESKW;
13
14
trait AESKW
15
{
16
    /**
17
     * The initial value used to wrap the key and check the integrity when unwrapped.
18
     * The RFC3394 set this value to 0xA6A6A6A6A6A6A6A6
19
     * The RFC5649 set this value to 0xA65959A6XXXXXXXX (The part with XXXXXXXX is the MLI, depends on the padding).
20
     *
21
     * @param string $key             The key
22
     * @param bool   $padding_enabled Enable padding (RFC5649)
23
     *
24
     * @return string
25
     *
26
     * @see https://tools.ietf.org/html/rfc3394#section-2.2.3.1
27
     */
28
    private static function getInitialValue(string &$key, bool $padding_enabled): string
29
    {
30
        if (false === $padding_enabled) {
31
            return hex2bin('A6A6A6A6A6A6A6A6');
32
        }
33
34
        $MLI = mb_strlen($key, '8bit');
35
        $iv = hex2bin('A65959A6').self::toXBits(32, $MLI);
36
37
        $n = intval(ceil($MLI / 8));
38
        $key = str_pad($key, 8 * $n, "\0", STR_PAD_RIGHT);
39
40
        return $iv;
41
    }
42
43
    /**
44
     * @param string $key
45
     * @param bool   $padding_enabled
46
     * @param string $iv
47
     *
48
     * @return bool
49
     */
50
    private static function checkInitialValue(string &$key, bool $padding_enabled, string $iv): bool
51
    {
52
        // RFC3394 compliant
53
        if ($iv === hex2bin('A6A6A6A6A6A6A6A6')) {
54
            return true;
55
        }
56
57
        // The RFC3394 is required but the previous check is not satisfied => invalid
58
        if (false === $padding_enabled) {
59
            return false;
60
        }
61
62
        // The high-order half of the AIV according to the RFC5649
63
        if (hex2bin('A65959A6') !== self::getMSB($iv)) {
64
            return false;
65
        }
66
67
        $n = mb_strlen($key, '8bit') / 8;
68
        $MLI = hexdec(bin2hex(ltrim(self::getLSB($iv), "\0")));
69
70
        if (!(8 * ($n - 1) < $MLI && $MLI <= 8 * $n)) {
71
            return false;
72
        }
73
74
        $b = 8 * $n - $MLI;
75
        for ($i = 0; $i < $b; ++$i) {
76
            if ("\0" !== mb_substr($key, $MLI + $i, 1, '8bit')) {
77
                return false;
78
            }
79
        }
80
        $key = mb_substr($key, 0, $MLI, '8bit');
81
82
        return true;
83
    }
84
85
    /**
86
     * @param string $key             The Key to wrap
87
     * @param bool   $padding_enabled
88
     */
89
    private static function checkKeySize(string $key, bool $padding_enabled)
90
    {
91
        if (empty($key)) {
92
            throw new \InvalidArgumentException('Bad key size');
93
        }
94
        if (false === $padding_enabled && 0 !== mb_strlen($key, '8bit') % 8) {
95
            throw new \InvalidArgumentException('Bad key size');
96
        }
97
    }
98
99
    /**
100
     * @param string $kek             The Key Encryption Key
101
     * @param string $key             The key to wrap
102
     * @param bool   $padding_enabled If false, the key to wrap must be a sequence of one or more 64-bit blocks (RFC3394 compliant), else the key size must be at least one octet (RFC5649 compliant)
103
     *
104
     * @return string The wrapped key
105
     */
106
    public static function wrap(string $kek, string $key, bool $padding_enabled = false): string
107
    {
108
        self::checkKEKSize($kek);
109
        $A = self::getInitialValue($key, $padding_enabled);
110
        self::checkKeySize($key, $padding_enabled);
111
        $P = str_split($key, 8);
112
        $N = count($P);
113
        $C = [];
114
115
        if (1 === $N) {
116
            $B = self::encrypt($kek, $A.$P[0]);
117
            $C[0] = self::getMSB($B);
118
            $C[1] = self::getLSB($B);
119
        } elseif (1 < $N) {
120
            $R = $P;
121
            for ($j = 0; $j <= 5; ++$j) {
122
                for ($i = 1; $i <= $N; ++$i) {
123
                    $B = self::encrypt($kek, $A.$R[$i - 1]);
124
                    $t = $i + $j * $N;
125
                    $A = self::toXBits(64, $t) ^ self::getMSB($B);
126
                    $R[$i - 1] = self::getLSB($B);
127
                }
128
            }
129
            $C = array_merge([$A], $R);
130
        }
131
132
        return implode('', $C);
133
    }
134
135
    /**
136
     * @param string $kek             The Key Encryption Key
137
     * @param string $key             The key to unwrap
138
     * @param bool   $padding_enabled If false, the AIV check must be RFC3394 compliant, else it must be RFC5649 or RFC3394 compliant
139
     *
140
     * @return string The key unwrapped
141
     */
142
    public static function unwrap(string $kek, string $key, bool $padding_enabled = false): string
143
    {
144
        self::checkKEKSize($kek);
145
        $P = str_split($key, 8);
146
        $A = $P[0];
147
        $N = count($P);
148
149
        if (2 > $N) {
150
            throw new \InvalidArgumentException('Bad data');
151
        } elseif (2 === $N) {
152
            $B = self::decrypt($kek, $P[0].$P[1]);
153
            $unwrapped = self::getLSB($B);
154
            $A = self::getMSB($B);
155
        } else {
156
            $R = $P;
157
            for ($j = 5; $j >= 0; --$j) {
158
                for ($i = $N - 1; $i >= 1; --$i) {
159
                    $t = $i + $j * ($N - 1);
160
                    $B = self::decrypt($kek, (self::toXBits(64, $t) ^ $A).$R[$i]);
161
                    $A = self::getMSB($B);
162
                    $R[$i] = self::getLSB($B);
163
                }
164
            }
165
            unset($R[0]);
166
167
            $unwrapped = implode('', $R);
168
        }
169
        if (false === self::checkInitialValue($unwrapped, $padding_enabled, $A)) {
170
            throw new \InvalidArgumentException('Integrity check failed!');
171
        }
172
173
        return $unwrapped;
174
    }
175
176
    /**
177
     * @return int
178
     */
179
    abstract protected static function getExpectedKEKSize(): int;
180
181
    /**
182
     * @param int $bits
183
     * @param int $value
184
     *
185
     * @return string
186
     */
187
    private static function toXBits(int $bits, int $value): string
188
    {
189
        return hex2bin(str_pad(dechex($value), $bits / 4, '0', STR_PAD_LEFT));
190
    }
191
192
    /**
193
     * @param string $value
194
     *
195
     * @return string
196
     */
197
    private static function getMSB(string $value): string
198
    {
199
        return mb_substr($value, 0, mb_strlen($value, '8bit') / 2, '8bit');
200
    }
201
202
    /**
203
     * @param string $value
204
     *
205
     * @return string
206
     */
207
    private static function getLSB(string $value): string
208
    {
209
        return mb_substr($value, mb_strlen($value, '8bit') / 2, null, '8bit');
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    private static function encrypt(string $kek, string $data): string
216
    {
217
        return openssl_encrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    private static function decrypt(string $kek, string $data): string
224
    {
225
        return openssl_decrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
226
    }
227
228
    /**
229
     * @param string $kek The Key Encryption Key
230
     */
231
    private static function checkKEKSize(string $kek)
232
    {
233
        if (mb_strlen($kek, '8bit') !== self::getExpectedKEKSize()) {
234
            throw new \InvalidArgumentException('Bad KEK size');
235
        }
236
    }
237
238
    /**
239
     * @param string $kek
240
     *
241
     * @return string
242
     */
243
    private static function getMethod(string $kek): string
244
    {
245
        return sprintf('aes-%d-ecb', mb_strlen($kek, '8bit') * 8);
246
    }
247
}
248