Completed
Push — master ( 275c5d...098f29 )
by Florent
01:56
created

AESKW::getLSB()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2015 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 14
    private static function getInitialValue(&$key, $padding_enabled)
29
    {
30 14
        if (false === $padding_enabled) {
31 11
            return hex2bin('A6A6A6A6A6A6A6A6');
32
        }
33
34 3
        $MLI = strlen($key);
35 3
        $iv = hex2bin('A65959A6').self::toXBits(32, $MLI);
36
37 3
        $n = intval(ceil($MLI / 8));
38 3
        $key = str_pad($key, 8 * $n, "\0", STR_PAD_RIGHT);
39
40 3
        return $iv;
41
    }
42
43
    /**
44
     * @param string $key
45
     * @param bool   $padding_enabled
46
     * @param string $iv
47
     *
48
     * @return bool
49
     */
50 15
    private static function checkInitialValue(&$key, $padding_enabled, $iv)
51
    {
52
        // RFC3394 compliant
53 15
        if ($iv === hex2bin('A6A6A6A6A6A6A6A6')) {
54 10
            return true;
55
        }
56
57
        // The RFC3394 is required but the previous check is not satisfied => invalid
58 5
        if (false === $padding_enabled) {
59 2
            return false;
60
        }
61
62
        // The high-order half of the AIV according to the RFC5649
63 3
        if (hex2bin('A65959A6') !== self::getMSB($iv)) {
64 1
            return false;
65
        }
66
67 2
        $n = strlen($key) / 8;
68 2
        $MLI = hexdec(bin2hex(ltrim(self::getLSB($iv), "\0")));
69
70 2
        if (!(8 * ($n - 1) < $MLI && $MLI <= 8 * $n)) {
71
            return false;
72
        }
73
74 2
        $b = 8 * $n - $MLI;
75 2
        for ($i = 0; $i < $b; ++$i) {
76 2
            if ("\0" !== substr($key, $MLI + $i, 1)) {
77
                return false;
78
            }
79 2
        }
80 2
        $key = substr($key, 0, $MLI);
81
82 2
        return true;
83
    }
84
85
    /**
86
     * @param string $key             The Key to wrap
87
     * @param bool   $padding_enabled
88
     *
89
     * @throws \InvalidArgumentException If the size of the Key is invalid
90
     */
91 14
    private static function checkKeySize($key, $padding_enabled)
92
    {
93 14
        if (false === $padding_enabled && 0 !== strlen($key) % 8) {
94 1
            throw new \InvalidArgumentException('Bad key size');
95
        }
96 13
        if (1 > strlen($key)) {
97 1
            throw new \InvalidArgumentException('Bad key size');
98
        }
99 12
    }
100
101
    /**
102
     * @param string $kek             The Key Encryption Key
103
     * @param string $key             The key to wrap
104
     * @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)
105
     *
106
     * @return string The wrapped key
107
     */
108 18
    public static function wrap($kek, $key, $padding_enabled = false)
109
    {
110 18
        self::checkKEKSize($kek);
111 14
        $A = self::getInitialValue($key, $padding_enabled);
112 14
        self::checkKeySize($key, $padding_enabled);
113 12
        $P = str_split($key, 8);
114 12
        $N = count($P);
115 12
        $C = [];
116 12
        if (1 === $N) {
117 4
            $B = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $kek, $A.$P[0], MCRYPT_MODE_ECB);
118 4
            $C[0] = self::getMSB($B);
119 4
            $C[1] = self::getLSB($B);
120 12
        } elseif (1 < $N) {
121 8
            $R = $P;
122 8
            for ($j = 0; $j <= 5; ++$j) {
123 8
                for ($i = 1; $i <= $N; ++$i) {
124 8
                    $B = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $kek, $A.$R[$i - 1], MCRYPT_MODE_ECB);
125 8
                    $t = $i + $j * $N;
126 8
                    $A = self::toXBits(64, $t) ^ self::getMSB($B);
127 8
                    $R[$i - 1] = self::getLSB($B);
128 8
                }
129 8
            }
130 8
            $C = array_merge([$A], $R);
131 8
        }
132
133 12
        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
     * @throws \RuntimeException If the wrapped key is not valid
142
     *
143
     * @return string The key unwrapped
144
     */
145 16
    public static function unwrap($kek, $key, $padding_enabled = false)
146
    {
147 16
        self::checkKEKSize($kek);
148 16
        $P = str_split($key, 8);
149 16
        $A = $P[0];
150 16
        $N = count($P);
151
152 16
        if (1 >= $N) {
153 1
            throw new \RuntimeException('Bad data');
154 15
        } elseif (2 === $N) {
155 5
            $B = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $kek, $P[0].$P[1], MCRYPT_MODE_ECB);
156 5
            $unwrapped = self::getLSB($B);
157 5
            $A = self::getMSB($B);
158 5
        } else {
159 10
            $R = $P;
160 10
            for ($j = 5; $j >= 0; --$j) {
161 10
                for ($i = $N - 1; $i >= 1; --$i) {
162 10
                    $t = $i + $j * ($N - 1);
163 10
                    $B = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $kek, (self::toXBits(64, $t) ^ $A).$R[$i], MCRYPT_MODE_ECB);
164 10
                    $A = self::getMSB($B);
165 10
                    $R[$i] = self::getLSB($B);
166 10
                }
167 10
            }
168 10
            unset($R[0]);
169
170 10
            $unwrapped = implode('', $R);
171
        }
172 15
        if (!self::checkInitialValue($unwrapped, $padding_enabled, $A)) {
173 3
            throw new \RuntimeException('Integrity check failed');
174
        }
175
176 12
        return $unwrapped;
177
    }
178
179
    /**
180
     * @param int $bits
181
     * @param int $value
182
     *
183
     * @return string
184
     */
185 12
    private static function toXBits($bits, $value)
186
    {
187 12
        return hex2bin(str_pad(dechex($value), $bits / 4, '0', STR_PAD_LEFT));
188
    }
189
190
    /**
191
     * @param string $value
192
     *
193
     * @return string
194
     */
195 15
    private static function getMSB($value)
196
    {
197 15
        return substr($value, 0, strlen($value) / 2);
198
    }
199
200
    /**
201
     * @param string $value
202
     *
203
     * @return string
204
     */
205 15
    private static function getLSB($value)
206
    {
207 15
        return substr($value, strlen($value) / 2);
208
    }
209
}
210