Completed
Push — master ( d044a7...7ba68a )
by Florent
03:57
created

AESKW::getEncryptor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
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
use Assert\Assertion;
15
16
trait AESKW
17
{
18
    /**
19
     * The initial value used to wrap the key and check the integrity when unwrapped.
20
     * The RFC3394 set this value to 0xA6A6A6A6A6A6A6A6
21
     * The RFC5649 set this value to 0xA65959A6XXXXXXXX (The part with XXXXXXXX is the MLI, depends on the padding).
22
     *
23
     * @param string $key             The key
24
     * @param bool   $padding_enabled Enable padding (RFC5649)
25
     *
26
     * @return string
27
     *
28
     * @see https://tools.ietf.org/html/rfc3394#section-2.2.3.1
29
     */
30
    private static function getInitialValue(&$key, $padding_enabled)
31
    {
32
        if (false === $padding_enabled) {
33
            return hex2bin('A6A6A6A6A6A6A6A6');
34
        }
35
36
        $MLI = strlen($key);
37
        $iv = hex2bin('A65959A6').self::toXBits(32, $MLI);
38
39
        $n = intval(ceil($MLI / 8));
40
        $key = str_pad($key, 8 * $n, "\0", STR_PAD_RIGHT);
41
42
        return $iv;
43
    }
44
45
    /**
46
     * @param string $key
47
     * @param bool   $padding_enabled
48
     * @param string $iv
49
     *
50
     * @return bool
51
     */
52
    private static function checkInitialValue(&$key, $padding_enabled, $iv)
53
    {
54
        // RFC3394 compliant
55
        if ($iv === hex2bin('A6A6A6A6A6A6A6A6')) {
56
            return true;
57
        }
58
59
        // The RFC3394 is required but the previous check is not satisfied => invalid
60
        if (false === $padding_enabled) {
61
            return false;
62
        }
63
64
        // The high-order half of the AIV according to the RFC5649
65
        if (hex2bin('A65959A6') !== self::getMSB($iv)) {
66
            return false;
67
        }
68
69
        $n = strlen($key) / 8;
70
        $MLI = hexdec(bin2hex(ltrim(self::getLSB($iv), "\0")));
71
72
        if (!(8 * ($n - 1) < $MLI && $MLI <= 8 * $n)) {
73
            return false;
74
        }
75
76
        $b = 8 * $n - $MLI;
77
        for ($i = 0; $i < $b; ++$i) {
78
            if ("\0" !== substr($key, $MLI + $i, 1)) {
79
                return false;
80
            }
81
        }
82
        $key = substr($key, 0, $MLI);
83
84
        return true;
85
    }
86
87
    /**
88
     * @param string $key             The Key to wrap
89
     * @param bool   $padding_enabled
90
     */
91
    private static function checkKeySize($key, $padding_enabled)
92
    {
93
        Assertion::false(false === $padding_enabled && 0 !== strlen($key) % 8, 'Bad key size');
94
        Assertion::greaterOrEqualThan(strlen($key), 1, 'Bad key size');
95
    }
96
97
    /**
98
     * @param string $kek             The Key Encryption Key
99
     * @param string $key             The key to wrap
100
     * @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)
101
     *
102
     * @return string The wrapped key
103
     */
104
    public static function wrap($kek, $key, $padding_enabled = false)
105
    {
106
        self::checkKEKSize($kek);
107
        $A = self::getInitialValue($key, $padding_enabled);
108
        self::checkKeySize($key, $padding_enabled);
109
        $P = str_split($key, 8);
110
        $N = count($P);
111
        $C = [];
112
113
        $encryptor = self::getEncryptor($kek);
114
        if (1 === $N) {
115
            //$B = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $kek, $A.$P[0], MCRYPT_MODE_ECB);
116
            $B = $encryptor->encrypt($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 = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $kek, $A.$R[$i - 1], MCRYPT_MODE_ECB);
124
                    $B = $encryptor->encrypt($A.$R[$i - 1]);
125
                    $t = $i + $j * $N;
126
                    $A = self::toXBits(64, $t) ^ self::getMSB($B);
127
                    $R[$i - 1] = self::getLSB($B);
128
                }
129
            }
130
            $C = array_merge([$A], $R);
131
        }
132
133
        return implode('', $C);
134
    }
135
136
    /**
137
     * @param string $kek             The Key Encryption Key
138
     * @param string $key             The key to unwrap
139
     * @param bool   $padding_enabled If false, the AIV check must be RFC3394 compliant, else it must be RFC5649 or RFC3394 compliant
140
     *
141
     * @return string The key unwrapped
142
     */
143
    public static function unwrap($kek, $key, $padding_enabled = false)
144
    {
145
        self::checkKEKSize($kek);
146
        $P = str_split($key, 8);
147
        $A = $P[0];
148
        $N = count($P);
149
150
        Assertion::greaterThan($N, 1, 'Bad data');
151
        $encryptor = self::getEncryptor($kek);
152
153
        if (2 === $N) {
154
            //$B = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $kek, $P[0].$P[1], MCRYPT_MODE_ECB);
155
            $B = $encryptor->decrypt($P[0].$P[1]);
156
            $unwrapped = self::getLSB($B);
157
            $A = self::getMSB($B);
158
        } else {
159
            $R = $P;
160
            for ($j = 5; $j >= 0; --$j) {
161
                for ($i = $N - 1; $i >= 1; --$i) {
162
                    $t = $i + $j * ($N - 1);
163
                    //$B = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $kek, (self::toXBits(64, $t) ^ $A).$R[$i], MCRYPT_MODE_ECB);
164
                    $B = $encryptor->decrypt((self::toXBits(64, $t) ^ $A).$R[$i]);
165
                    $A = self::getMSB($B);
166
                    $R[$i] = self::getLSB($B);
167
                }
168
            }
169
            unset($R[0]);
170
171
            $unwrapped = implode('', $R);
172
        }
173
        Assertion::true(self::checkInitialValue($unwrapped, $padding_enabled, $A), 'Integrity check failed');
174
175
        return $unwrapped;
176
    }
177
178
    /**
179
     * @param int $bits
180
     * @param int $value
181
     *
182
     * @return string
183
     */
184
    private static function toXBits($bits, $value)
185
    {
186
        return hex2bin(str_pad(dechex($value), $bits / 4, '0', STR_PAD_LEFT));
187
    }
188
189
    /**
190
     * @param string $value
191
     *
192
     * @return string
193
     */
194
    private static function getMSB($value)
195
    {
196
        return substr($value, 0, strlen($value) / 2);
197
    }
198
199
    /**
200
     * @param string $value
201
     *
202
     * @return string
203
     */
204
    private static function getLSB($value)
205
    {
206
        return substr($value, strlen($value) / 2);
207
    }
208
209
    /**
210
     * @param string $kek
211
     *
212
     * @return \AESKW\EncryptorInterface
213
     */
214
    private static function getEncryptor($kek)
215
    {
216
        if (extension_loaded('openssl')) {
217
            return new OpenSSLEncryptor($kek);
218
        } elseif (extension_loaded('mcrypt')) {
219
            return new MCryptEncryptor($kek);
220
        }
221
222
        throw new \RuntimeException('Please install OpenSSL or MCrypt extension.');
223
    }
224
}
225