Browse Source

Custom iterations: Inline docs and warnings; Code cleanups and tweaks; Unit tests

Curtis Farnham 4 years ago
parent
commit
8374586b80

+ 2 - 2
examples/decrypt.php

@@ -8,8 +8,8 @@ $base64Encrypted = "AgGXutvFqW9RqQuokYLjehbfM7F+8OO/2sD8g3auA+oNCQFoarRmc59qcKJv
     . 'EN7Cv";
 
 $cryptor = new \RNCryptor\RNCryptor\Decryptor;
-//uncomment the following line to set iterations
-//$cryptor->setIterations(1);
+
+//$cryptor->setIterations(100);
 $plaintext = $cryptor->decrypt($base64Encrypted, $password);
 
 echo "Base64 Encrypted:\n$base64Encrypted\n\n";

+ 2 - 2
examples/encrypt.php

@@ -6,8 +6,8 @@ $password = "myPassword";
 $plaintext = "Here is my test vector. It's not too long, but more than a block and needs padding.";
 
 $cryptor = new \RNCryptor\RNCryptor\Encryptor;
-//uncomment the following line to set iterations
-//$cryptor->setIterations(1);
+
+//$cryptor->setIterations(100);
 $base64Encrypted = $cryptor->encrypt($plaintext, $password);
 
 echo "Plaintext:\n$plaintext\n\n";

+ 31 - 3
src/RNCryptor/Cryptor.php

@@ -11,9 +11,37 @@ class Cryptor
 
     protected $iterations = 10000;
 
-	public function setIterations($num) {
-		$this->iterations = $num;
-	}
+    /**
+     * Set the number of PBKDF2 iterations to use
+     *
+     * NOTE: The RNCryptor spec explicitly specifies 10,000 iterations and does not allow this to be customized.
+     * However, some integrators desire to customize it anyway in order to reduce CPU consumption. This is
+     * supported by some implementations in other languages (e.g. Java, Swift). So we're allowing it here too.
+     *
+     * WARNING: Reducing the number of iterations weakens security. Only do this if you are sure you need it, and
+     * are prepared to deal with the potential consequences.
+     *
+     * IMPORTANT: The same number of iterations MUST be used for encrypting a given payload as are used for decrypting
+     * it, and the encrypted payload doesn't inform the decryptor about how many iterations were used.  Therefore,
+     * any custom number of iterations is out-of-band and will have to be known ahead of time by whatever implementation
+     * is decrypting the same payload.
+     *
+     * @param int $iterations Number of iterations
+     */
+    public function setIterations($iterations) : self
+    {
+        $this->iterations = $iterations;
+
+        return $this;
+    }
+
+    /**
+     * @return int Number of iterations which this instance is configured to use
+     */
+    public function getIterations()
+    {
+        return $this->iterations;
+    }
 
     public function generateKey($salt, $password, $version = self::DEFAULT_SCHEMA_VERSION)
     {

+ 26 - 0
tests/RNCryptor/CryptorTest.php

@@ -148,6 +148,32 @@ class CryptorTest extends TestCase
         $decryptor->decrypt($encrypted, self::SAMPLE_PASSWORD);
     }
 
+    public function testUsesCustomIterations()
+    {
+        $encrypted = (new Encryptor)
+            ->setIterations(42)
+            ->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
+
+        $decrypted = (new Decryptor)
+            ->setIterations(42)
+            ->decrypt($encrypted, self::SAMPLE_PASSWORD);
+
+        $this->assertEquals(self::SAMPLE_PLAINTEXT, $decrypted);
+    }
+
+    public function testFailsRoundtripIfIterationsDiffer()
+    {
+        $encrypted = (new Encryptor)
+            ->setIterations(42)
+            ->encrypt(self::SAMPLE_PLAINTEXT, self::SAMPLE_PASSWORD);
+
+        $decrypted = (new Decryptor)
+            ->setIterations(43)
+            ->decrypt($encrypted, self::SAMPLE_PASSWORD);
+
+        $this->assertFalse($decrypted);
+    }
+
     private function generateEncryptedStringWithUnsupportedSchemaNumber($fakeSchemaNumber)
     {
         $encryptor = new Encryptor;

+ 8 - 0
tests/RNCryptor/DecryptorTest.php

@@ -140,4 +140,12 @@ class DecryptorTest extends TestCase
         $decrypted = $decryptor->decrypt(self::IOS_ENCRYPTED_V2_NON_BLOCK_INTERVAL, 'bad-password');
         $this->assertEquals(false, $decrypted);
     }
+
+    public function testCanSetCustomIterations()
+    {
+        $decryptor = new Decryptor;
+        $decryptor->setIterations(42);
+
+        $this->assertEquals(42, $decryptor->getIterations());
+    }
 }

+ 8 - 0
tests/RNCryptor/EncryptorTest.php

@@ -61,4 +61,12 @@ class EncryptorTest extends TestCase
         $actualVersion = ord(substr(base64_decode($encrypted), 0, 1));
         $this->assertEquals(2, $actualVersion);
     }
+
+    public function testCanSetCustomIterations()
+    {
+        $encryptor = new Encryptor;
+        $encryptor->setIterations(42);
+
+        $this->assertEquals(42, $encryptor->getIterations());
+    }
 }