| @@ 8-162 (lines=155) @@ | ||
| 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 |
|
| 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)); |
|
| 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); |
|
| 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)); |
|
| 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); |
|
| 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); |
|
| @@ 226-405 (lines=180) @@ | ||
| 223 | ||
| 224 | } |
|
| 225 | ||
| 226 | class aesctr extends Aes |
|
| 227 | { |
|
| 228 | ||
| 229 | /** |
|
| 230 | * Encrypt a text using AES encryption in Counter mode of operation |
|
| 231 | * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf |
|
| 232 | * |
|
| 233 | * Unicode multi-byte character safe |
|
| 234 | * |
|
| 235 | * @param plaintext string text to be encrypted |
|
| 236 | * @param password the password to use to generate a key |
|
| 237 | * @param nBits integer of bits to be used in the key (128, 192, or 256) |
|
| 238 | * @return string text |
|
| 239 | */ |
|
| 240 | public static function encrypt($plaintext, $password, $nBits) |
|
| 241 | { |
|
| 242 | $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES |
|
| 243 | if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { |
|
| 244 | return ''; // standard allows 128/192/256 bit keys |
|
| 245 | } |
|
| 246 | // note PHP (5) gives us plaintext and password in UTF8 encoding! |
|
| 247 | ||
| 248 | // use AES itself to encrypt password to get cipher key (using plain password as source for |
|
| 249 | // key expansion) - gives us well encrypted key |
|
| 250 | $nBytes = $nBits / 8; // no bytes in key |
|
| 251 | $pwBytes = array(); |
|
| 252 | for ($i = 0; $i < $nBytes; $i++) { |
|
| 253 | $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; |
|
| 254 | } |
|
| 255 | $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes)); |
|
| 256 | $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long |
|
| 257 | ||
| 258 | // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in |
|
| 259 | // 1st 8 bytes, block counter in 2nd 8 bytes |
|
| 260 | $counterBlock = array(); |
|
| 261 | $nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970 |
|
| 262 | $nonceSec = floor($nonce / 1000); |
|
| 263 | $nonceMs = $nonce % 1000; |
|
| 264 | // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes |
|
| 265 | for ($i = 0; $i < 4; $i++) { |
|
| 266 | $counterBlock[$i] = self::urs($nonceSec, $i * 8) & 0xff; |
|
| 267 | } |
|
| 268 | for ($i = 0; $i < 4; $i++) { |
|
| 269 | $counterBlock[$i + 4] = $nonceMs & 0xff; |
|
| 270 | } |
|
| 271 | // and convert it to a string to go on the front of the ciphertext |
|
| 272 | $ctrTxt = ''; |
|
| 273 | for ($i = 0; $i < 8; $i++) { |
|
| 274 | $ctrTxt .= chr($counterBlock[$i]); |
|
| 275 | } |
|
| 276 | ||
| 277 | // generate key schedule - an expansion of the key into distinct Key Rounds for each round |
|
| 278 | $keySchedule = Aes::keyExpansion($key); |
|
| 279 | ||
| 280 | $blockCount = ceil(strlen($plaintext) / $blockSize); |
|
| 281 | $ciphertxt = array(); // ciphertext as array of strings |
|
| 282 | ||
| 283 | for ($b = 0; $b < $blockCount; $b++) { |
|
| 284 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) |
|
| 285 | // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) |
|
| 286 | for ($c = 0; $c < 4; $c++) { |
|
| 287 | $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; |
|
| 288 | } |
|
| 289 | for ($c = 0; $c < 4; $c++) { |
|
| 290 | $counterBlock[15 - $c - 4] = self::urs($b / 0x100000000, $c * 8); |
|
| 291 | } |
|
| 292 | ||
| 293 | $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // -- encrypt counter block -- |
|
| 294 | ||
| 295 | // block size is reduced on final block |
|
| 296 | $blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1; |
|
| 297 | $cipherByte = array(); |
|
| 298 | ||
| 299 | for ($i = 0; $i < $blockLength; $i++) { // -- xor plaintext with ciphered counter byte-by-byte -- |
|
| 300 | $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1)); |
|
| 301 | $cipherByte[$i] = chr($cipherByte[$i]); |
|
| 302 | } |
|
| 303 | $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext |
|
| 304 | } |
|
| 305 | ||
| 306 | // implode is more efficient than repeated string concatenation |
|
| 307 | $ciphertext = $ctrTxt.implode('', $ciphertxt); |
|
| 308 | $ciphertext = base64_encode($ciphertext); |
|
| 309 | ||
| 310 | return $ciphertext; |
|
| 311 | } |
|
| 312 | ||
| 313 | /** |
|
| 314 | * Decrypt a text encrypted by AES in counter mode of operation |
|
| 315 | * |
|
| 316 | * @param ciphertext source text to be decrypted |
|
| 317 | * @param password the password to use to generate a key |
|
| 318 | * @param nBits integer of bits to be used in the key (128, 192, or 256) |
|
| 319 | * @return string text |
|
| 320 | */ |
|
| 321 | public static function decrypt($ciphertext, $password, $nBits) |
|
| 322 | { |
|
| 323 | $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES |
|
| 324 | if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { |
|
| 325 | return ''; // standard allows 128/192/256 bit keys |
|
| 326 | } |
|
| 327 | $ciphertext = base64_decode($ciphertext); |
|
| 328 | ||
| 329 | // use AES to encrypt password (mirroring encrypt routine) |
|
| 330 | $nBytes = $nBits / 8; // no bytes in key |
|
| 331 | $pwBytes = array(); |
|
| 332 | for ($i = 0; $i < $nBytes; $i++) { |
|
| 333 | $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; |
|
| 334 | } |
|
| 335 | $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes)); |
|
| 336 | $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long |
|
| 337 | ||
| 338 | // recover nonce from 1st element of ciphertext |
|
| 339 | $counterBlock = array(); |
|
| 340 | $ctrTxt = substr($ciphertext, 0, 8); |
|
| 341 | for ($i = 0; $i < 8; $i++) { |
|
| 342 | $counterBlock[$i] = ord(substr($ctrTxt, $i, 1)); |
|
| 343 | } |
|
| 344 | ||
| 345 | // generate key schedule |
|
| 346 | $keySchedule = Aes::keyExpansion($key); |
|
| 347 | ||
| 348 | // separate ciphertext into blocks (skipping past initial 8 bytes) |
|
| 349 | $nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize); |
|
| 350 | $ct = array(); |
|
| 351 | for ($b = 0; $b < $nBlocks; $b++) { |
|
| 352 | $ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16); |
|
| 353 | } |
|
| 354 | $ciphertext = $ct; // ciphertext is now array of block-length strings |
|
| 355 | ||
| 356 | // plaintext will get generated block-by-block into array of block-length strings |
|
| 357 | $plaintxt = array(); |
|
| 358 | ||
| 359 | for ($b = 0; $b < $nBlocks; $b++) { |
|
| 360 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) |
|
| 361 | for ($c = 0; $c < 4; $c++) { |
|
| 362 | $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; |
|
| 363 | } |
|
| 364 | for ($c = 0; $c < 4; $c++) { |
|
| 365 | $counterBlock[15 - $c - 4] = self::urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff; |
|
| 366 | } |
|
| 367 | ||
| 368 | $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // encrypt counter block |
|
| 369 | ||
| 370 | $plaintxtByte = array(); |
|
| 371 | for ($i = 0; $i < strlen($ciphertext[$b]); $i++) { |
|
| 372 | // -- xor plaintext with ciphered counter byte-by-byte -- |
|
| 373 | $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1)); |
|
| 374 | $plaintxtByte[$i] = chr($plaintxtByte[$i]); |
|
| 375 | } |
|
| 376 | $plaintxt[$b] = implode('', $plaintxtByte); |
|
| 377 | } |
|
| 378 | ||
| 379 | // join array of blocks into single plaintext string |
|
| 380 | $plaintext = implode('', $plaintxt); |
|
| 381 | ||
| 382 | return $plaintext; |
|
| 383 | } |
|
| 384 | ||
| 385 | /* |
|
| 386 | * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints |
|
| 387 | * |
|
| 388 | * @param a number to be shifted (32-bit integer) |
|
| 389 | * @param b number of bits to shift a to the right (0..31) |
|
| 390 | * @return a right-shifted and zero-filled by b bits |
|
| 391 | */ |
|
| 392 | private static function urs($a, $b) |
|
| 393 | { |
|
| 394 | $a &= 0xffffffff; |
|
| 395 | $b &= 0x1f; // (bounds check) |
|
| 396 | if ($a & 0x80000000 && $b > 0) { // if left-most bit set |
|
| 397 | $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit |
|
| 398 | $a = $a >> ($b - 1); // remaining right-shifts |
|
| 399 | } else { // otherwise |
|
| 400 | $a = ($a >> $b); // use normal right-shift |
|
| 401 | } |
|
| 402 | ||
| 403 | return $a; |
|
| 404 | } |
|
| 405 | } |
|
| 406 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ |
|
| 407 | ||
| @@ 226-405 (lines=180) @@ | ||
| 223 | ||
| 224 | } |
|
| 225 | ||
| 226 | class aesctr extends Aes |
|
| 227 | { |
|
| 228 | ||
| 229 | /** |
|
| 230 | * Encrypt a text using AES encryption in Counter mode of operation |
|
| 231 | * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf |
|
| 232 | * |
|
| 233 | * Unicode multi-byte character safe |
|
| 234 | * |
|
| 235 | * @param plaintext string text to be encrypted |
|
| 236 | * @param password the password to use to generate a key |
|
| 237 | * @param nBits integer of bits to be used in the key (128, 192, or 256) |
|
| 238 | * @return string text |
|
| 239 | */ |
|
| 240 | public static function encrypt($plaintext, $password, $nBits) |
|
| 241 | { |
|
| 242 | $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES |
|
| 243 | if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { |
|
| 244 | return ''; // standard allows 128/192/256 bit keys |
|
| 245 | } |
|
| 246 | // note PHP (5) gives us plaintext and password in UTF8 encoding! |
|
| 247 | ||
| 248 | // use AES itself to encrypt password to get cipher key (using plain password as source for |
|
| 249 | // key expansion) - gives us well encrypted key |
|
| 250 | $nBytes = $nBits / 8; // no bytes in key |
|
| 251 | $pwBytes = array(); |
|
| 252 | for ($i = 0; $i < $nBytes; $i++) { |
|
| 253 | $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; |
|
| 254 | } |
|
| 255 | $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes)); |
|
| 256 | $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long |
|
| 257 | ||
| 258 | // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in |
|
| 259 | // 1st 8 bytes, block counter in 2nd 8 bytes |
|
| 260 | $counterBlock = array(); |
|
| 261 | $nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970 |
|
| 262 | $nonceSec = floor($nonce / 1000); |
|
| 263 | $nonceMs = $nonce % 1000; |
|
| 264 | // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes |
|
| 265 | for ($i = 0; $i < 4; $i++) { |
|
| 266 | $counterBlock[$i] = self::urs($nonceSec, $i * 8) & 0xff; |
|
| 267 | } |
|
| 268 | for ($i = 0; $i < 4; $i++) { |
|
| 269 | $counterBlock[$i + 4] = $nonceMs & 0xff; |
|
| 270 | } |
|
| 271 | // and convert it to a string to go on the front of the ciphertext |
|
| 272 | $ctrTxt = ''; |
|
| 273 | for ($i = 0; $i < 8; $i++) { |
|
| 274 | $ctrTxt .= chr($counterBlock[$i]); |
|
| 275 | } |
|
| 276 | ||
| 277 | // generate key schedule - an expansion of the key into distinct Key Rounds for each round |
|
| 278 | $keySchedule = Aes::keyExpansion($key); |
|
| 279 | ||
| 280 | $blockCount = ceil(strlen($plaintext) / $blockSize); |
|
| 281 | $ciphertxt = array(); // ciphertext as array of strings |
|
| 282 | ||
| 283 | for ($b = 0; $b < $blockCount; $b++) { |
|
| 284 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) |
|
| 285 | // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) |
|
| 286 | for ($c = 0; $c < 4; $c++) { |
|
| 287 | $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; |
|
| 288 | } |
|
| 289 | for ($c = 0; $c < 4; $c++) { |
|
| 290 | $counterBlock[15 - $c - 4] = self::urs($b / 0x100000000, $c * 8); |
|
| 291 | } |
|
| 292 | ||
| 293 | $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // -- encrypt counter block -- |
|
| 294 | ||
| 295 | // block size is reduced on final block |
|
| 296 | $blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1; |
|
| 297 | $cipherByte = array(); |
|
| 298 | ||
| 299 | for ($i = 0; $i < $blockLength; $i++) { // -- xor plaintext with ciphered counter byte-by-byte -- |
|
| 300 | $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1)); |
|
| 301 | $cipherByte[$i] = chr($cipherByte[$i]); |
|
| 302 | } |
|
| 303 | $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext |
|
| 304 | } |
|
| 305 | ||
| 306 | // implode is more efficient than repeated string concatenation |
|
| 307 | $ciphertext = $ctrTxt.implode('', $ciphertxt); |
|
| 308 | $ciphertext = base64_encode($ciphertext); |
|
| 309 | ||
| 310 | return $ciphertext; |
|
| 311 | } |
|
| 312 | ||
| 313 | /** |
|
| 314 | * Decrypt a text encrypted by AES in counter mode of operation |
|
| 315 | * |
|
| 316 | * @param ciphertext source text to be decrypted |
|
| 317 | * @param password the password to use to generate a key |
|
| 318 | * @param nBits integer of bits to be used in the key (128, 192, or 256) |
|
| 319 | * @return string text |
|
| 320 | */ |
|
| 321 | public static function decrypt($ciphertext, $password, $nBits) |
|
| 322 | { |
|
| 323 | $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES |
|
| 324 | if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { |
|
| 325 | return ''; // standard allows 128/192/256 bit keys |
|
| 326 | } |
|
| 327 | $ciphertext = base64_decode($ciphertext); |
|
| 328 | ||
| 329 | // use AES to encrypt password (mirroring encrypt routine) |
|
| 330 | $nBytes = $nBits / 8; // no bytes in key |
|
| 331 | $pwBytes = array(); |
|
| 332 | for ($i = 0; $i < $nBytes; $i++) { |
|
| 333 | $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; |
|
| 334 | } |
|
| 335 | $key = Aes::cipher($pwBytes, Aes::keyExpansion($pwBytes)); |
|
| 336 | $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long |
|
| 337 | ||
| 338 | // recover nonce from 1st element of ciphertext |
|
| 339 | $counterBlock = array(); |
|
| 340 | $ctrTxt = substr($ciphertext, 0, 8); |
|
| 341 | for ($i = 0; $i < 8; $i++) { |
|
| 342 | $counterBlock[$i] = ord(substr($ctrTxt, $i, 1)); |
|
| 343 | } |
|
| 344 | ||
| 345 | // generate key schedule |
|
| 346 | $keySchedule = Aes::keyExpansion($key); |
|
| 347 | ||
| 348 | // separate ciphertext into blocks (skipping past initial 8 bytes) |
|
| 349 | $nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize); |
|
| 350 | $ct = array(); |
|
| 351 | for ($b = 0; $b < $nBlocks; $b++) { |
|
| 352 | $ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16); |
|
| 353 | } |
|
| 354 | $ciphertext = $ct; // ciphertext is now array of block-length strings |
|
| 355 | ||
| 356 | // plaintext will get generated block-by-block into array of block-length strings |
|
| 357 | $plaintxt = array(); |
|
| 358 | ||
| 359 | for ($b = 0; $b < $nBlocks; $b++) { |
|
| 360 | // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) |
|
| 361 | for ($c = 0; $c < 4; $c++) { |
|
| 362 | $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; |
|
| 363 | } |
|
| 364 | for ($c = 0; $c < 4; $c++) { |
|
| 365 | $counterBlock[15 - $c - 4] = self::urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff; |
|
| 366 | } |
|
| 367 | ||
| 368 | $cipherCntr = Aes::cipher($counterBlock, $keySchedule); // encrypt counter block |
|
| 369 | ||
| 370 | $plaintxtByte = array(); |
|
| 371 | for ($i = 0; $i < strlen($ciphertext[$b]); $i++) { |
|
| 372 | // -- xor plaintext with ciphered counter byte-by-byte -- |
|
| 373 | $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1)); |
|
| 374 | $plaintxtByte[$i] = chr($plaintxtByte[$i]); |
|
| 375 | } |
|
| 376 | $plaintxt[$b] = implode('', $plaintxtByte); |
|
| 377 | } |
|
| 378 | ||
| 379 | // join array of blocks into single plaintext string |
|
| 380 | $plaintext = implode('', $plaintxt); |
|
| 381 | ||
| 382 | return $plaintext; |
|
| 383 | } |
|
| 384 | ||
| 385 | /* |
|
| 386 | * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints |
|
| 387 | * |
|
| 388 | * @param a number to be shifted (32-bit integer) |
|
| 389 | * @param b number of bits to shift a to the right (0..31) |
|
| 390 | * @return a right-shifted and zero-filled by b bits |
|
| 391 | */ |
|
| 392 | private static function urs($a, $b) |
|
| 393 | { |
|
| 394 | $a &= 0xffffffff; |
|
| 395 | $b &= 0x1f; // (bounds check) |
|
| 396 | if ($a & 0x80000000 && $b > 0) { // if left-most bit set |
|
| 397 | $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit |
|
| 398 | $a = $a >> ($b - 1); // remaining right-shifts |
|
| 399 | } else { // otherwise |
|
| 400 | $a = ($a >> $b); // use normal right-shift |
|
| 401 | } |
|
| 402 | ||
| 403 | return $a; |
|
| 404 | } |
|
| 405 | } |
|
| 406 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ |
|
| 407 | ||