Completed
Push — development ( c4d33f...5f74fd )
by Nils
09:26
created

AesCtr::encrypt()   D

Complexity

Conditions 13
Paths 273

Size

Total Lines 74
Code Lines 39

Duplication

Lines 74
Ratio 100 %

Importance

Changes 0
Metric Value
cc 13
eloc 39
nc 273
nop 3
dl 74
loc 74
rs 4.0378
c 0
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 View Code Duplication
class AesCtr extends Aes
0 ignored issues
show
Duplication introduced by
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
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
Documentation introduced by
$pwBytes is of type array, but the function expects a object<cipher>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$pwBytes is of type array, but the function expects a object<message>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
\Aes::keyExpansion($pwBytes) is of type array, but the function expects a object<key>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
38
    $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
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
Documentation introduced by
$key is of type array<integer,?>, but the function expects a object<cipher>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
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 --
0 ignored issues
show
Documentation introduced by
$counterBlock is of type array, but the function expects a object<message>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$keySchedule is of type array, but the function expects a object<key>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
Documentation introduced by
$pwBytes is of type array, but the function expects a object<cipher>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$pwBytes is of type array, but the function expects a object<message>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
\Aes::keyExpansion($pwBytes) is of type array, but the function expects a object<key>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
120
    $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
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
Documentation introduced by
$key is of type array<integer,?>, but the function expects a object<cipher>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
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
0 ignored issues
show
Documentation introduced by
$counterBlock is of type array, but the function expects a object<message>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$keySchedule is of type array, but the function expects a object<key>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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;
188
    }
189
190
}
191
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
192