Browse Source

Merge branch 'php71'

Curtis Farnham 7 years ago
parent
commit
d5e1cf5ec8

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ composer.phar
 composer.lock
 .DS_Store
 /vendor
+/scripts

+ 1 - 1
.travis.yml

@@ -1,8 +1,8 @@
 language: php
 php:
-  - 5.5
   - 5.6
   - 7.0
+  - 7.1
 before_script:
   - composer install
 script: vendor/bin/phpunit

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2013 Rob Napier, Guysung Kim, Curtis Farnham
+Copyright (c) 2013-2017 Rob Napier, Guysung Kim, Curtis Farnham
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 17 - 8
composer.json

@@ -20,17 +20,26 @@
         }
     ],
     "require": {
-        "php": ">=5.5 <7.1",
-        "ext-mcrypt": "*",
-		"sarciszewski/php-future": "0.4.*"
-    },
+		"php": ">=7.0 <7.2",
+		"ext-openssl": "*"
+	},
     "require-dev": {
-		"rncryptor/spec": "3.0.1",
-		"phpunit/phpunit": ">=4.8 <6.0"
+		"rncryptor/spec": "~4.0",
+		"phpunit/phpunit": "~6.0",
+        "squizlabs/php_codesniffer": "^2.8"
     },
     "autoload": {
-        "psr-0": {
-            "RNCryptor": "lib/"
+        "psr-4": {
+            "RNCryptor\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\": "tests/"
         }
+    },
+    "config": {
+        "preferred-install": "dist",
+        "sort-packages": true
     }
 }

+ 4 - 2
examples/decrypt.php

@@ -3,9 +3,11 @@
 require __DIR__ . '/../vendor/autoload.php';
 
 $password = "myPassword";
-$base64Encrypted = "AgGXutvFqW9RqQuokYLjehbfM7F+8OO/2sD8g3auA+oNCQFoarRmc59qcKJve7FHyH9MkyJWZ4Cj6CegDU+UbtpXKR0ND6UlfwaZncRUNkw53jy09cgUkHRJI0gCfOsS4rXmRdiaqUt+ukkkaYfAJJk/o3HBvqK/OI4qttyo+kdiLbiAop5QQwWReG2LMQ08v9TAiiOQgFWhd1dc+qFEN7Cv";
+$base64Encrypted = "AgGXutvFqW9RqQuokYLjehbfM7F+8OO/2sD8g3auA+oNCQFoarRmc59qcKJve7FHyH9MkyJWZ4Cj6CegDU+UbtpXKR0ND6Ulfwa'
+    . 'ZncRUNkw53jy09cgUkHRJI0gCfOsS4rXmRdiaqUt+ukkkaYfAJJk/o3HBvqK/OI4qttyo+kdiLbiAop5QQwWReG2LMQ08v9TAiiOQgFWhd1dc+qF'
+    . 'EN7Cv";
 
-$cryptor = new \RNCryptor\Decryptor();
+$cryptor = new \RNCryptor\RNCryptor\Decryptor;
 $plaintext = $cryptor->decrypt($base64Encrypted, $password);
 
 echo "Base64 Encrypted:\n$base64Encrypted\n\n";

+ 1 - 1
examples/encrypt.php

@@ -5,7 +5,7 @@ require __DIR__ . '/../vendor/autoload.php';
 $password = "myPassword";
 $plaintext = "Here is my test vector. It's not too long, but more than a block and needs padding.";
 
-$cryptor = new \RNCryptor\Encryptor();
+$cryptor = new \RNCryptor\RNCryptor\Encryptor;
 $base64Encrypted = $cryptor->encrypt($plaintext, $password);
 
 echo "Plaintext:\n$plaintext\n\n";

+ 0 - 62
lib/RNCryptor/Autoloader.php

@@ -1,62 +0,0 @@
-<?php
-
-/*
- * This file is part of the Predis package.
- *
- * (c) Daniele Alessandri <suppakilla@gmail.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace RNCryptor;
-
-/**
- * Implements a lightweight PSR-0 compliant autoloader.
- *
- * @author Eric Naeseth <eric@thumbtack.com>
- * @author Daniele Alessandri <suppakilla@gmail.com>
- */
-class Autoloader
-{
-    private $directory;
-    private $prefix;
-    private $prefixLength;
-
-    /**
-     * @param string $baseDirectory Base directory where the source files are located.
-     */
-    public function __construct($baseDirectory = __DIR__)
-    {
-        $this->directory = $baseDirectory;
-        $this->prefix = __NAMESPACE__ . '\\';
-        $this->prefixLength = strlen($this->prefix);
-    }
-
-    /**
-     * Registers the autoloader class with the PHP SPL autoloader.
-     *
-     * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
-     */
-    public static function register($prepend = false)
-    {
-        spl_autoload_register(array(new self, 'autoload'), true, $prepend);
-    }
-
-    /**
-     * Loads a class from a file using its fully qualified name.
-     *
-     * @param string $className Fully qualified name of a class.
-     */
-    public function autoload($className)
-    {
-        if (0 === strpos($className, $this->prefix)) {
-            $parts = explode('\\', substr($className, $this->prefixLength));
-            $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
-
-            if (is_file($filepath)) {
-                require($filepath);
-            }
-        }
-    }
-}

+ 0 - 150
lib/RNCryptor/Cryptor.php

@@ -1,150 +0,0 @@
-<?php
-namespace RNCryptor;
-
-class Cryptor {
-
-	const DEFAULT_SCHEMA_VERSION = 3;
-
-	protected $_settings;
-
-	public function __construct() {
-		if (!extension_loaded('mcrypt')) {
-			throw new \Exception('The mcrypt extension is missing.');
-		}
-	}
-
-	protected function _configureSettings($version) {
-
-		$settings = new \stdClass();
-
-		$settings->algorithm = MCRYPT_RIJNDAEL_128;
-		$settings->saltLength = 8;
-		$settings->ivLength = 16;
-
-		$settings->pbkdf2 = new \stdClass();
-		$settings->pbkdf2->prf = 'sha1';
-		$settings->pbkdf2->iterations = 10000;
-		$settings->pbkdf2->keyLength = 32;
-		
-		$settings->hmac = new \stdClass();
-		$settings->hmac->length = 32;
-
-		switch ($version) {
-			case 0:
-				$settings->mode = 'ctr';
-				$settings->options = 0;
-				$settings->hmac->includesHeader = false;
-				$settings->hmac->algorithm = 'sha1';
-				$settings->hmac->includesPadding = true;
-				$settings->truncatesMultibytePasswords = true;
-				break;
-
-			case 1:
-				$settings->mode = 'cbc';
-				$settings->options = 1;
-				$settings->hmac->includesHeader = false;
-				$settings->hmac->algorithm = 'sha256';
-				$settings->hmac->includesPadding = false;
-				$settings->truncatesMultibytePasswords = true;
-				break;
-
-			case 2:
-				$settings->mode = 'cbc';
-				$settings->options = 1;
-				$settings->hmac->includesHeader = true;
-				$settings->hmac->algorithm = 'sha256';
-				$settings->hmac->includesPadding = false;
-				$settings->truncatesMultibytePasswords = true;
-				break;
-
-			case 3:
-				$settings->mode = 'cbc';
-				$settings->options = 1;
-				$settings->hmac->includesHeader = true;
-				$settings->hmac->algorithm = 'sha256';
-				$settings->hmac->includesPadding = false;
-				$settings->truncatesMultibytePasswords = false;
-				break;
-
-			default:
-				throw new \Exception('Unsupported schema version ' . $version);
-		}
-
-		$this->_settings = $settings;
-	}
-
-	/**
-	 * Encrypt or decrypt using AES CTR Little Endian mode
-	 */
-	protected function _aesCtrLittleEndianCrypt($payload, $key, $iv) {
-
-		$numOfBlocks = ceil(strlen($payload) / strlen($iv));
-		$counter = '';
-		for ($i = 0; $i < $numOfBlocks; ++$i) {
-			$counter .= $iv;
-
-			// Yes, the next line only ever increments the first character
-			// of the counter string, ignoring overflow conditions.  This
-			// matches CommonCrypto's behavior!
-			$iv[0] = chr(ord($iv[0]) + 1);
-		}
-
-		return $payload ^ mcrypt_encrypt($this->_settings->algorithm, $key, $counter, 'ecb');
-	}
-
-	protected function _generateHmac(\stdClass $components, $hmacKey) {
-	
-		$hmacMessage = '';
-		if ($this->_settings->hmac->includesHeader) {
-			$hmacMessage .= $components->headers->version
-							. $components->headers->options
-							. (isset($components->headers->encSalt) ? $components->headers->encSalt : '')
-							. (isset($components->headers->hmacSalt) ? $components->headers->hmacSalt : '')
-							. $components->headers->iv;
-		}
-
-		$hmacMessage .= $components->ciphertext;
-
-		$hmac = hash_hmac($this->_settings->hmac->algorithm, $hmacMessage, $hmacKey, true);
-
-		if ($this->_settings->hmac->includesPadding) {
-			$hmac = str_pad($hmac, $this->_settings->hmac->length, chr(0));
-		}
-	
-		return $hmac;
-	}
-
-	/**
-	 * Key derivation -- This method is intended for testing.  It merely
-	 * exposes the underlying key-derivation functionality.
-	 */
-	public function generateKey($salt, $password, $version = self::DEFAULT_SCHEMA_VERSION) {
-		$this->_configureSettings($version);
-		return $this->_generateKey($salt, $password);
-	}
-
-	public function generateSalt($version = self::DEFAULT_SCHEMA_VERSION) {
-		$this->_configureSettings($version);
-		return $this->_generateIv($this->_settings->saltLength);
-	}
-
-	protected function _generateKey($salt, $password) {
-
-		if ($this->_settings->truncatesMultibytePasswords) {
-			$utf8Length = mb_strlen($password, 'utf-8');
-			$password = substr($password, 0, $utf8Length);
-		}
-
-		return hash_pbkdf2($this->_settings->pbkdf2->prf, $password, $salt, $this->_settings->pbkdf2->iterations, $this->_settings->pbkdf2->keyLength, true);
-	}
-
-	private function _generateIv($blockSize) {
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-			$randomSource = MCRYPT_RAND;
-		} else {
-			$randomSource = MCRYPT_DEV_URANDOM;
-		}
-		return mcrypt_create_iv($blockSize, $randomSource);
-	}
-
-}

+ 0 - 129
lib/RNCryptor/Decryptor.php

@@ -1,129 +0,0 @@
-<?php
-namespace RNCryptor;
-
-/**
- * RNDecryptor for PHP
- * 
- * Decrypt data interchangeably with Rob Napier's Objective-C implementation
- * of RNCryptor
- */
-class Decryptor extends Cryptor {
-
-	/**
-	 * Decrypt RNCryptor-encrypted data
-	 *
-	 * @param string $base64EncryptedData Encrypted, Base64-encoded text
-	 * @param string $password Password the text was encoded with
-	 * @throws Exception If the detected version is unsupported
-	 * @return string|false Decrypted string, or false if decryption failed
-	 */
-	public function decrypt($encryptedBase64Data, $password) {
-
-		$components = $this->_unpackEncryptedBase64Data($encryptedBase64Data);
-
-		if (!$this->_hmacIsValid($components, $password)) {
-			return false;
-		}
-
-		$key = $this->_generateKey($components->headers->encSalt, $password);
-		$plaintext = null;
-		switch ($this->_settings->mode) {
-			case 'ctr':
-				$plaintext = $this->_aesCtrLittleEndianCrypt($components->ciphertext, $key, $components->headers->iv);
-				break;
-
-			case 'cbc':
-				$paddedPlaintext = mcrypt_decrypt($this->_settings->algorithm, $key, $components->ciphertext, 'cbc', $components->headers->iv);
-				$plaintext = $this->_stripPKCS7Padding($paddedPlaintext);
-				break;
-		}
-
-		return $plaintext;
-	}
-
-	public function decryptWithArbitraryKeys($encryptedBase64Data, $encKey, $hmacKey) {
-		$components = $this->_unpackEncryptedBase64Data($encryptedBase64Data, false);
-		if (!$this->_hmacIsValid($components, $hmacKey, false)) {
-			return false;
-		}
-		$plaintext = null;
-		switch ($this->_settings->mode) {
-			case 'ctr':
-				$plaintext = $this->_aesCtrLittleEndianCrypt($components->ciphertext, $encKey, $components->headers->iv);
-				break;
-			case 'cbc':
-				$paddedPlaintext = mcrypt_decrypt($this->_settings->algorithm, $encKey, $components->ciphertext, 'cbc', $components->headers->iv);
-				$plaintext = $this->_stripPKCS7Padding($paddedPlaintext);
-				break;
-		}
-		return $plaintext;
-	}
-
-	private function _unpackEncryptedBase64Data($encryptedBase64Data, $isPasswordBased = true) {
-
-		$binaryData = base64_decode($encryptedBase64Data);
-
-		$components = new \stdClass();
-		$components->headers = $this->_parseHeaders($binaryData, $isPasswordBased);
-
-		$components->hmac = substr($binaryData, - $this->_settings->hmac->length);
-
-		$headerLength = $components->headers->length;
-
-		$components->ciphertext = substr($binaryData, $headerLength, strlen($binaryData) - $headerLength - strlen($components->hmac));
-
-		return $components;
-	}
-
-	private function _parseHeaders($binData, $isPasswordBased = true) {
-
-		$offset = 0;
-
-		$versionChr = $binData[0];
-		$offset += strlen($versionChr);
-
-		$this->_configureSettings(ord($versionChr));
-
-		$optionsChr = $binData[1];
-		$offset += strlen($optionsChr);
-
-		$encSalt = null;
-		$hmacSalt = null;
-		if($isPasswordBased) {
-			$encSalt = substr($binData, $offset, $this->_settings->saltLength);
-			$offset += strlen($encSalt);
-
-			$hmacSalt = substr($binData, $offset, $this->_settings->saltLength);
-			$offset += strlen($hmacSalt);
-		}
-
-		$iv = substr($binData, $offset, $this->_settings->ivLength);
-		$offset += strlen($iv);
-
-		$headers = (object)array(
-			'version' => $versionChr,
-			'options' => $optionsChr,
-			'encSalt' => $encSalt,
-			'hmacSalt' => $hmacSalt,
-			'iv' => $iv,
-			'length' => $offset
-		);
-
-		return $headers;
-	}
-
-	private function _stripPKCS7Padding($plaintext) {
-		$padLength = ord($plaintext[strlen($plaintext)-1]);
-		return substr($plaintext, 0, strlen($plaintext) - $padLength);
-	}
-
-	private function _hmacIsValid($components, $password, $isPasswordBased = true) {
-		$hmacKey = null;
-		if($isPasswordBased)
-			$hmacKey = $this->_generateKey($components->headers->hmacSalt, $password);
-		else
-			$hmacKey = $password;
-		return hash_equals($components->hmac, $this->_generateHmac($components, $hmacKey));
-	}
-
-}

+ 0 - 116
lib/RNCryptor/Encryptor.php

@@ -1,116 +0,0 @@
-<?php
-namespace RNCryptor;
-
-/**
- * RNEncryptor for PHP
- * 
- * Encrypt data interchangeably with Rob Napier's Objective-C implementation
- * of RNCryptor
- */
-class Encryptor extends Cryptor {
-
-	/**
-	 * Encrypt plaintext using RNCryptor's algorithm
-	 * 
-	 * @param string $plaintext Text to be encrypted
-	 * @param string $password Password to use
-	 * @param int $version (Optional) RNCryptor schema version to use.
-	 * @throws \Exception If the provided version (if any) is unsupported
-	 * @return string Encrypted, Base64-encoded string
-	 */
-	public function encrypt($plaintext, $password, $version = Cryptor::DEFAULT_SCHEMA_VERSION) {
-
-		$this->_configureSettings($version);
-
-		$components = $this->_generateInitializedComponents($version);
-		$components->headers->encSalt = $this->_generateSalt();
-		$components->headers->hmacSalt = $this->_generateSalt();
-		$components->headers->iv = $this->_generateIv($this->_settings->ivLength);
-
-		$encKey = $this->_generateKey($components->headers->encSalt, $password);
-		$hmacKey = $this->_generateKey($components->headers->hmacSalt, $password);
-
-		return $this->_encrypt($plaintext, $components, $encKey, $hmacKey);
-	}
-
-	public function encryptWithArbitrarySalts($plaintext, $password, $encSalt, $hmacSalt, $iv, $version = Cryptor::DEFAULT_SCHEMA_VERSION) {
-	
-		$this->_configureSettings($version);
-
-		$components = $this->_generateInitializedComponents($version);
-		$components->headers->encSalt = $encSalt;
-		$components->headers->hmacSalt = $hmacSalt;
-		$components->headers->iv = $iv;
-
-		$encKey = $this->_generateKey($components->headers->encSalt, $password);
-		$hmacKey = $this->_generateKey($components->headers->hmacSalt, $password);
-
-		return $this->_encrypt($plaintext, $components, $encKey, $hmacKey);
-	}
-
-	public function encryptWithArbitraryKeys($plaintext, $encKey, $hmacKey, $iv, $version = Cryptor::DEFAULT_SCHEMA_VERSION) {
-
-		$this->_configureSettings($version);
-
-		$this->_settings->options = 0;
-
-		$components = $this->_generateInitializedComponents($version);
-		$components->headers->iv = $iv;
-
-		return $this->_encrypt($plaintext, $components, $encKey, $hmacKey);
-	}
-
-	private function _generateInitializedComponents($version) {
-
-		$components = new \stdClass();
-		$components->headers = new \stdClass();
-		$components->headers->version = chr($version);
-		$components->headers->options = chr($this->_settings->options);
-
-		return $components;
-	}
-
-	private function _encrypt($plaintext, \stdClass $components, $encKey, $hmacKey) {
-	
-		switch ($this->_settings->mode) {
-			case 'ctr':
-				$components->ciphertext = $this->_aesCtrLittleEndianCrypt($plaintext, $encKey, $components->headers->iv);
-				break;
-	
-			case 'cbc':
-				$paddedPlaintext = $this->_addPKCS7Padding($plaintext, strlen($components->headers->iv));
-				$components->ciphertext = mcrypt_encrypt($this->_settings->algorithm, $encKey, $paddedPlaintext, 'cbc', $components->headers->iv);
-				break;
-		}
-
-		$binaryData = ''
-				. $components->headers->version
-				. $components->headers->options
-				. (isset($components->headers->encSalt) ? $components->headers->encSalt : '')
-				. (isset($components->headers->hmacSalt) ? $components->headers->hmacSalt : '')
-				. $components->headers->iv
-				. $components->ciphertext;
-	
-		$hmac = $this->_generateHmac($components, $hmacKey);
-	
-		return base64_encode($binaryData . $hmac);
-	}
-
-	private function _addPKCS7Padding($plaintext, $blockSize) {
-		$padSize = $blockSize - (strlen($plaintext) % $blockSize);
-		return $plaintext . str_repeat(chr($padSize), $padSize);
-	}
-
-	private function _generateSalt() {
-		return $this->_generateIv($this->_settings->saltLength);
-	}
-
-	private function _generateIv($blockSize) {
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-			$randomSource = MCRYPT_RAND;
-		} else {
-			$randomSource = MCRYPT_DEV_URANDOM;
-		}
-		return mcrypt_create_iv($blockSize, $randomSource);
-	}
-}

+ 0 - 22
phpunit-coverage-html.xml

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<phpunit bootstrap="tests/bootstrap.php"
-         colors="true" 
->
-    <testsuites>
-        <testsuite name="RNCryptor Test Suite">
-            <directory>tests/RNCryptor/</directory>
-        </testsuite>
-    </testsuites>
-
-    <filter>
-        <whitelist>
-            <directory suffix=".php">lib/RNCryptor/</directory>
-        </whitelist>
-    </filter>
-    
-    <logging>
-        <log type="coverage-html" target="./tests/coverage" charset="UTF-8"
-             yui="true" highlight="false" lowUpperBound="35" highLowerBound="70" />
-    </logging>
-</phpunit>

+ 2 - 2
phpunit.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit bootstrap="tests/bootstrap.php"
+<phpunit bootstrap="vendor/autoload.php"
          colors="true" 
 >
     <testsuites>
@@ -11,7 +11,7 @@
 
     <filter>
         <whitelist>
-            <directory suffix=".php">lib/RNCryptor/</directory>
+            <directory suffix=".php">src/RNCryptor/</directory>
         </whitelist>
     </filter>
 </phpunit>

+ 133 - 0
src/RNCryptor/Cryptor.php

@@ -0,0 +1,133 @@
+<?php
+namespace RNCryptor\RNCryptor;
+
+use stdClass;
+
+class Cryptor
+{
+    const DEFAULT_SCHEMA_VERSION = 3;
+
+    protected $config;
+
+    public function generateKey($salt, $password, $version = self::DEFAULT_SCHEMA_VERSION)
+    {
+        $this->configure($version);
+
+        return $this->makeKey($salt, $password);
+    }
+
+    protected function aesCtrLittleEndianCrypt($payload, $key, $iv)
+    {
+        $numOfBlocks = ceil(strlen($payload) / strlen($iv));
+        $counter = '';
+        for ($i = 0; $i < $numOfBlocks; ++$i) {
+            $counter .= $iv;
+
+            // Yes, the next line only ever increments the first character
+            // of the counter string, ignoring overflow conditions.  This
+            // matches CommonCrypto's behavior!
+            $iv[0] = chr(ord($iv[0]) + 1);
+        }
+
+        return $payload ^ $this->encryptInternal($key, $counter, 'ecb');
+    }
+
+    protected function encryptInternal($key, $payload, $mode, $iv = null)
+    {
+        return openssl_encrypt($payload, $this->config->algorithm . $mode, $key, OPENSSL_RAW_DATA, (string)$iv);
+    }
+
+    protected function makeHmac(stdClass $components, $hmacKey)
+    {
+        $hmacMessage = '';
+        if ($this->config->hmac->includesHeader) {
+            $hmacMessage .= ''
+                . $components->headers->version
+                . $components->headers->options
+                . (isset($components->headers->encSalt) ? $components->headers->encSalt : '')
+                . (isset($components->headers->hmacSalt) ? $components->headers->hmacSalt : '')
+                . $components->headers->iv;
+        }
+
+        $hmacMessage .= $components->ciphertext;
+
+        $hmac = hash_hmac($this->config->hmac->algorithm, $hmacMessage, $hmacKey, true);
+
+        if ($this->config->hmac->includesPadding) {
+            $hmac = str_pad($hmac, $this->config->hmac->length, chr(0));
+        }
+    
+        return $hmac;
+    }
+
+    protected function makeKey($salt, $password)
+    {
+        if ($this->config->truncatesMultibytePasswords) {
+            $utf8Length = mb_strlen($password, 'utf-8');
+            $password = substr($password, 0, $utf8Length);
+        }
+
+        $algo = $this->config->pbkdf2->prf;
+        $iterations = $this->config->pbkdf2->iterations;
+        $length = $this->config->pbkdf2->keyLength;
+
+        return hash_pbkdf2($algo, $password, $salt, $iterations, $length, true);
+    }
+
+    protected function configure($version)
+    {
+        $config = new stdClass;
+
+        $config->algorithm = 'aes-256-';
+        $config->saltLength = 8;
+        $config->ivLength = 16;
+
+        $config->pbkdf2 = new stdClass;
+        $config->pbkdf2->prf = 'sha1';
+        $config->pbkdf2->iterations = 10000;
+        $config->pbkdf2->keyLength = 32;
+
+        $config->hmac = new stdClass();
+        $config->hmac->length = 32;
+
+        if (!$version) {
+            $this->configureVersionZero($config);
+        } elseif ($version <= 3) {
+            $config->mode = 'cbc';
+            $config->options = 1;
+            $config->hmac->algorithm = 'sha256';
+            $config->hmac->includesPadding = false;
+
+            switch ($version) {
+                case 1:
+                    $config->hmac->includesHeader = false;
+                    $config->truncatesMultibytePasswords = true;
+                    break;
+
+                case 2:
+                    $config->hmac->includesHeader = true;
+                    $config->truncatesMultibytePasswords = true;
+                    break;
+
+                case 3:
+                    $config->hmac->includesHeader = true;
+                    $config->truncatesMultibytePasswords = false;
+                    break;
+            }
+        } else {
+            throw new \RuntimeException('Unsupported schema version ' . $version);
+        }
+
+        $this->config = $config;
+    }
+
+    private function configureVersionZero(stdClass $config)
+    {
+        $config->mode = 'ctr';
+        $config->options = 0;
+        $config->hmac->includesHeader = false;
+        $config->hmac->algorithm = 'sha1';
+        $config->hmac->includesPadding = true;
+        $config->truncatesMultibytePasswords = true;
+    }
+}

+ 101 - 0
src/RNCryptor/Decryptor.php

@@ -0,0 +1,101 @@
+<?php
+namespace RNCryptor\RNCryptor;
+
+use stdClass;
+
+/**
+ * RNDecryptor for PHP
+ *
+ * Decrypt data interchangeably with Rob Napier's Objective-C implementation
+ * of RNCryptor
+ */
+class Decryptor extends Cryptor
+{
+    /**
+     * Decrypt RNCryptor-encrypted data
+     *
+     * @param string $base64EncryptedData Encrypted, Base64-encoded text
+     * @param string $password Password the text was encoded with
+     * @throws Exception If the detected version is unsupported
+     * @return string|false Decrypted string, or false if decryption failed
+     */
+    public function decrypt($encryptedBase64Data, $password)
+    {
+        $components = $this->unpackEncryptedBase64Data($encryptedBase64Data);
+
+        if (!$this->hmacIsValid($components, $password)) {
+            return false;
+        }
+
+        $key = $this->makeKey($components->headers->encSalt, $password);
+        if ($this->config->mode == 'ctr') {
+            return $this->aesCtrLittleEndianCrypt($components->ciphertext, $key, $components->headers->iv);
+        }
+
+        $iv = (string)$components->headers->iv;
+        $method = $this->config->algorithm . 'cbc';
+
+        return openssl_decrypt($components->ciphertext, $method, $key, OPENSSL_RAW_DATA, (string)$iv);
+    }
+
+    private function unpackEncryptedBase64Data($encryptedBase64Data, $isPasswordBased = true)
+    {
+        $binaryData = base64_decode($encryptedBase64Data);
+
+        $components = new stdClass;
+        $components->headers = $this->parseHeaders($binaryData, $isPasswordBased);
+
+        $components->hmac = substr($binaryData, -$this->config->hmac->length);
+
+        $offset = $components->headers->length;
+        $length = strlen($binaryData) - $offset - strlen($components->hmac);
+
+        $components->ciphertext = substr($binaryData, $offset, $length);
+
+        return $components;
+    }
+
+    private function parseHeaders($binData, $isPasswordBased = true)
+    {
+        $offset = 0;
+
+        $versionChr = $binData[0];
+        $offset += strlen($versionChr);
+
+        $this->configure(ord($versionChr));
+
+        $optionsChr = $binData[1];
+        $offset += strlen($optionsChr);
+
+        $encSalt = null;
+        $hmacSalt = null;
+        if ($isPasswordBased) {
+            $encSalt = substr($binData, $offset, $this->config->saltLength);
+            $offset += strlen($encSalt);
+
+            $hmacSalt = substr($binData, $offset, $this->config->saltLength);
+            $offset += strlen($hmacSalt);
+        }
+
+        $iv = substr($binData, $offset, $this->config->ivLength);
+        $offset += strlen($iv);
+
+        $headers = (object)[
+            'version' => $versionChr,
+            'options' => $optionsChr,
+            'encSalt' => $encSalt,
+            'hmacSalt' => $hmacSalt,
+            'iv' => $iv,
+            'length' => $offset
+        ];
+
+        return $headers;
+    }
+
+    private function hmacIsValid($components, $password)
+    {
+        $hmacKey = $this->makeKey($components->headers->hmacSalt, $password);
+
+        return hash_equals($components->hmac, $this->makeHmac($components, $hmacKey));
+    }
+}

+ 114 - 0
src/RNCryptor/Encryptor.php

@@ -0,0 +1,114 @@
+<?php
+namespace RNCryptor\RNCryptor;
+
+use stdClass;
+
+/**
+ * RNEncryptor for PHP
+ *
+ * Encrypt data interchangeably with Rob Napier's Objective-C implementation
+ * of RNCryptor
+ */
+class Encryptor extends Cryptor
+{
+    /**
+     * Encrypt plaintext using RNCryptor's algorithm
+     *
+     * @param string $plaintext Text to be encrypted
+     * @param string $password Password to use
+     * @param int $version (Optional) RNCryptor schema version to use.
+     * @throws \Exception If the provided version (if any) is unsupported
+     * @return string Encrypted, Base64-encoded string
+     */
+    public function encrypt($plaintext, $password, $version = Cryptor::DEFAULT_SCHEMA_VERSION)
+    {
+        $this->configure($version);
+
+        $components = $this->makeComponents($version);
+        $components->headers->encSalt = $this->makeSalt();
+        $components->headers->hmacSalt = $this->makeSalt();
+        $components->headers->iv = $this->makeIv($this->config->ivLength);
+
+        $encKey = $this->makeKey($components->headers->encSalt, $password);
+        $hmacKey = $this->makeKey($components->headers->hmacSalt, $password);
+
+        return $this->encryptFromComponents($plaintext, $components, $encKey, $hmacKey);
+    }
+
+    public function encryptWithArbitrarySalts(
+        $plaintext,
+        $password,
+        $encSalt,
+        $hmacSalt,
+        $iv,
+        $version = Cryptor::DEFAULT_SCHEMA_VERSION
+    ) {
+        $this->configure($version);
+
+        $components = $this->makeComponents($version);
+        $components->headers->encSalt = $encSalt;
+        $components->headers->hmacSalt = $hmacSalt;
+        $components->headers->iv = $iv;
+
+        $encKey = $this->makeKey($components->headers->encSalt, $password);
+        $hmacKey = $this->makeKey($components->headers->hmacSalt, $password);
+
+        return $this->encryptFromComponents($plaintext, $components, $encKey, $hmacKey);
+    }
+
+    public function encryptWithArbitraryKeys(
+        $plaintext,
+        $encKey,
+        $hmacKey,
+        $iv,
+        $version = Cryptor::DEFAULT_SCHEMA_VERSION
+    ) {
+        $this->configure($version);
+
+        $this->config->options = 0;
+
+        $components = $this->makeComponents($version);
+        $components->headers->iv = $iv;
+
+        return $this->encryptFromComponents($plaintext, $components, $encKey, $hmacKey);
+    }
+
+    private function makeComponents($version)
+    {
+        $components = new stdClass;
+        $components->headers = new stdClass;
+        $components->headers->version = chr($version);
+        $components->headers->options = chr($this->config->options);
+
+        return $components;
+    }
+
+    private function encryptFromComponents($plaintext, stdClass $components, $encKey, $hmacKey)
+    {
+        $iv = $components->headers->iv;
+        if ($this->config->mode == 'ctr') {
+            $components->ciphertext = $this->aesCtrLittleEndianCrypt($plaintext, $encKey, $iv);
+        } else {
+            $components->ciphertext = $this->encryptInternal($encKey, $plaintext, 'cbc', $iv);
+        }
+
+        return base64_encode(''
+            . $components->headers->version
+            . $components->headers->options
+            . ($components->headers->encSalt ?? '')
+            . ($components->headers->hmacSalt ?? '')
+            . $components->headers->iv
+            . $components->ciphertext
+            . $this->makeHmac($components, $hmacKey));
+    }
+
+    private function makeSalt()
+    {
+        return $this->makeIv($this->config->saltLength);
+    }
+
+    private function makeIv($blockSize)
+    {
+        return openssl_random_pseudo_bytes($blockSize);
+    }
+}

+ 0 - 284
tests-xcode/testphp.xcodeproj/project.pbxproj

@@ -1,284 +0,0 @@
-// !$*UTF8*$!
-{
-	archiveVersion = 1;
-	classes = {
-	};
-	objectVersion = 46;
-	objects = {
-
-/* Begin PBXBuildFile section */
-		FBFBB5CC16B178DD002BA3EA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBFBB5CB16B178DD002BA3EA /* Foundation.framework */; };
-		FBFBB5CF16B178DD002BA3EA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5CE16B178DD002BA3EA /* main.m */; };
-		FBFBB5E216B178FD002BA3EA /* RNCryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5DA16B178FD002BA3EA /* RNCryptor.m */; };
-		FBFBB5E316B178FD002BA3EA /* RNCryptorEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5DD16B178FD002BA3EA /* RNCryptorEngine.m */; };
-		FBFBB5E416B178FD002BA3EA /* RNDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5DF16B178FD002BA3EA /* RNDecryptor.m */; };
-		FBFBB5E516B178FD002BA3EA /* RNEncryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5E116B178FD002BA3EA /* RNEncryptor.m */; };
-		FBFBB5E916B17931002BA3EA /* Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = FBFBB5E816B17931002BA3EA /* Base64.m */; };
-		FBFBB5EB16B179D3002BA3EA /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBFBB5EA16B179D3002BA3EA /* Security.framework */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXCopyFilesBuildPhase section */
-		FBFBB5C516B178DC002BA3EA /* CopyFiles */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = /usr/share/man/man1/;
-			dstSubfolderSpec = 0;
-			files = (
-			);
-			runOnlyForDeploymentPostprocessing = 1;
-		};
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
-		FBFBB5C716B178DD002BA3EA /* testphp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = testphp; sourceTree = BUILT_PRODUCTS_DIR; };
-		FBFBB5CB16B178DD002BA3EA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
-		FBFBB5CE16B178DD002BA3EA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
-		FBFBB5D116B178DD002BA3EA /* testphp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "testphp-Prefix.pch"; sourceTree = "<group>"; };
-		FBFBB5D916B178FD002BA3EA /* RNCryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCryptor.h; path = ../../../RNCryptor/RNCryptor.h; sourceTree = "<group>"; };
-		FBFBB5DA16B178FD002BA3EA /* RNCryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCryptor.m; path = ../../../RNCryptor/RNCryptor.m; sourceTree = "<group>"; };
-		FBFBB5DB16B178FD002BA3EA /* RNCryptor+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RNCryptor+Private.h"; path = "../../../RNCryptor/RNCryptor+Private.h"; sourceTree = "<group>"; };
-		FBFBB5DC16B178FD002BA3EA /* RNCryptorEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCryptorEngine.h; path = ../../../RNCryptor/RNCryptorEngine.h; sourceTree = "<group>"; };
-		FBFBB5DD16B178FD002BA3EA /* RNCryptorEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCryptorEngine.m; path = ../../../RNCryptor/RNCryptorEngine.m; sourceTree = "<group>"; };
-		FBFBB5DE16B178FD002BA3EA /* RNDecryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNDecryptor.h; path = ../../../RNCryptor/RNDecryptor.h; sourceTree = "<group>"; };
-		FBFBB5DF16B178FD002BA3EA /* RNDecryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNDecryptor.m; path = ../../../RNCryptor/RNDecryptor.m; sourceTree = "<group>"; };
-		FBFBB5E016B178FD002BA3EA /* RNEncryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNEncryptor.h; path = ../../../RNCryptor/RNEncryptor.h; sourceTree = "<group>"; };
-		FBFBB5E116B178FD002BA3EA /* RNEncryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNEncryptor.m; path = ../../../RNCryptor/RNEncryptor.m; sourceTree = "<group>"; };
-		FBFBB5E716B17931002BA3EA /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base64.h; sourceTree = "<group>"; };
-		FBFBB5E816B17931002BA3EA /* Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Base64.m; sourceTree = "<group>"; };
-		FBFBB5EA16B179D3002BA3EA /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
-		FBFBB5C416B178DC002BA3EA /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				FBFBB5EB16B179D3002BA3EA /* Security.framework in Frameworks */,
-				FBFBB5CC16B178DD002BA3EA /* Foundation.framework in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
-		FBFBB5BC16B178DC002BA3EA = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5CD16B178DD002BA3EA /* testphp */,
-				FBFBB5CA16B178DD002BA3EA /* Frameworks */,
-				FBFBB5C816B178DD002BA3EA /* Products */,
-			);
-			sourceTree = "<group>";
-		};
-		FBFBB5C816B178DD002BA3EA /* Products */ = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5C716B178DD002BA3EA /* testphp */,
-			);
-			name = Products;
-			sourceTree = "<group>";
-		};
-		FBFBB5CA16B178DD002BA3EA /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5EA16B179D3002BA3EA /* Security.framework */,
-				FBFBB5CB16B178DD002BA3EA /* Foundation.framework */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
-		FBFBB5CD16B178DD002BA3EA /* testphp */ = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5E716B17931002BA3EA /* Base64.h */,
-				FBFBB5E816B17931002BA3EA /* Base64.m */,
-				FBFBB5E616B1791C002BA3EA /* RNCryptor */,
-				FBFBB5CE16B178DD002BA3EA /* main.m */,
-				FBFBB5D016B178DD002BA3EA /* Supporting Files */,
-			);
-			path = testphp;
-			sourceTree = "<group>";
-		};
-		FBFBB5D016B178DD002BA3EA /* Supporting Files */ = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5D116B178DD002BA3EA /* testphp-Prefix.pch */,
-			);
-			name = "Supporting Files";
-			sourceTree = "<group>";
-		};
-		FBFBB5E616B1791C002BA3EA /* RNCryptor */ = {
-			isa = PBXGroup;
-			children = (
-				FBFBB5D916B178FD002BA3EA /* RNCryptor.h */,
-				FBFBB5DA16B178FD002BA3EA /* RNCryptor.m */,
-				FBFBB5DB16B178FD002BA3EA /* RNCryptor+Private.h */,
-				FBFBB5DC16B178FD002BA3EA /* RNCryptorEngine.h */,
-				FBFBB5DD16B178FD002BA3EA /* RNCryptorEngine.m */,
-				FBFBB5DE16B178FD002BA3EA /* RNDecryptor.h */,
-				FBFBB5DF16B178FD002BA3EA /* RNDecryptor.m */,
-				FBFBB5E016B178FD002BA3EA /* RNEncryptor.h */,
-				FBFBB5E116B178FD002BA3EA /* RNEncryptor.m */,
-			);
-			name = RNCryptor;
-			sourceTree = "<group>";
-		};
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
-		FBFBB5C616B178DC002BA3EA /* testphp */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = FBFBB5D616B178DD002BA3EA /* Build configuration list for PBXNativeTarget "testphp" */;
-			buildPhases = (
-				FBFBB5C316B178DC002BA3EA /* Sources */,
-				FBFBB5C416B178DC002BA3EA /* Frameworks */,
-				FBFBB5C516B178DC002BA3EA /* CopyFiles */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = testphp;
-			productName = testphp;
-			productReference = FBFBB5C716B178DD002BA3EA /* testphp */;
-			productType = "com.apple.product-type.tool";
-		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
-		FBFBB5BE16B178DC002BA3EA /* Project object */ = {
-			isa = PBXProject;
-			attributes = {
-				LastUpgradeCheck = 0450;
-				ORGANIZATIONNAME = "Rob Napier";
-			};
-			buildConfigurationList = FBFBB5C116B178DC002BA3EA /* Build configuration list for PBXProject "testphp" */;
-			compatibilityVersion = "Xcode 3.2";
-			developmentRegion = English;
-			hasScannedForEncodings = 0;
-			knownRegions = (
-				en,
-			);
-			mainGroup = FBFBB5BC16B178DC002BA3EA;
-			productRefGroup = FBFBB5C816B178DD002BA3EA /* Products */;
-			projectDirPath = "";
-			projectRoot = "";
-			targets = (
-				FBFBB5C616B178DC002BA3EA /* testphp */,
-			);
-		};
-/* End PBXProject section */
-
-/* Begin PBXSourcesBuildPhase section */
-		FBFBB5C316B178DC002BA3EA /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				FBFBB5CF16B178DD002BA3EA /* main.m in Sources */,
-				FBFBB5E216B178FD002BA3EA /* RNCryptor.m in Sources */,
-				FBFBB5E316B178FD002BA3EA /* RNCryptorEngine.m in Sources */,
-				FBFBB5E416B178FD002BA3EA /* RNDecryptor.m in Sources */,
-				FBFBB5E516B178FD002BA3EA /* RNEncryptor.m in Sources */,
-				FBFBB5E916B17931002BA3EA /* Base64.m in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-/* End PBXSourcesBuildPhase section */
-
-/* Begin XCBuildConfiguration section */
-		FBFBB5D416B178DD002BA3EA /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				COPY_PHASE_STRIP = NO;
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_DYNAMIC_NO_PIC = NO;
-				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
-				GCC_OPTIMIZATION_LEVEL = 0;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DEBUG=1",
-					"$(inherited)",
-				);
-				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.8;
-				ONLY_ACTIVE_ARCH = YES;
-				SDKROOT = macosx;
-			};
-			name = Debug;
-		};
-		FBFBB5D516B178DD002BA3EA /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_OBJC_ARC = YES;
-				CLANG_WARN_EMPTY_BODY = YES;
-				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-				COPY_PHASE_STRIP = YES;
-				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				GCC_C_LANGUAGE_STANDARD = gnu99;
-				GCC_ENABLE_OBJC_EXCEPTIONS = YES;
-				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-				GCC_WARN_ABOUT_RETURN_TYPE = YES;
-				GCC_WARN_UNINITIALIZED_AUTOS = YES;
-				GCC_WARN_UNUSED_VARIABLE = YES;
-				MACOSX_DEPLOYMENT_TARGET = 10.8;
-				SDKROOT = macosx;
-			};
-			name = Release;
-		};
-		FBFBB5D716B178DD002BA3EA /* Debug */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "testphp/testphp-Prefix.pch";
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Debug;
-		};
-		FBFBB5D816B178DD002BA3EA /* Release */ = {
-			isa = XCBuildConfiguration;
-			buildSettings = {
-				GCC_PRECOMPILE_PREFIX_HEADER = YES;
-				GCC_PREFIX_HEADER = "testphp/testphp-Prefix.pch";
-				PRODUCT_NAME = "$(TARGET_NAME)";
-			};
-			name = Release;
-		};
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
-		FBFBB5C116B178DC002BA3EA /* Build configuration list for PBXProject "testphp" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				FBFBB5D416B178DD002BA3EA /* Debug */,
-				FBFBB5D516B178DD002BA3EA /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		FBFBB5D616B178DD002BA3EA /* Build configuration list for PBXNativeTarget "testphp" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				FBFBB5D716B178DD002BA3EA /* Debug */,
-				FBFBB5D816B178DD002BA3EA /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-/* End XCConfigurationList section */
-	};
-	rootObject = FBFBB5BE16B178DC002BA3EA /* Project object */;
-}

+ 0 - 7
tests-xcode/testphp.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Workspace
-   version = "1.0">
-   <FileRef
-      location = "self:testphp.xcodeproj">
-   </FileRef>
-</Workspace>

+ 0 - 53
tests-xcode/testphp/Base64.h

@@ -1,53 +0,0 @@
-//
-//  Base64.h
-//
-//  Version 1.1
-//
-//  Created by Nick Lockwood on 12/01/2012.
-//  Copyright (C) 2012 Charcoal Design
-//
-//  Distributed under the permissive zlib License
-//  Get the latest version from here:
-//
-//  https://github.com/nicklockwood/Base64
-//
-//  This software is provided 'as-is', without any express or implied
-//  warranty.  In no event will the authors be held liable for any damages
-//  arising from the use of this software.
-//
-//  Permission is granted to anyone to use this software for any purpose,
-//  including commercial applications, and to alter it and redistribute it
-//  freely, subject to the following restrictions:
-//
-//  1. The origin of this software must not be misrepresented; you must not
-//  claim that you wrote the original software. If you use this software
-//  in a product, an acknowledgment in the product documentation would be
-//  appreciated but is not required.
-//
-//  2. Altered source versions must be plainly marked as such, and must not be
-//  misrepresented as being the original software.
-//
-//  3. This notice may not be removed or altered from any source distribution.
-//
-
-#import <Foundation/Foundation.h>
-
-
-@interface NSData (Base64)
-
-+ (NSData *)dataWithBase64EncodedString:(NSString *)string;
-- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
-- (NSString *)base64EncodedString;
-
-@end
-
-
-@interface NSString (Base64)
-
-+ (NSString *)stringWithBase64EncodedString:(NSString *)string;
-- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
-- (NSString *)base64EncodedString;
-- (NSString *)base64DecodedString;
-- (NSData *)base64DecodedData;
-
-@end

+ 0 - 202
tests-xcode/testphp/Base64.m

@@ -1,202 +0,0 @@
-//
-//  Base64.m
-//
-//  Version 1.1
-//
-//  Created by Nick Lockwood on 12/01/2012.
-//  Copyright (C) 2012 Charcoal Design
-//
-//  Distributed under the permissive zlib License
-//  Get the latest version from here:
-//
-//  https://github.com/nicklockwood/Base64
-//
-//  This software is provided 'as-is', without any express or implied
-//  warranty.  In no event will the authors be held liable for any damages
-//  arising from the use of this software.
-//
-//  Permission is granted to anyone to use this software for any purpose,
-//  including commercial applications, and to alter it and redistribute it
-//  freely, subject to the following restrictions:
-//
-//  1. The origin of this software must not be misrepresented; you must not
-//  claim that you wrote the original software. If you use this software
-//  in a product, an acknowledgment in the product documentation would be
-//  appreciated but is not required.
-//
-//  2. Altered source versions must be plainly marked as such, and must not be
-//  misrepresented as being the original software.
-//
-//  3. This notice may not be removed or altered from any source distribution.
-//
-
-#import "Base64.h"
-
-
-#import <Availability.h>
-#if !__has_feature(objc_arc)
-#error This library requires automatic reference counting
-#endif
-
-
-@implementation NSData (Base64)
-
-+ (NSData *)dataWithBase64EncodedString:(NSString *)string
-{
-  const char lookup[] =
-  {
-    99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-    99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-    99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
-    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
-    99,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
-    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
-    99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99
-  };
-
-  NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
-  long long inputLength = [inputData length];
-  const unsigned char *inputBytes = [inputData bytes];
-
-  long long maxOutputLength = (inputLength / 4 + 1) * 3;
-  NSMutableData *outputData = [NSMutableData dataWithLength:maxOutputLength];
-  unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
-
-  int accumulator = 0;
-  long long outputLength = 0;
-  unsigned char accumulated[] = {0, 0, 0, 0};
-  for (long long i = 0; i < inputLength; i++)
-  {
-    unsigned char decoded = lookup[inputBytes[i] & 0x7F];
-    if (decoded != 99)
-    {
-      accumulated[accumulator] = decoded;
-      if (accumulator == 3)
-      {
-        outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4);
-        outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2);
-        outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3];
-      }
-      accumulator = (accumulator + 1) % 4;
-    }
-  }
-
-  //handle left-over data
-  if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4);
-  if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2);
-  if (accumulator > 2) outputLength++;
-
-  //truncate data to match actual output length
-  outputData.length = outputLength;
-  return outputLength? outputData: nil;
-}
-
-- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
-{
-  //ensure wrapWidth is a multiple of 4
-  wrapWidth = (wrapWidth / 4) * 4;
-
-  const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-  long long inputLength = [self length];
-  const unsigned char *inputBytes = [self bytes];
-
-  long long maxOutputLength = (inputLength / 3 + 1) * 4;
-  maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0;
-  unsigned char *outputBytes = (unsigned char *)malloc(maxOutputLength);
-
-  long long i;
-  long long outputLength = 0;
-  for (i = 0; i < inputLength - 2; i += 3)
-  {
-    outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
-    outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
-    outputBytes[outputLength++] = lookup[((inputBytes[i + 1] & 0x0F) << 2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
-    outputBytes[outputLength++] = lookup[inputBytes[i + 2] & 0x3F];
-
-    //add line break
-    if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
-    {
-      outputBytes[outputLength++] = '\r';
-      outputBytes[outputLength++] = '\n';
-    }
-  }
-
-  //handle left-over data
-  if (i == inputLength - 2)
-  {
-    // = terminator
-    outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
-    outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
-    outputBytes[outputLength++] = lookup[(inputBytes[i + 1] & 0x0F) << 2];
-    outputBytes[outputLength++] =   '=';
-  }
-  else if (i == inputLength - 1)
-  {
-    // == terminator
-    outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
-    outputBytes[outputLength++] = lookup[(inputBytes[i] & 0x03) << 4];
-    outputBytes[outputLength++] = '=';
-    outputBytes[outputLength++] = '=';
-  }
-
-  if (outputLength >= 4)
-  {
-    //truncate data to match actual output length
-    outputBytes = realloc(outputBytes, outputLength);
-    return [[NSString alloc] initWithBytesNoCopy:outputBytes
-                                          length:outputLength
-                                        encoding:NSASCIIStringEncoding
-                                    freeWhenDone:YES];
-  }
-  else if (outputBytes)
-  {
-    free(outputBytes);
-  }
-  return nil;
-}
-
-- (NSString *)base64EncodedString
-{
-  return [self base64EncodedStringWithWrapWidth:0];
-}
-
-@end
-
-
-@implementation NSString (Base64)
-
-+ (NSString *)stringWithBase64EncodedString:(NSString *)string
-{
-  NSData *data = [NSData dataWithBase64EncodedString:string];
-  if (data)
-  {
-    return [[self alloc] initWithData:data encoding:NSUTF8StringEncoding];
-  }
-  return nil;
-}
-
-- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
-{
-  NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
-  return [data base64EncodedStringWithWrapWidth:wrapWidth];
-}
-
-- (NSString *)base64EncodedString
-{
-  NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
-  return [data base64EncodedString];
-}
-
-- (NSString *)base64DecodedString
-{
-  return [NSString stringWithBase64EncodedString:self];
-}
-
-- (NSData *)base64DecodedData
-{
-  return [NSData dataWithBase64EncodedString:self];
-}
-
-@end

+ 0 - 30
tests-xcode/testphp/main.m

@@ -1,30 +0,0 @@
-//
-//  main.m
-//  testphp
-//
-//  Created by Rob Napier on 1/24/13.
-//  Copyright (c) 2013 Rob Napier. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-#import "RNDecryptor.h"
-#import "Base64.h"
-
-int main(int argc, const char * argv[])
-{
-
-  @autoreleasepool {
-    NSError *decryptionError = nil;
-    // Output of encrypt.php
-    NSData *fromPHPData = [@"AgF12bfxXb0lpR5tJAWTNb9jRUyyhTS5A8GBu5M1qhwA7CV0NMqHYTsyEsDjSccQiohU+FV9wk+VzGDrRmEpoK6PnVKTmsmJpnlqftxOv9BXlkmHIiEBCXzTprhzv4lWQ2MiEKkx+zda9B4WEoBuMTPxdLwnAxek9baTgv9mDH64oPmhZZWtlG3s9gSEaA1Cu2uYScDOin3+T1sEOdVAbnJG" base64DecodedData];
-
-    NSData *fromPHPDecryptedData = [RNDecryptor decryptData:fromPHPData withPassword:@"myPassword" error:&decryptionError];
-
-    NSLog(@"decryptionError %@", decryptionError);
-    NSLog(@"Result = %@", fromPHPDecryptedData);
-    NSLog(@"Result = %@", [[NSString alloc] initWithData:fromPHPDecryptedData encoding:NSUTF8StringEncoding]);
-
-  }
-    return 0;
-}
-

+ 0 - 7
tests-xcode/testphp/testphp-Prefix.pch

@@ -1,7 +0,0 @@
-//
-// Prefix header for all source files of the 'testphp' target in the 'testphp' project
-//
-
-#ifdef __OBJC__
-  #import <Foundation/Foundation.h>
-#endif

+ 0 - 8
tests/.gitignore

@@ -1,8 +0,0 @@
-
-# PEAR stuff, for running PHPUnit
-.pearrc
-pear
-
-# other stuff
-coverage
-

+ 0 - 13
tests/README

@@ -1,13 +0,0 @@
-RNCryptor PHP Tests
--------------------
-
-Unit tests use PHPUnit.  For convenience, a script is included to install a
-local copy of it from PEAR.  To install the local copy, run this:
-
-# ./install-local-phpunit.sh
-
-The file in this folder named "phpunit" is a symlink to the local
-installation's PHPUnit executable.  To run the tests, do this:
-
-# ./phpunit
-

+ 151 - 134
tests/RNCryptor/CryptorTest.php

@@ -1,144 +1,161 @@
 <?php
-namespace RNCryptor;
+namespace Tests\RNCryptor;
 
-class CryptorTest extends \PHPUnit_Framework_TestCase {
+use PHPUnit\Framework\TestCase;
+use RNCryptor\RNCryptor\Decryptor;
+use RNCryptor\RNCryptor\Encryptor;
 
-	// relative to __DIR__
-	const TEXT_FILENAME = 'lorem-ipsum.txt';
+class CryptorTest extends TestCase
+{
+    // relative to __DIR__
+    const TEXT_FILENAME = 'lorem-ipsum.txt';
 
-	const SAMPLE_PLAINTEXT = 'What\'s your name?  My name is Tilgath Pilesar.  Why are you crying?';
-	const SAMPLE_PASSWORD = 'do-not-write-this-down';
-	
-	const SAMPLE_PLAINTEXT_V2_BLOCKSIZE = 'Lorem ipsum dolor sit amet, cons';
+    const SAMPLE_PLAINTEXT = 'What\'s your name?  My name is Tilgath Pilesar.  Why are you crying?';
+    const SAMPLE_PASSWORD = 'do-not-write-this-down';
+    
+    const SAMPLE_PLAINTEXT_V2_BLOCKSIZE = 'Lorem ipsum dolor sit amet, cons';
 
-    public static function main() {
+    public static function main()
+    {
         $suite  = new PHPUnit_Framework_TestSuite(get_called_class());
-        $result = PHPUnit_TextUI_TestRunner::run($suite);
+        PHPUnit_TextUI_TestRunner::run($suite);
     }
 
-  	public function testCanDecryptSelfEncryptedDefaultVersion() {
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
-  		
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testCanDecryptSelfEncryptedStringEqualToBlockSizeMultiple() {
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT_V2_BLOCKSIZE, self::SAMPLE_PASSWORD);
-  	
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT_V2_BLOCKSIZE, $decrypted);
-  	}
-
-  	public function testCanDecryptSelfEncryptedVersion0() {
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
-  		
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testCanDecryptSelfEncryptedVersion1() {
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
-  		
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-  	
-  	public function testCanDecryptSelfEncryptedVersion2() {
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
-  	
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testCanDecryptLongText() {
-
-  		$text = file_get_contents(__DIR__ . '/_files/lorem-ipsum.txt');
-  	
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt($text, self::SAMPLE_PASSWORD);
-  	
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  		$this->assertEquals($text, $decrypted);
-  	}
-
-  	public function testVersion1TruncatesMultibytePasswords() {
-  		$password1 = '中文密码';
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 1);
-
-  		// Yikes, it's truncated! So with an all-multibyte password
-  		// like above, we can replace the last half of the string
-  		// with whatver we want, and decryption will still work.
-  		$password2 = '中文中文';
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password2);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password1);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testVersion2TruncatesMultibytePasswords() {
-  		$password1 = '中文密码';
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 2);
-
-  		// Yikes, it's truncated! So with an all-multibyte password
-  		// like above, we can replace the last half of the string
-  		// with whatver we want, and decryption will still work.
-  		$password2 = '中文中文';
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password2);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password1);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testVersion3AcceptsMultibytePasswords() {
-  		$password1 = '中文密码';
-  		$encryptor = new Encryptor();
-  		$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 3);
-
-  		$password2 = '中文中文';
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password2);
-  		$this->assertFalse($decrypted);
-
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt($encrypted, $password1);
-  		$this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
-  	}
-
-  	public function testCannotUseWithUnsupportedSchemaVersions() {
-  		$fakeSchemaNumber = 57;
-  		$encrypted = $this->_generateEncryptedStringWithUnsupportedSchemaNumber($fakeSchemaNumber);
-  		$decryptor = new Decryptor();
-  		$this->setExpectedException('Exception');
-  		$decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
-  	}
-
-  	private function _generateEncryptedStringWithUnsupportedSchemaNumber($fakeSchemaNumber) {
-  		$encryptor = new Encryptor();
-  		$plaintext = 'The price of ice is nice for mice';
-  		$encrypted = $encryptor->encrypt($plaintext, self::SAMPLE_PASSWORD);
-
-  		$encryptedBinary = base64_decode($encrypted);
+    public function testCanDecryptSelfEncryptedDefaultVersion()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
+        
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testCanDecryptSelfEncryptedStringEqualToBlockSizeMultiple()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT_V2_BLOCKSIZE, self::SAMPLE_PASSWORD);
+    
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT_V2_BLOCKSIZE, $decrypted);
+    }
+
+    public function testCanDecryptSelfEncryptedVersion0()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
+        
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testCanDecryptSelfEncryptedVersion1()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
+        
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+    
+    public function testCanDecryptSelfEncryptedVersion2()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
+    
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testCanDecryptLongText()
+    {
+        $text = file_get_contents(__DIR__ . '/../files/lorem-ipsum.txt');
+    
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt($text, self::SAMPLE_PASSWORD);
+    
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+        $this->assertEquals($text, $decrypted);
+    }
+
+    public function testVersion1TruncatesMultibytePasswords()
+    {
+        $password1 = '中文密码';
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 1);
+
+        // Yikes, it's truncated! So with an all-multibyte password
+        // like above, we can replace the last half of the string
+        // with whatver we want, and decryption will still work.
+        $password2 = '中文中文';
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password2);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password1);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testVersion2TruncatesMultibytePasswords()
+    {
+        $password1 = '中文密码';
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 2);
+
+        // Yikes, it's truncated! So with an all-multibyte password
+        // like above, we can replace the last half of the string
+        // with whatver we want, and decryption will still work.
+        $password2 = '中文中文';
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password2);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password1);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testVersion3AcceptsMultibytePasswords()
+    {
+        $password1 = '中文密码';
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, $password1, 3);
+
+        $password2 = '中文中文';
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password2);
+        $this->assertFalse($decrypted);
+
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt($encrypted, $password1);
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testCannotUseWithUnsupportedSchemaVersions()
+    {
+        $fakeSchemaNumber = 57;
+        $encrypted = $this->generateEncryptedStringWithUnsupportedSchemaNumber($fakeSchemaNumber);
+        $decryptor = new Decryptor;
+        $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
+    }
+
+    private function generateEncryptedStringWithUnsupportedSchemaNumber($fakeSchemaNumber)
+    {
+        $encryptor = new Encryptor;
+        $plaintext = 'The price of ice is nice for mice';
+        $encrypted = $encryptor->encrypt($plaintext, self::SAMPLE_PASSWORD);
+
+        $encryptedBinary = base64_decode($encrypted);
         $encryptedBinary = chr($fakeSchemaNumber) . substr($encryptedBinary, 1);
-  		return base64_encode($encryptedBinary);
-  	}
+        return base64_encode($encryptedBinary);
+    }
 }

+ 127 - 88
tests/RNCryptor/DecryptorTest.php

@@ -1,104 +1,143 @@
 <?php
-namespace RNCryptor;
+namespace Tests\RNCryptor;
 
-class DecryptorTest extends \PHPUnit_Framework_TestCase {
+use PHPUnit\Framework\TestCase;
+use RNCryptor\RNCryptor\Decryptor;
 
-	const IOS_PASSWORD = 'mypassword123$!';
+class DecryptorTest extends TestCase
+{
 
-	const PLAINTEXT_V0_LESS_THAN_ONE_BLOCK = 'Monkey';
-	const IOS_ENCRYPTED_V0_LESS_THAN_ONE_BLOCK = 'AACoGb/5NAItZ9gY0YkCXK0Q7d+1p2mNyFFKIDldCA5QRqX5i9MNpezRS7CDX8jUDKGtIlZU6d8CZQeJAAAAAAAAAAAAAAAA';
-	
-	const PLAINTEXT_V0_EXACTLY_ONE_BLOCK = 'O happy day now.';
-	const IOS_ENCRYPTED_V0_EXACTLY_ONE_BLOCK = 'AADsM/JbTInOMSm0epc/7MqQ1Ol2Fu/ySnQ0FknhJeTD6GpZo+SF8JDloHN82yZIHrOcJ3vZuXmrCUt3AysLYg6Vpu4KDwAAAAAAAAAAAAAAAA==';
-	
-	const PLAINTEXT_V0_EXACTLY_TWO_BLOCKS = 'Earth is round, sun is round too';
-	const IOS_ENCRYPTED_V0_EXACTLY_TWO_BLOCKS = 'AAApp4OoYpg4Fz+WSZDbcf5KPJasOkhdCnptrmwVkt58BZi/lnTWoIOf2IhIZhHsvTKYYEJsds6bFL/nZC/GtENusHWFyEw1IdtQ7KFSp8XZEhiAT88AAAAAAAAAAAAAAAA=';
-	
-	const PLAINTEXT_V0_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
-	const IOS_ENCRYPTED_V0_NON_BLOCK_INTERVAL = 'AADu55As8qH9KsSR17p1akydMUlbHrsHudMOr/yTj4olfQedJPTZg8hK4ua99zNkj3Nw7Hle1f1onHclWIYoLkWtMVk4Cp96CcxRhaWbBZqAVvTabtVruxcAi+GEB2K4rrmyARxB2QJH9tfz2yTFoFNMln+xOCUm0wAAAAAAAAAAAAAAAA==';
-	
-	const PLAINTEXT_V1_EXACTLY_ONE_BLOCK = 'Lorem ipsum dolor sit amet, cons';
-	const IOS_ENCRYPTED_V1_EXACTLY_ONE_BLOCK = 'AQEjdvTrgCAo8UMn9omCd30um3iMfq/Swiglr5I/wAESEuHBcdbtpbqpUliyDs6NyI83SQGzV9wpAdW8EYBzGdJ1AcE/nld27XX9jPF4Fj+X++Ws4EL2gEoJYO1fGuX3+hUFhIWaPCzxg/HvLMTDVq4k';
-	
-	const PLAINTEXT_V1_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
-	const IOS_ENCRYPTED_V1_NON_BLOCK_INTERVAL = 'AQE9u3aB1APkWDRHcfy1cvD3kwwoXUw+8JhtCkZ3xDkSQghIyFoqLgazX3cXBxv3Mj75sSofHoDI35KaFTdXovY3HQYAaQmMdPNvSRVGvlptkyr5LSBMUA3/Uj7lmhnaf515pN8pUbcbOV8RP+oWhXX4iKN009mrcMaX2j1KQz2JfFj8bfpbu9BOtj+1NotIe14=';
-	
-	const PLAINTEXT_V2_EXACTLY_ONE_BLOCK = 'Lorem ipsum dolor sit amet, cons';
-	const IOS_ENCRYPTED_V2_EXACTLY_ONE_BLOCK = 'AgEjDKHOcviYJbHBiZ4l0sku8Dd+0EZIUEz69uTtQI/yJorbiCu3mxpbTVrM6Kj4/vywmOdXdwSR0ov2S/oJ1rVtA8gJ2ulKyrYOOySfDS0/YioWKe21zJMfizK8PHveyjBoKmIJdPhT5/caF3l/+JCs';
-	
-	const PLAINTEXT_V2_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
-	const IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL = 'AgG8X+ixN6HN9zFnuK1NMJAPntIuC0+WPsmFhGL314zLuq1T9xWDHYzpnzW8EqDz81Amj36+EqrjazQ1gO9ao6bpMwUKdT2xY4ZUrhtCQm3LD2okbEIGjj5dtMJtB3i759WdnmNf8K0ULDWNzNQHPzdNDcEE2BPh+2kRaqVzWyBOzJppJoD5n+WdglS7BEBU+4U=';
+    const IOS_PASSWORD = 'mypassword123$!';
 
-  	public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthLessThanOneBlock() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_LESS_THAN_ONE_BLOCK, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V0_LESS_THAN_ONE_BLOCK, $decrypted);
-  	}
+    const PLAINTEXT_V0_LESS_THAN_ONE_BLOCK = 'Monkey';
+    const IOS_ENCRYPTED_V0_LESS_THAN_ONE_BLOCK
+        = 'AACoGb/5NAItZ9gY0YkCXK0Q7d+1p2mNyFFKIDldCA5QRqX5i9MNpezRS7CDX8jUDKGtIlZU6d8CZQeJAAAAAAAAAAAAAAAA';
+    
+    const PLAINTEXT_V0_EXACTLY_ONE_BLOCK = 'O happy day now.';
+    const IOS_ENCRYPTED_V0_EXACTLY_ONE_BLOCK
+        = 'AADsM/JbTInOMSm0epc/7MqQ1Ol2Fu/ySnQ0FknhJeTD6GpZo+SF8JDloHN82yZIHrOcJ3vZuXmrCUt3AysLYg6Vpu4KDwAAAAAAAAAAAAAA'
+        . 'AA==';
+    
+    const PLAINTEXT_V0_EXACTLY_TWO_BLOCKS = 'Earth is round, sun is round too';
+    const IOS_ENCRYPTED_V0_EXACTLY_TWO_BLOCKS
+        = 'AAApp4OoYpg4Fz+WSZDbcf5KPJasOkhdCnptrmwVkt58BZi/lnTWoIOf2IhIZhHsvTKYYEJsds6bFL/nZC/GtENusHWFyEw1IdtQ7KFSp8XZ'
+        . 'EhiAT88AAAAAAAAAAAAAAAA=';
+    
+    const PLAINTEXT_V0_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
+    const IOS_ENCRYPTED_V0_NON_BLOCK_INTERVAL
+        = 'AADu55As8qH9KsSR17p1akydMUlbHrsHudMOr/yTj4olfQedJPTZg8hK4ua99zNkj3Nw7Hle1f1onHclWIYoLkWtMVk4Cp96CcxRhaWbBZqA'
+        . 'VvTabtVruxcAi+GEB2K4rrmyARxB2QJH9tfz2yTFoFNMln+xOCUm0wAAAAAAAAAAAAAAAA==';
+    
+    const PLAINTEXT_V1_EXACTLY_ONE_BLOCK = 'Lorem ipsum dolor sit amet, cons';
+    const IOS_ENCRYPTED_V1_EXACTLY_ONE_BLOCK
+        = 'AQEjdvTrgCAo8UMn9omCd30um3iMfq/Swiglr5I/wAESEuHBcdbtpbqpUliyDs6NyI83SQGzV9wpAdW8EYBzGdJ1AcE/nld27XX9jPF4Fj+X'
+        . '++Ws4EL2gEoJYO1fGuX3+hUFhIWaPCzxg/HvLMTDVq4k';
+    
+    const PLAINTEXT_V1_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
+    const IOS_ENCRYPTED_V1_NON_BLOCK_INTERVAL
+        = 'AQE9u3aB1APkWDRHcfy1cvD3kwwoXUw+8JhtCkZ3xDkSQghIyFoqLgazX3cXBxv3Mj75sSofHoDI35KaFTdXovY3HQYAaQmMdPNvSRVGvlpt'
+        . 'kyr5LSBMUA3/Uj7lmhnaf515pN8pUbcbOV8RP+oWhXX4iKN009mrcMaX2j1KQz2JfFj8bfpbu9BOtj+1NotIe14=';
+    
+    const PLAINTEXT_V2_EXACTLY_ONE_BLOCK = 'Lorem ipsum dolor sit amet, cons';
+    const IOS_ENCRYPTED_V2_EXACTLY_ONE_BLOCK
+        = 'AgEjDKHOcviYJbHBiZ4l0sku8Dd+0EZIUEz69uTtQI/yJorbiCu3mxpbTVrM6Kj4/vywmOdXdwSR0ov2S/oJ1rVtA8gJ2ulKyrYOOySfDS0/'
+        . 'YioWKe21zJMfizK8PHveyjBoKmIJdPhT5/caF3l/+JCs';
+    
+    const PLAINTEXT_V2_NON_BLOCK_INTERVAL = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do...';
+    const IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL
+        = 'AgG8X+ixN6HN9zFnuK1NMJAPntIuC0+WPsmFhGL314zLuq1T9xWDHYzpnzW8EqDz81Amj36+EqrjazQ1gO9ao6bpMwUKdT2xY4ZUrhtCQm3L'
+        . 'D2okbEIGjj5dtMJtB3i759WdnmNf8K0ULDWNzNQHPzdNDcEE2BPh+2kRaqVzWyBOzJppJoD5n+WdglS7BEBU+4U=';
 
-  	public function testCanDecryptIosEncryptedVersion0WithPlaintextReallyLong() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(file_get_contents(__DIR__ . '/_files/lorem-ipsum-encrypted-base64-schema0.txt'), self::IOS_PASSWORD);
-  		$this->assertEquals(file_get_contents(__DIR__ . '/_files/lorem-ipsum.txt'), $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthLessThanOneBlock()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_LESS_THAN_ONE_BLOCK, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V0_LESS_THAN_ONE_BLOCK, $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthExactlyOneBlock() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V0_EXACTLY_ONE_BLOCK, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion0WithPlaintextReallyLong()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(
+            file_get_contents(__DIR__ . '/../files/lorem-ipsum-encrypted-base64-schema0.txt'),
+            self::IOS_PASSWORD
+        );
+        $this->assertEquals(file_get_contents(__DIR__ . '/../files/lorem-ipsum.txt'), $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthExactlyTwoBlocks() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_EXACTLY_TWO_BLOCKS, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V0_EXACTLY_TWO_BLOCKS, $decrypted);
-  	}
-  	
-  	public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthNotOnBlockInterval() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V0_NON_BLOCK_INTERVAL, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthExactlyOneBlock()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V0_EXACTLY_ONE_BLOCK, $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion1WithPlaintextReallyLong() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(file_get_contents(__DIR__ . '/_files/lorem-ipsum-encrypted-base64-schema1.txt'), self::IOS_PASSWORD);
-  		$this->assertEquals(file_get_contents(__DIR__ . '/_files/lorem-ipsum.txt'), $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthExactlyTwoBlocks()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_EXACTLY_TWO_BLOCKS, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V0_EXACTLY_TWO_BLOCKS, $decrypted);
+    }
+    
+    public function testCanDecryptIosEncryptedVersion0WithPlaintextLengthNotOnBlockInterval()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V0_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V0_NON_BLOCK_INTERVAL, $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion1WithPlaintextLengthExactlyOneBlock() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V1_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V1_EXACTLY_ONE_BLOCK, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion1WithPlaintextReallyLong()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(
+            file_get_contents(__DIR__ . '/../files/lorem-ipsum-encrypted-base64-schema1.txt'),
+            self::IOS_PASSWORD
+        );
+        $this->assertEquals(file_get_contents(__DIR__ . '/../files/lorem-ipsum.txt'), $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion1WithPlaintextLengthNotOnBlockInterval() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V1_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V1_NON_BLOCK_INTERVAL, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion1WithPlaintextLengthExactlyOneBlock()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V1_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V1_EXACTLY_ONE_BLOCK, $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion2WithPlaintextReallyLong() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(file_get_contents(__DIR__ . '/_files/lorem-ipsum-encrypted-base64-schema2.txt'), self::IOS_PASSWORD);
-  		$this->assertEquals(file_get_contents(__DIR__ . '/_files/lorem-ipsum.txt'), $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion1WithPlaintextLengthNotOnBlockInterval()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V1_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V1_NON_BLOCK_INTERVAL, $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion2WithPlaintextLengthExactlyOneBlock() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V2_EXACTLY_ONE_BLOCK, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion2WithPlaintextReallyLong()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(
+            file_get_contents(__DIR__ . '/../files/lorem-ipsum-encrypted-base64-schema2.txt'),
+            self::IOS_PASSWORD
+        );
+        $this->assertEquals(file_get_contents(__DIR__ . '/../files/lorem-ipsum.txt'), $decrypted);
+    }
 
-  	public function testCanDecryptIosEncryptedVersion2WithPlaintextLengthNotOnBlockInterval() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
-  		$this->assertEquals(self::PLAINTEXT_V2_NON_BLOCK_INTERVAL, $decrypted);
-  	}
+    public function testCanDecryptIosEncryptedVersion2WithPlaintextLengthExactlyOneBlock()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_EXACTLY_ONE_BLOCK, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V2_EXACTLY_ONE_BLOCK, $decrypted);
+    }
 
-  	public function testDecryptingWithBadPasswordFails() {
-  		$decryptor = new Decryptor();
-  		$decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL, 'bad-password');
-  		$this->assertEquals(false, $decrypted);
-  	}
-  	
+    public function testCanDecryptIosEncryptedVersion2WithPlaintextLengthNotOnBlockInterval()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL, self::IOS_PASSWORD);
+        $this->assertEquals(self::PLAINTEXT_V2_NON_BLOCK_INTERVAL, $decrypted);
+    }
+
+    public function testDecryptingWithBadPasswordFails()
+    {
+        $decryptor = new Decryptor;
+        $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL, 'bad-password');
+        $this->assertEquals(false, $decrypted);
+    }
 }

+ 46 - 36
tests/RNCryptor/EncryptorTest.php

@@ -1,54 +1,64 @@
 <?php
-namespace RNCryptor;
+namespace Tests\RNCryptor;
 
-class EncryptorTest extends \PHPUnit_Framework_TestCase {
+use PHPUnit\Framework\TestCase;
+use RNCryptor\RNCryptor\Encryptor;
 
-	const SAMPLE_PLAINTEXT = 'Hello, how are you today?  I am doing fine.';
-	const SAMPLE_PASSWORD = 'keep-out-123';
+class EncryptorTest extends TestCase
+{
 
-    public function testCanEncryptWithDefaultVersion() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
-    	$this->assertNotEmpty($encrypted);
+    const SAMPLE_PLAINTEXT = 'Hello, how are you today?  I am doing fine.';
+    const SAMPLE_PASSWORD = 'keep-out-123';
+
+    public function testCanEncryptWithDefaultVersion()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
+        $this->assertNotEmpty($encrypted);
     }
 
-    public function testCanEncryptWithVersion0() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
-    	$this->assertNotEmpty($encrypted);
+    public function testCanEncryptWithVersion0()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
+        $this->assertNotEmpty($encrypted);
     }
     
-    public function testCanEncryptWithVersion1() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
-    	$this->assertNotEmpty($encrypted);
+    public function testCanEncryptWithVersion1()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
+        $this->assertNotEmpty($encrypted);
     }
     
-    public function testCanEncryptWithVersion2() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
-    	$this->assertNotEmpty($encrypted);
+    public function testCanEncryptWithVersion2()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
+        $this->assertNotEmpty($encrypted);
     }
 
-    public function testSelfEncryptedVersion0VectorIsVersion0() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
-    	$actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
-    	$this->assertEquals(0, $actualVersion);
+    public function testSelfEncryptedVersion0VectorIsVersion0()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 0);
+        $actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
+        $this->assertEquals(0, $actualVersion);
     }
     
-    public function testSelfEncryptedVersion1VectorIsVersion1() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
-    	$actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
-    	$this->assertEquals(1, $actualVersion);
+    public function testSelfEncryptedVersion1VectorIsVersion1()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 1);
+        $actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
+        $this->assertEquals(1, $actualVersion);
     }
 
-    public function testSelfEncryptedVersion2VectorIsVersion2() {
-    	$encryptor = new Encryptor();
-    	$encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
-    	$actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
-    	$this->assertEquals(2, $actualVersion);
+    public function testSelfEncryptedVersion2VectorIsVersion2()
+    {
+        $encryptor = new Encryptor;
+        $encrypted = $encryptor->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD, 2);
+        $actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
+        $this->assertEquals(2, $actualVersion);
     }
-
 }

+ 162 - 158
tests/RNCryptor/VectorTest.php

@@ -1,248 +1,252 @@
 <?php
-namespace RNCryptor;
-
-class VectorBase extends \PHPUnit_Framework_TestCase {
-
-	/**
-	 * Base directory for the test vector files,
-	 * relative to __DIR__
-	 */
-	const PARALLEL_VECTOR_DIR = '/../../../spec/vectors/CURRENT';
-	const SUBPACKAGE_VECTOR_DIR = '/../../vendor/rncryptor/spec/vectors/CURRENT';
-
-	public function testKdfVectorAllFieldsEmptyOrZero() {
-
-		$vector = $this->_getVectors('kdf')[0];
-
-        $cryptor = new Cryptor();
+namespace Tests\RNCryptor;
+
+use PHPUnit\Framework\TestCase;
+use RNCryptor\RNCryptor\Cryptor;
+use RNCryptor\RNCryptor\Encryptor;
+
+class VectorBase extends TestCase
+{
+    /**
+     * Base directory for the test vector files,
+     * relative to __DIR__
+     */
+    const PARALLEL_VECTOR_DIR = '/../../../spec/vectors/CURRENT';
+    const SUBPACKAGE_VECTOR_DIR = '/../../vendor/rncryptor/spec/vectors/CURRENT';
+
+    public function testKdfVectorAllFieldsEmptyOrZero()
+    {
+        $vector = $this->getVectors('kdf')[0];
+
+        $cryptor = new Cryptor;
         $key = $cryptor->generateKey(
-            $this->_prettyHexToBin($vector['salt_hex']),
+            $this->prettyHexToBin($vector['salt_hex']),
             $vector['password'],
             $vector['version']
         );
 
-        $this->assertEquals($this->_prettyHexToBin($vector['key_hex']), $key);
-	}
-
-    public function testKdfVectorOneByte() {
+        $this->assertEquals($this->prettyHexToBin($vector['key_hex']), $key);
+    }
 
-        $vector = $this->_getVectors('kdf')[1];
+    public function testKdfVectorOneByte()
+    {
+        $vector = $this->getVectors('kdf')[1];
 
-        $cryptor = new Cryptor();
+        $cryptor = new Cryptor;
         $key = $cryptor->generateKey(
-            $this->_prettyHexToBin($vector['salt_hex']),
+            $this->prettyHexToBin($vector['salt_hex']),
             $vector['password'],
             $vector['version']
         );
 
-        $this->assertEquals($this->_prettyHexToBin($vector['key_hex']), $key);
+        $this->assertEquals($this->prettyHexToBin($vector['key_hex']), $key);
     }
 
-    public function testKdfVectorExactlyOneBlock() {
+    public function testKdfVectorExactlyOneBlock()
+    {
+        $vector = $this->getVectors('kdf')[2];
 
-        $vector = $this->_getVectors('kdf')[2];
-
-        $cryptor = new Cryptor();
+        $cryptor = new Cryptor;
         $key = $cryptor->generateKey(
-            $this->_prettyHexToBin($vector['salt_hex']),
+            $this->prettyHexToBin($vector['salt_hex']),
             $vector['password'],
             $vector['version']
         );
 
-        $this->assertEquals($this->_prettyHexToBin($vector['key_hex']), $key);
+        $this->assertEquals($this->prettyHexToBin($vector['key_hex']), $key);
     }
 
-    public function testKdfVectorMoreThanOneBlock() {
-
-        $vector = $this->_getVectors('kdf')[3];
+    public function testKdfVectorMoreThanOneBlock()
+    {
+        $vector = $this->getVectors('kdf')[3];
 
-        $cryptor = new Cryptor();
+        $cryptor = new Cryptor;
         $key = $cryptor->generateKey(
-            $this->_prettyHexToBin($vector['salt_hex']),
+            $this->prettyHexToBin($vector['salt_hex']),
             $vector['password'],
             $vector['version']
         );
 
-        $this->assertEquals($this->_prettyHexToBin($vector['key_hex']), $key);
+        $this->assertEquals($this->prettyHexToBin($vector['key_hex']), $key);
     }
 
-    public function testKeyVectorAllFieldsEmptyOrZero() {
-
-		$vector = $this->_getVectors('key')[0];
+    public function testKeyVectorAllFieldsEmptyOrZero()
+    {
+        $vector = $this->getVectors('key')[0];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitraryKeys(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
-            $this->_prettyHexToBin($vector['enc_key_hex']),
-            $this->_prettyHexToBin($vector['hmac_key_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['enc_key_hex']),
+            $this->prettyHexToBin($vector['hmac_key_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
-	}
-
-    public function testKeyVectorOneByte() {
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
+    }
 
-        $vector = $this->_getVectors('key')[1];
+    public function testKeyVectorOneByte()
+    {
+        $vector = $this->getVectors('key')[1];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitraryKeys(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
-            $this->_prettyHexToBin($vector['enc_key_hex']),
-            $this->_prettyHexToBin($vector['hmac_key_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['enc_key_hex']),
+            $this->prettyHexToBin($vector['hmac_key_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    public function testKeyVectorExactlyOneBlock() {
-
-        $vector = $this->_getVectors('key')[2];
+    public function testKeyVectorExactlyOneBlock()
+    {
+        $vector = $this->getVectors('key')[2];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitraryKeys(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
-            $this->_prettyHexToBin($vector['enc_key_hex']),
-            $this->_prettyHexToBin($vector['hmac_key_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['enc_key_hex']),
+            $this->prettyHexToBin($vector['hmac_key_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    public function testKeyVectorMoreThanOneBlock() {
+    public function testKeyVectorMoreThanOneBlock()
+    {
+        $vector = $this->getVectors('key')[3];
 
-        $vector = $this->_getVectors('key')[3];
-
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitraryKeys(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
-            $this->_prettyHexToBin($vector['enc_key_hex']),
-            $this->_prettyHexToBin($vector['hmac_key_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['enc_key_hex']),
+            $this->prettyHexToBin($vector['hmac_key_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    public function testPasswordVectorAllFieldsEmptyOrZero() {
-
-		$vector = $this->_getVectors('password')[0];
+    public function testPasswordVectorAllFieldsEmptyOrZero()
+    {
+        $vector = $this->getVectors('password')[0];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitrarySalts(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
             $vector['password'],
-            $this->_prettyHexToBin($vector['enc_salt_hex']),
-            $this->_prettyHexToBin($vector['hmac_salt_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['enc_salt_hex']),
+            $this->prettyHexToBin($vector['hmac_salt_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
-	}
-
-    public function testPasswordVectorOneByte() {
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
+    }
 
-        $vector = $this->_getVectors('password')[1];
+    public function testPasswordVectorOneByte()
+    {
+        $vector = $this->getVectors('password')[1];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitrarySalts(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
             $vector['password'],
-            $this->_prettyHexToBin($vector['enc_salt_hex']),
-            $this->_prettyHexToBin($vector['hmac_salt_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['enc_salt_hex']),
+            $this->prettyHexToBin($vector['hmac_salt_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    public function testPasswordVectorExactlyOneBlock() {
-
-        $vector = $this->_getVectors('password')[2];
+    public function testPasswordVectorExactlyOneBlock()
+    {
+        $vector = $this->getVectors('password')[2];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitrarySalts(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
             $vector['password'],
-            $this->_prettyHexToBin($vector['enc_salt_hex']),
-            $this->_prettyHexToBin($vector['hmac_salt_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['enc_salt_hex']),
+            $this->prettyHexToBin($vector['hmac_salt_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    public function testPasswordVectorMoreThanOneBlock() {
-
-        $vector = $this->_getVectors('password')[3];
+    public function testPasswordVectorMoreThanOneBlock()
+    {
+        $vector = $this->getVectors('password')[3];
 
-        $encryptor = new Encryptor();
+        $encryptor = new Encryptor;
         $encryptedB64 = $encryptor->encryptWithArbitrarySalts(
-            $this->_prettyHexToBin($vector['plaintext_hex']),
+            $this->prettyHexToBin($vector['plaintext_hex']),
             $vector['password'],
-            $this->_prettyHexToBin($vector['enc_salt_hex']),
-            $this->_prettyHexToBin($vector['hmac_salt_hex']),
-            $this->_prettyHexToBin($vector['iv_hex']),
+            $this->prettyHexToBin($vector['enc_salt_hex']),
+            $this->prettyHexToBin($vector['hmac_salt_hex']),
+            $this->prettyHexToBin($vector['iv_hex']),
             $vector['version']
         );
 
-        $this->assertEquals($vector['ciphertext_hex'], $this->_binToPrettyHex(base64_decode($encryptedB64)));
+        $this->assertEquals($vector['ciphertext_hex'], $this->binToPrettyHex(base64_decode($encryptedB64)));
     }
 
-    private function _prettyHexToBin($data) {
-		return hex2bin(preg_replace("/[^a-z0-9]/i", '', $data));
-	}
-
-	private function _binToPrettyHex($data) {
-
-		$hex = bin2hex($data);
-
-		$prettyHex = '';
-		foreach (str_split($hex, 8) as $index => $part) {
-			$prettyHex .= ($index != 0 ? ' ' : '') . $part;
-		}
-		return $prettyHex;
-	}
-
-	private function _getVectors($filename) {
-
-		$absolutePath = __DIR__ . '/' . self::PARALLEL_VECTOR_DIR . '/' . $filename;
-		if (!file_exists($absolutePath)) {
-			$absolutePath = __DIR__ . '/' . self::SUBPACKAGE_VECTOR_DIR . '/' . $filename;
-			if (!file_exists($absolutePath)) {
-				throw new \Exception('No such file: ' . $absolutePath);
-			}
-		}
-
-		$index = -1;
-		$tests = array();
-		$fd = fopen($absolutePath, 'r');
-		while (!feof($fd)) {
-			$line = trim(fgets($fd));
-	
-			if (preg_match("/^\s*(\w+)\s*\:\s*(.*)/", $line, $match)) {
-				$key = strtolower($match[1]);
-				$value = trim($match[2]);
-	
-				if ($key == 'title') {
-					$index++;
-				}
-	
-				$tests[$index][$key] = $value;
-			}
-		}
-		fclose($fd);
-	
-		return $tests;
-	}
-	
+    private function prettyHexToBin($data)
+    {
+        return hex2bin(preg_replace("/[^a-z0-9]/i", '', $data));
+    }
+
+    private function binToPrettyHex($data)
+    {
+        $hex = bin2hex($data);
+
+        $prettyHex = '';
+        foreach (str_split($hex, 8) as $index => $part) {
+            $prettyHex .= ($index != 0 ? ' ' : '') . $part;
+        }
+        return $prettyHex;
+    }
+
+    private function getVectors($filename)
+    {
+        $absolutePath = __DIR__ . '/' . self::PARALLEL_VECTOR_DIR . '/' . $filename;
+        if (!file_exists($absolutePath)) {
+            $absolutePath = __DIR__ . '/' . self::SUBPACKAGE_VECTOR_DIR . '/' . $filename;
+            if (!file_exists($absolutePath)) {
+                throw new \Exception('No such file: ' . $absolutePath);
+            }
+        }
+
+        $index = -1;
+        $tests = array();
+        $fd = fopen($absolutePath, 'r');
+        while (!feof($fd)) {
+            $line = trim(fgets($fd));
+    
+            if (preg_match("/^\s*(\w+)\s*\:\s*(.*)/", $line, $match)) {
+                $key = strtolower($match[1]);
+                $value = trim($match[2]);
+    
+                if ($key == 'title') {
+                    $index++;
+                }
+    
+                $tests[$index][$key] = $value;
+            }
+        }
+        fclose($fd);
+    
+        return $tests;
+    }
 }

+ 0 - 18
tests/bootstrap.php

@@ -1,18 +0,0 @@
-<?php
-
-/*
- * This file is part of the RNCryptor package.
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-if (file_exists(__DIR__.'/../vendor/autoload.php')) {
-    require __DIR__.'/../vendor/autoload.php';
-
-} else if (@include('RNCryptor/Autoloader.php')) {
-    RNCryptor\Autoloader::register();
-
-} else {
-    die('ERROR: Unable to find a suitable mean to register RNCryptor\Autoloader.');
-}

+ 0 - 0
tests/RNCryptor/_files/lorem-ipsum-encrypted-base64-schema0.txt → tests/files/lorem-ipsum-encrypted-base64-schema0.txt


+ 0 - 0
tests/RNCryptor/_files/lorem-ipsum-encrypted-base64-schema1.txt → tests/files/lorem-ipsum-encrypted-base64-schema1.txt


+ 0 - 0
tests/RNCryptor/_files/lorem-ipsum-encrypted-base64-schema2.txt → tests/files/lorem-ipsum-encrypted-base64-schema2.txt


+ 0 - 0
tests/RNCryptor/_files/lorem-ipsum.txt → tests/files/lorem-ipsum.txt


+ 0 - 16
tests/functions.php

@@ -1,16 +0,0 @@
-<?php
-
-if (!function_exists('hex2bin')) {
-
-	/**
-	 * If the PHP version being used is earlier than 5.4.0, we need to
-	 * make up for the lack of a hex2bin() function.
-	 */
-	function hex2bin($data) {
-		$bin = '';
-		foreach (str_split($data, 2) as $pair) {
-			$bin .= chr(hexdec($pair));
-		}
-		return $bin;
-	}
-}

+ 0 - 29
tests/install-local-phpunit.sh

@@ -1,29 +0,0 @@
-#!/bin/bash
-
-echo "Starting to install dependencies"
-echo
-
-PEAR_CONFIG_FILE=".pearrc"
-
-# clean up prior installation
-rm -rf pear $PEAR_CONFIG_FILE || exit 1
-
-# make new local config
-pear config-create $(pwd)/ $PEAR_CONFIG_FILE || exit 1
-pear -c $PEAR_CONFIG_FILE config-set auto_discover 1 || exit 1
-
-pear -c $PEAR_CONFIG_FILE channel-discover pear.phpunit.de || exit 1
-pear -c $PEAR_CONFIG_FILE install phpunit/PHPUnit || exit 1
-
-# patch PHP's include_path in phpunit script
-PEAR_DIR=$(pwd)/pear
-sed '2a\
-set_include_path(dirname(__FILE__) . "/php" . PATH_SEPARATOR . get_include_path());
-' $PEAR_DIR/phpunit > $PEAR_DIR/phpunit2 || exit 1
-mv $PEAR_DIR/phpunit2 $PEAR_DIR/phpunit || exit 1
-chmod 755 $PEAR_DIR/phpunit || exit 1
-
-cd $ORIG_DIR
-
-echo
-echo "Done installing dependencies"