source/Threema/MsgApi/Tests/CryptToolTest.php
<?php
/**
* @author Threema GmbH
* @copyright Copyright (c) 2015-2016 Threema GmbH
*/
namespace Threema\MsgApi\Tests;
use Threema\Console\Common;
use Threema\MsgApi\Messages\TextMessage;
use Threema\MsgApi\Tools\CryptTool;
class CryptToolTests extends \PHPUnit_Framework_TestCase {
/**
* test generating key pair
*/
public function testCreateKeyPair() {
$this->doTest(function(CryptTool $cryptTool, $prefix) {
$this->assertNotNull($cryptTool, $prefix.' could not instance crypto tool');
$keyPair = $cryptTool->generateKeyPair();
$this->assertNotNull($keyPair, $prefix.': invalid key pair');
$this->assertNotNull($keyPair->privateKey, $prefix.': private key is null');
$this->assertNotNull($keyPair->publicKey, $prefix.': public key is null');
});
}
/**
* test generating random nonce
*/
public function testRandomNonce() {
$this->doTest(function(CryptTool $cryptTool, $prefix) {
$randomNonce = $cryptTool->randomNonce();
$this->assertEquals(24, strlen($randomNonce), $prefix.': random nonce size not 24');
});
}
public function testDecrypt() {
/** @noinspection PhpUnusedParameterInspection */
$this->doTest(function(CryptTool $cryptTool, $prefix) {
$nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0';
$box = '45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a';
$privateKey = Common::getPrivateKey(Constants::otherPrivateKey);
$this->assertNotNull($privateKey);
$publicKey = Common::getPublicKey(Constants::myPublicKey);
$this->assertNotNull($publicKey);
$message = $cryptTool->decryptMessage($cryptTool->hex2bin($box),
$cryptTool->hex2bin($privateKey),
$cryptTool->hex2bin($publicKey),
$cryptTool->hex2bin($nonce));
$this->assertNotNull($message);
$this->assertTrue($message instanceof TextMessage);
if($message instanceof TextMessage) {
$this->assertEquals($message->getText(), 'Dies ist eine Testnachricht. äöü');
}
});
}
public function testEncrypt() {
/** @noinspection PhpUnusedParameterInspection */
$this->doTest(function(CryptTool $cryptTool, $prefix) {
$text = 'Dies ist eine Testnachricht. äöü';
$nonce = '0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0';
$privateKey = Common::getPrivateKey(Constants::myPrivateKey);
$this->assertNotNull($privateKey);
$publicKey = Common::getPublicKey(Constants::otherPublicKey);
$this->assertNotNull($publicKey);
$message = $cryptTool->encryptMessageText($text,
$cryptTool->hex2bin($privateKey),
$cryptTool->hex2bin($publicKey),
$cryptTool->hex2bin($nonce));
$this->assertNotNull($message);
$box = $cryptTool->decryptMessage($message,
$cryptTool->hex2bin(Common::getPrivateKey(Constants::otherPrivateKey)),
$cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey)),
$cryptTool->hex2bin($nonce));
$this->assertNotNull($box);
});
}
public function testDerivePublicKey() {
$this->doTest(function(CryptTool $cryptTool, $prefix){
$publicKey = $cryptTool->derivePublicKey($cryptTool->hex2bin(Common::getPrivateKey(Constants::myPrivateKey)));
$myPublicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey));
$this->assertEquals($publicKey, $myPublicKey, $prefix.' derive public key failed');
});
}
public function testEncryptImage() {
$threemaIconContent = file_get_contents(dirname(__FILE__).'/threema.jpg');
/** @noinspection PhpUnusedParameterInspection */
$this->doTest(function(CryptTool $cryptTool, $prefix) use($threemaIconContent) {
$privateKey = $cryptTool->hex2bin(Common::getPrivateKey(Constants::myPrivateKey));
$publicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::myPublicKey));
$otherPrivateKey = $cryptTool->hex2bin(Common::getPrivateKey(Constants::otherPrivateKey));
$otherPublicKey = $cryptTool->hex2bin(Common::getPublicKey(Constants::otherPublicKey));
$result = $cryptTool->encryptImage($threemaIconContent, $privateKey, $otherPublicKey);
$decryptedImage = $cryptTool->decryptImage($result->getData(), $publicKey, $otherPrivateKey, $result->getNonce());
$this->assertEquals($decryptedImage, $threemaIconContent, 'decryption of image failed');
});
}
/**
* test hex2bin and bin2hex
*/
public function testHexBin() {
$this->doTest(function(CryptTool $cryptTool, $prefix) {
$testStr = Constants::myPrivateKeyExtract;
// convert hex to bin
$testStrBin = $cryptTool->hex2bin($testStr);
$this->assertNotNull($testStrBin);
$testStrBinPhp = hex2bin($testStr);
// compare usual PHP conversion with crypt tool version
$this->assertEquals($testStrBin, $testStrBinPhp, $prefix.': hex2bin returns different result than PHP-only implementation');
// convert back to hex
$testStrHex = $cryptTool->bin2hex($testStrBin);
$this->assertNotNull($testStrHex);
$testStrHexPhp = bin2hex($testStrBin);
// compare usual PHP conversion with crypt tool version
$this->assertEquals($testStrHexPhp, $testStrHex, $prefix.': bin2hex returns different result than PHP-only implementation');
// compare with initial value
$this->assertEquals($testStrHex, $testStr, $prefix.': binary string is different than initial string after conversions');
});
}
/**
* test compare functions to make sure they are resistant to timing attacks
*/
public function testCompare() {
$this->doTest(function(CryptTool $cryptTool, $prefix) {
// make strings large enough
$string1 = str_repeat(Constants::myPrivateKey, 100000);
$string2 = str_repeat(Constants::otherPrivateKey, 100000);
echo PHP_EOL;
$humanDescr = [
'length' => 'different length',
'diff' => 'same length, different content',
'same' => 'same length, same content'
];
// test different strings when comparing
$comparisonResult = [];
foreach(array(
'length' => [$string1, $string1 . 'a'],
'diff' => [$string1, $string2],
'same' => [$string1, $string1]
) as $testName => $strings) {
for ($i=0; $i < 3; $i++) {
// test run with delay
$comparisonResult[$testName][$i] = $cryptTool->stringCompare($strings[0], $strings[1]);
// check result
if ($testName == 'length' || $testName == 'diff') {
$this->assertEquals(false, $comparisonResult[$testName][$i], $prefix.': comparison of "'.$humanDescr[$testName].' #'.$i.'" is wrong: expected: false, got '.$comparisonResult[$testName][$i]);
} else {
$this->assertEquals(true, $comparisonResult[$testName][$i], $prefix.': comparison of "'.$humanDescr[$testName].' #'.$i.'" is wrong: expected: true, got '.$comparisonResult[$testName][$i]);
}
}
}
});
}
/**
* test variable deletion
*/
public function testRemoveVar() {
$this->doTest(function(CryptTool $cryptTool, $prefix) {
foreach(array(
'hex' => Constants::myPrivateKeyExtract,
'bin' => $cryptTool->hex2bin(Constants::myPrivateKeyExtract)
) as $key => $testVar) {
// let it remove
$cryptTool->removeVar($testVar);
$this->assertEmpty($testVar, $prefix.': variable is not empty (test: '.$key.')');
$this->assertNull($testVar, $prefix.': variable is not null (test: '.$key.')');
}
});
}
private function doTest(\Closure $c) {
foreach(array(
'Salt' => CryptTool::createInstance(CryptTool::TYPE_SALT),
'Sodium' => CryptTool::createInstance(CryptTool::TYPE_SODIUM)
) as $key => $instance) {
if($instance === null) {
echo $key.": could not instance crypt tool\n";
break;
}
/** @noinspection PhpUndefinedMethodInspection */
$this->assertTrue($instance->isSupported(), $key.' not supported');
$c->__invoke($instance, $key);
}
}
}