Passed
Branch wip_sessions (2e0cc8)
by Nils
04:59
created

AesCtr::encrypt()   D

Complexity

Conditions 13
Paths 273

Size

Total Lines 73
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 38
nc 273
nop 3
dl 0
loc 73
rs 4.8708
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
3
/*  AES counter (CTR) mode implementation in PHP (c) Chris Veness 2005-2010. Right of free use is */
4
/*    granted for all commercial or non-commercial use under CC-BY licence. No warranty of any    */
5
/*    form is offered.                                                                            */
6
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
7
8
class AesCtr extends Aes
9
{
10
    /**
11
     * Encrypt a text using AES encryption in Counter mode of operation
12
     *  - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
13
     *
14
     * Unicode multi-byte character safe
15
     *
16
     * @param plaintext source text to be encrypted
0 ignored issues
show
Bug introduced by
The type source was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
     * @param password  the password to use to generate a key
18
     * @param nBits     number of bits to be used in the key (128, 192, or 256)
19
     * @return          string text
20
     */
21
    public static function encrypt($plaintext, $password, $nBits)
22
    {
23
    $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
24
    if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) {
25
        return '';
26
    }
27
    // standard allows 128/192/256 bit keys
28
    // note PHP (5) gives us plaintext and password in UTF8 encoding!
29
30
    // use AES itself to encrypt password to get cipher key (using plain password as source for
31
    // key expansion) - gives us well encrypted key
32
    $nBytes = $nBits / 8; // no bytes in key
33
    $pwBytes = array();
34
    for ($i = 0; $i < $nBytes; $i++) {
35
        $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
36
    }
37
    $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes));
0 ignored issues
show
Bug introduced by
$pwBytes of type array is incompatible with the type cipher expected by parameter $key of Aes::keyExpansion(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

37
    $key = Aes::cipher($pwBytes, Aes::keyExpansion(/** @scrutinizer ignore-type */ $pwBytes));
Loading history...
Bug introduced by
$pwBytes of type array is incompatible with the type message expected by parameter $input of Aes::cipher(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

37
    $key = Aes::cipher(/** @scrutinizer ignore-type */ $pwBytes, Aes::keyExpansion($pwBytes));
Loading history...
38
    $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
0 ignored issues
show
Bug introduced by
$key of type ciphertext is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

38
    $key = array_merge(/** @scrutinizer ignore-type */ $key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
Bug introduced by
$key of type ciphertext is incompatible with the type array expected by parameter $array of array_slice(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

38
    $key = array_merge($key, array_slice(/** @scrutinizer ignore-type */ $key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
Bug introduced by
$nBytes - 16 of type double is incompatible with the type integer|null expected by parameter $length of array_slice(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

38
    $key = array_merge($key, array_slice($key, 0, /** @scrutinizer ignore-type */ $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
39
40
    // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in
41
    // 1st 8 bytes, block counter in 2nd 8 bytes
42
    $counterBlock = array();
43
    $nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970
44
    $nonceSec = floor($nonce / 1000);
45
    $nonceMs = $nonce % 1000;
46
    // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
47
    for ($i = 0; $i < 4; $i++) {
48
        $counterBlock[$i] = self::urs($nonceSec, $i * 8) & 0xff;
49
    }
50
    for ($i = 0; $i < 4; $i++) {
51
        $counterBlock[$i + 4] = $nonceMs & 0xff;
52
    }
53
    // and convert it to a string to go on the front of the ciphertext
54
    $ctrTxt = '';
55
    for ($i = 0; $i < 8; $i++) {
56
        $ctrTxt .= chr($counterBlock[$i]);
57
    }
58
59
    // generate key schedule - an expansion of the key into distinct Key Rounds for each round
60
    $keySchedule = Aes::keyExpansion($key);
0 ignored issues
show
Bug introduced by
$key of type array is incompatible with the type cipher expected by parameter $key of Aes::keyExpansion(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

60
    $keySchedule = Aes::keyExpansion(/** @scrutinizer ignore-type */ $key);
Loading history...
61
    //print_r($keySchedule);
62
63
    $blockCount = ceil(strlen($plaintext) / $blockSize);
64
    $ciphertxt = array(); // ciphertext as array of strings
65
66
    for ($b = 0; $b < $blockCount; $b++) {
67
        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
68
        // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
69
        for ($c = 0; $c < 4; $c++) {
70
            $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff;
71
        }
72
        for ($c = 0; $c < 4; $c++) {
73
            $counterBlock[15 - $c - 4] = self::urs($b / 0x100000000, $c * 8);
74
        }
75
76
        $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // -- encrypt counter block --
77
78
        // block size is reduced on final block
79
        $blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1;
80
        $cipherByte = array();
81
82
        for ($i = 0; $i < $blockLength; $i++) {  // -- xor plaintext with ciphered counter byte-by-byte --
83
        $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1));
84
        $cipherByte[$i] = chr($cipherByte[$i]);
85
        }
86
        $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext
87
    }
88
89
    // implode is more efficient than repeated string concatenation
90
    $ciphertext = $ctrTxt.implode('', $ciphertxt);
91
    $ciphertext = base64_encode($ciphertext);
92
93
    return $ciphertext;
94
    }
95
96
    /**
97
     * Decrypt a text encrypted by AES in counter mode of operation
98
     *
99
     * @param ciphertext source text to be decrypted
100
     * @param password   the password to use to generate a key
101
     * @param nBits      number of bits to be used in the key (128, 192, or 256)
102
     * @return           string text
103
     */
104
    public static function decrypt($ciphertext, $password, $nBits)
105
    {
106
    $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
107
    if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) {
108
        return '';
109
    }
110
    // standard allows 128/192/256 bit keys
111
    $ciphertext = base64_decode($ciphertext);
112
113
    // use AES to encrypt password (mirroring encrypt routine)
114
    $nBytes = $nBits / 8; // no bytes in key
115
    $pwBytes = array();
116
    for ($i = 0; $i < $nBytes; $i++) {
117
        $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
118
    }
119
    $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes));
0 ignored issues
show
Bug introduced by
$pwBytes of type array is incompatible with the type cipher expected by parameter $key of Aes::keyExpansion(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
    $key = Aes::cipher($pwBytes, Aes::keyExpansion(/** @scrutinizer ignore-type */ $pwBytes));
Loading history...
Bug introduced by
$pwBytes of type array is incompatible with the type message expected by parameter $input of Aes::cipher(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
    $key = Aes::cipher(/** @scrutinizer ignore-type */ $pwBytes, Aes::keyExpansion($pwBytes));
Loading history...
120
    $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
0 ignored issues
show
Bug introduced by
$nBytes - 16 of type double is incompatible with the type integer|null expected by parameter $length of array_slice(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
    $key = array_merge($key, array_slice($key, 0, /** @scrutinizer ignore-type */ $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
Bug introduced by
$key of type ciphertext is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
    $key = array_merge(/** @scrutinizer ignore-type */ $key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
Bug introduced by
$key of type ciphertext is incompatible with the type array expected by parameter $array of array_slice(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
    $key = array_merge($key, array_slice(/** @scrutinizer ignore-type */ $key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
Loading history...
121
122
    // recover nonce from 1st element of ciphertext
123
    $counterBlock = array();
124
    $ctrTxt = substr($ciphertext, 0, 8);
125
    for ($i = 0; $i < 8; $i++) {
126
        $counterBlock[$i] = ord(substr($ctrTxt, $i, 1));
127
    }
128
129
    // generate key schedule
130
    $keySchedule = Aes::keyExpansion($key);
0 ignored issues
show
Bug introduced by
$key of type array is incompatible with the type cipher expected by parameter $key of Aes::keyExpansion(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
    $keySchedule = Aes::keyExpansion(/** @scrutinizer ignore-type */ $key);
Loading history...
131
132
    // separate ciphertext into blocks (skipping past initial 8 bytes)
133
    $nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize);
134
    $ct = array();
135
    for ($b = 0; $b < $nBlocks; $b++) {
136
        $ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16);
137
    }
138
    $ciphertext = $ct; // ciphertext is now array of block-length strings
139
140
    // plaintext will get generated block-by-block into array of block-length strings
141
    $plaintxt = array();
142
143
    for ($b = 0; $b < $nBlocks; $b++) {
144
        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
145
        for ($c = 0; $c < 4; $c++) {
146
            $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff;
147
        }
148
        for ($c = 0; $c < 4; $c++) {
149
            $counterBlock[15 - $c - 4] = self::urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff;
150
        }
151
152
        $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // encrypt counter block
153
154
        $plaintxtByte = array();
155
        for ($i = 0; $i < strlen($ciphertext[$b]); $i++) {
156
        // -- xor plaintext with ciphered counter byte-by-byte --
157
        $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1));
158
        $plaintxtByte[$i] = chr($plaintxtByte[$i]);
159
160
        }
161
        $plaintxt[$b] = implode('', $plaintxtByte);
162
    }
163
164
    // join array of blocks into single plaintext string
165
    $plaintext = implode('', $plaintxt);
166
167
    return $plaintext;
168
    }
169
170
    /*
171
   * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
172
   *
173
   * @param a  number to be shifted (32-bit integer)
174
   * @param b  number of bits to shift a to the right (0..31)
175
   * @return   a right-shifted and zero-filled by b bits
176
   */
177
    private static function urs($a, $b)
178
    {
179
    $a &= 0xffffffff; $b &= 0x1f; // (bounds check)
180
    if ($a & 0x80000000 && $b > 0) {   // if left-most bit set
181
        $a = ($a >> 1) & 0x7fffffff; //   right-shift one bit & clear left-most bit
182
        $a = $a >> ($b - 1); //   remaining right-shifts
183
    } else {                       // otherwise
184
        $a = ($a >> $b); //   use normal right-shift
185
    }
186
187
    return $a;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $a returns the type integer which is incompatible with the documented return type a.
Loading history...
188
    }
189
190
}
191
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
192