Skip to content

Commit 748ba96

Browse files
committed
Initial commit
0 parents  commit 748ba96

10 files changed

+443
-0
lines changed

Autoloader.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace PHPCrypt;
4+
5+
class Autoloader
6+
{
7+
public static function load($class_name)
8+
{
9+
if (class_exists($class_name)) {
10+
return TRUE;
11+
}
12+
13+
$file_name = self::convertClassToPath($class_name);
14+
15+
$include_paths = explode(PATH_SEPARATOR, get_include_path());
16+
17+
foreach ($include_paths as $include_path) {
18+
19+
$full_path = $include_path . DIRECTORY_SEPARATOR . $file_name;
20+
21+
if (file_exists($full_path)) {
22+
include_once $file_name;
23+
return TRUE;
24+
}
25+
}
26+
27+
return FALSE;
28+
}
29+
30+
public static function install()
31+
{
32+
spl_autoload_register('self::load');
33+
}
34+
35+
public static function convertClassToPath($class_name)
36+
{
37+
$path = str_replace('_', DIRECTORY_SEPARATOR, $class_name);
38+
$path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
39+
$path .= '.php';
40+
41+
return $path;
42+
}
43+
44+
public static function registerIncludePath()
45+
{
46+
set_include_path(
47+
get_include_path() . PATH_SEPARATOR . realpath(dirname(__FILE__) . '/..')
48+
);
49+
}
50+
}

Example.php

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // vim:ts=4:sts=4:sw=4:et:
2+
3+
require_once 'Autoloader.php';
4+
\PHPCrypt\Autoloader::install();
5+
\PHPCrypt\Autoloader::registerIncludePath();
6+
7+
$phpcrypt = new \PHPCrypt\Simple(
8+
'6zp4y5vnUQpfEroWI6dMq5lC46F5Dmqa4NDcM1W4u2k=',
9+
'RJikKksPg3UmqgQPXBwCmcSOMHQn0iOtQAFcfRcQOTU='
10+
);
11+
12+
$ciphertext = $phpcrypt->encrypt('Foobar');
13+
printf("Ciphertext: %s\n", $ciphertext);
14+
15+
$decrypted = $phpcrypt->decrypt($ciphertext);
16+
printf("Decrypted: %s\n", $decrypted);

README

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
\PHPCrypt\ Simple Encryption Library For PHP 5.3+
2+
Version 1.0
3+
Written by Bryan C. Geraghty
4+
5+
Introduction
6+
============
7+
A fellow programmer/friend whom I know from my local PHP user group was
8+
tasked with encrypting some data in an application and started searching
9+
Google for a good solution. After finding something promising, she sent
10+
what she had found to me asking for a review, knowing that I have a deep
11+
intrest and some backgroud in cryptography. Upon reviewing the code, I
12+
found some critical problems. As I wrote my response and explained the
13+
fixes that would need to be implemented, I realized that these are not
14+
things that I could expect anyone without some cryptanalysis experience to
15+
to implement correctly.
16+
17+
So, I set out to create this libray in which I have made all of the hard
18+
decisions for you. There are better contructions out there but they come
19+
with some baggage that is more difficult to handle and so they are not
20+
really suited for "simple" libraries. In this library's current design,
21+
all you must do is run a couple of commands to generate a couple of keys
22+
that will be stuck in your code. Then, you just call the encrypt() and
23+
decrypt() functions when you need them.
24+
25+
My goal with this libary is to improve the baseline for encryption in PHP.
26+
27+
Cryptgraphic Details
28+
====================
29+
For most cryptographic functionality, this libary makes use of the Mcrypt
30+
extension. The extension has been around for over a decade but is not
31+
include by default on many distributions. If it is not, you will need to
32+
ensure that you have installed it.
33+
34+
The cryptographic primitives & practices behind this library are:
35+
36+
* 256-bit Rijndael block cipher (256-bit key AND block size; not AES compatible)
37+
* CBC block cipher mode
38+
* HMAC-SHA-256
39+
* Encrypt-Then-MAC construction
40+
* Constant-time MAC comparison
41+
42+
Example
43+
=======
44+
The goal of this libary was secure defaults and simplicity. There are
45+
five simple steps to follow to encrypt and decrypt data (see the
46+
Example.php script to see it all in one place):
47+
48+
1) Find a Linux machine that has been running for more than 15 minutes and
49+
generate an encryption key with the following command:
50+
51+
head -c 32 /dev/urandom | base64
52+
53+
The output will look something like (IT MUST BE UNIQUE!):
54+
6zp4y5vnUQpfEroWI6dMq5lC46F5Dmqa4NDcM1W4u2k=
55+
56+
2) Run the command a second time to generate your MAC key:
57+
58+
> head -c 32 /dev/urandom | base64
59+
RJikKksPg3UmqgQPXBwCmcSOMHQn0iOtQAFcfRcQOTU=
60+
61+
3) Given the above inputs, add the following code to your bootstrap:
62+
63+
require_once 'PHPCrypt/Autoloader.php';
64+
\PHPCrypt\Autoloader::install();
65+
66+
$phpcrypt = new \PHPCrypt\Simple(
67+
'6zp4y5vnUQpfEroWI6dMq5lC46F5Dmqa4NDcM1W4u2k=',
68+
'RJikKksPg3UmqgQPXBwCmcSOMHQn0iOtQAFcfRcQOTU=',
69+
);
70+
71+
4) Call the encrypt() function:
72+
73+
$ciphertext = $phpcrypt->encrypt('Foobar');
74+
75+
This will generate output similar to this:
76+
DrZ/CdwAxdia1eO4A04jptl+hBpT57xI8FOEiNMSZE2ol0Pk1xDN6IY5VYi9s7wY9q6ubboF7lPnyQRTkx8y5w==|floT1+Ha5GHuO36+wie9rcNh+cQjRDJ5+OegF3mToew=
77+
78+
As you can see, the output is base64 encoded for you and the MAC is
79+
appended automatically, so you don't have to worry about anything.
80+
Just feed plaintext in, and encoded & signed ciphertext comes out.
81+
82+
Also, keep in mind that the IV for each encryption is randomized, so
83+
encrypting the same value will produce different ciphertexts. The
84+
encrypt() function accepts an IV as an optional second argument if you
85+
need to manually control it. Most people should not use it.
86+
87+
5) Call the decrypt() function:
88+
89+
$ciphertext = $phpcrypt->decrypt(
90+
'DrZ/CdwAxdia1eO4A04jptl+hBpT57xI8FOEiNMSZE2ol0Pk1xDN6IY5VYi9s7wY9q6ubboF7lPnyQRTkx8y5w==|floT1+Ha5GHuO36+wie9rcNh+cQjRDJ5+OegF3mToew='
91+
);
92+
93+
This will produce the value, 'Foobar'.
94+
95+
That's it!
96+
==========
97+
I this is better than what we have now.

Simple.php

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php // vim:ts=4:sts=4:sw=4:et:
2+
3+
/**
4+
* Simple Encryption Library For PHP 5.3+
5+
*
6+
* PHP Version 5.3
7+
*
8+
* @category PHP
9+
* @package PHPCrypt
10+
* @author Bryan C. Geraghty <[email protected]>
11+
* @copyright 2013 Bryan C. Geraghty
12+
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL
13+
* @link https://github.com/archwisp/PHPCrypt
14+
*/
15+
16+
namespace PHPCrypt;
17+
18+
class Simple
19+
{
20+
private $_encryptionAlgorithm = MCRYPT_RIJNDAEL_256;
21+
private $_mode = MCRYPT_MODE_CBC;
22+
private $_macAlgorithm = 'sha256';
23+
private $_macByteCount = 32;
24+
private $_encryptionKey;
25+
private $_macKey;
26+
27+
public function __construct($encryptionKey, $macKey) {
28+
$this->_encryptionKey = base64_decode($encryptionKey);
29+
$this->_macKey = base64_decode($macKey);
30+
}
31+
32+
public function encrypt($plaintext, $iv = null) {
33+
if (is_null($iv)) {
34+
$iv = $this->generateIv();
35+
}
36+
37+
$decodedIv = base64_decode($iv);
38+
$paddedPlaintext = $this->padWithPkcs7($plaintext);
39+
40+
$ciphertext = $decodedIv . mcrypt_encrypt($this->_encryptionAlgorithm,
41+
$this->_encryptionKey, $paddedPlaintext, $this->_mode, $decodedIv
42+
);
43+
44+
$signature = hash_hmac($this->_macAlgorithm, $ciphertext, $this->_macKey, true);
45+
46+
$output = base64_encode($ciphertext) . '|' . base64_encode($signature);
47+
48+
return $output;
49+
}
50+
51+
public function decrypt($signedCiphertext) {
52+
$signedCiphertextParts = explode('|', $signedCiphertext);
53+
54+
If (count($signedCiphertextParts) !== 2) {
55+
Throw new \RuntimeException('Invalid signature');
56+
}
57+
58+
$decodedCiphertext = base64_decode($signedCiphertextParts[0]);
59+
60+
$signature = hash_hmac($this->_macAlgorithm,
61+
$decodedCiphertext, $this->_macKey, true
62+
);
63+
64+
// This should really be a constant-time comparison to prevent timing
65+
// attacks (see http://blog.jasonmooberry.com/2010/10/constant-time-string-comparison/)
66+
if (!$this->_compareMac($signature, base64_decode($signedCiphertextParts[1]))) {
67+
Throw new \RuntimeException('Invalid signature');
68+
}
69+
70+
$iv = substr($decodedCiphertext, 0, $this->getBlockSize());
71+
$ciphertext = substr($decodedCiphertext, $this->getBlockSize());
72+
73+
$paddedPlaintext = mcrypt_decrypt($this->_encryptionAlgorithm,
74+
$this->_encryptionKey, $ciphertext, $this->_mode, $iv);
75+
76+
return $this->trimPkcs7($paddedPlaintext);
77+
}
78+
79+
public function generateIv() {
80+
return base64_encode(mcrypt_create_iv($this->getBlockSize(), MCRYPT_DEV_URANDOM));
81+
}
82+
83+
public function generateKey() {
84+
return base64_encode(mcrypt_create_iv($this->getKeySize(), MCRYPT_DEV_URANDOM));
85+
}
86+
87+
private function getBlockSize() {
88+
return mcrypt_get_iv_size($this->_encryptionAlgorithm, $this->_mode);
89+
}
90+
91+
private function getKeySize() {
92+
return mcrypt_get_key_size($this->_encryptionAlgorithm, $this->_mode);
93+
}
94+
95+
private function padWithPkcs7($plaintext) {
96+
$block_size = $this->getBlockSize();
97+
98+
if ($block_size > 255) {
99+
throw new RuntimeException('PKCS7 padding is only well defined for block sizes smaller than 256 bits');
100+
}
101+
102+
$pad_length = ($block_size - (strlen($plaintext) % $block_size));
103+
104+
return $plaintext . str_repeat(chr($pad_length), $pad_length);
105+
}
106+
107+
private function trimPkcs7($plaintext) {
108+
$pad_char = substr($plaintext, -1);
109+
$pad_length = ord($pad_char);
110+
111+
if (substr($plaintext, -$pad_length) !== str_repeat($pad_char, $pad_length)) {
112+
throw new \RuntimeException('Invalid pad value');
113+
}
114+
115+
return substr($plaintext, 0, -$pad_length);
116+
}
117+
118+
/**
119+
* Constant-time comparison function
120+
*
121+
* Stolen and adapted from:
122+
* https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time
123+
*
124+
* DON'T MESS WITH THIS FUNCTION
125+
*
126+
* returns boolean true on match, otherwise, false
127+
*/
128+
private function _compareMac($a, $b) {
129+
$result = "\x00";
130+
131+
for ($i = 0; $i < $this->_macByteCount; $i++) {
132+
$result |= substr($a, $i, 1) ^ substr($b, $i, 1);
133+
}
134+
135+
/* \x00 if equal, nonzero otherwise */
136+
$return = ($result === "\x00") ? true : false;
137+
138+
return $return;
139+
}
140+
}

SimpleTest.php

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php // vim:ts=4:sts=4:sw=4:et:
2+
3+
namespace PHPCrypt;
4+
5+
class SimpleTest extends \PHPUnit_Framework_TestCase
6+
{
7+
private $_instance;
8+
private $_iv = '3e5VO09Oslbw/sskJPdloizTQ/2iz8Icyo+VT3PxYWM=';
9+
private $_encryptionKey = 'nXA5gXtlOgHgxl6EZTfkfDmIzWaRqxZ1rq7DRNCIQ/Q=';
10+
private $_macKey = 'K9iPmOMowXUvcQTd7ehfcxvvHd4OtzyztQp+wuQwb6U=';
11+
12+
public function setUp() {
13+
$this->_instance = new \PHPCrypt\Simple($this->_encryptionKey, $this->_macKey);
14+
}
15+
16+
public function testEncrypt() {
17+
$ciphertext = $this->_instance->encrypt('FooBar ', 'lAuCU7ft5tnHPKWRjF1IKV4J6V9/eCGQIisHZfuqMtY=');
18+
19+
$this->assertEquals(
20+
'lAuCU7ft5tnHPKWRjF1IKV4J6V9/eCGQIisHZfuqMta/3gJg7IhHgxvaUY6isRyPcRq+x/rQfnPxY/A1XqY2nA==|6MBBCKBbYc+aWLpnk1MQVW2jM5Jnz5oHfXnDriyIL9Q=',
21+
$ciphertext
22+
);
23+
}
24+
25+
public function testDecrypt() {
26+
$plaintext = $this->_instance->decrypt(
27+
'lAuCU7ft5tnHPKWRjF1IKV4J6V9/eCGQIisHZfuqMta/3gJg7IhHgxvaUY6isRyPcRq+x/rQfnPxY/A1XqY2nA==|6MBBCKBbYc+aWLpnk1MQVW2jM5Jnz5oHfXnDriyIL9Q='
28+
);
29+
30+
$this->assertEquals('FooBar ', $plaintext);
31+
}
32+
33+
/**
34+
/* This function encrypts the same string with randomized IVs and
35+
/* flips a single bit of the ciphertext
36+
*/
37+
public function invalidCiphertextData() {
38+
$this->setUp();
39+
$invalidCiphertexts = array();
40+
41+
for ($x = 0; $x < 100; $x++) {
42+
$signedCiphertext = $this->_instance->encrypt('Randomize this with new IVs');
43+
list($encodedCiphertext, $encodedSignature) = explode('|', $signedCiphertext);
44+
$ciphertext = base64_decode($encodedCiphertext);
45+
$randomByte = rand(1, strlen($ciphertext));
46+
$mask = str_repeat("\x00", $randomByte -1) . "\x01" . str_repeat("\x00", strlen($ciphertext) - $randomByte);
47+
48+
// SANITY CHECK: If this mask is removed, this test should fail every
49+
// single run because the ciphertext should match.
50+
$invalidCiphertext = $ciphertext ^ $mask;
51+
52+
// printf("Ciphertext: %s\n", bin2hex($ciphertext));
53+
// printf("Mask: %s\n", bin2hex($mask));
54+
// printf("Invalid Ciphertext: %s\n", bin2hex($invalidCiphertext));
55+
56+
$encodedInvalidCiphertext = base64_encode($invalidCiphertext);
57+
58+
$invalidCiphertexts[] = array($encodedInvalidCiphertext . '|' . $encodedSignature);
59+
}
60+
61+
return $invalidCiphertexts;
62+
}
63+
64+
/**
65+
* @dataProvider invalidCiphertextData
66+
*/
67+
public function testDecryptInvalidCiphertext($signedCiphertext) {
68+
$this->setExpectedException('Exception', 'Invalid signature');
69+
$plaintext = $this->_instance->decrypt($signedCiphertext);
70+
}
71+
72+
public function testEncryptAndDecrypt() {
73+
$plaintext = 'Something';
74+
$ciphertext = $this->_instance->encrypt('Something');
75+
$this->assertEquals($plaintext, $this->_instance->decrypt($ciphertext));
76+
}
77+
78+
public function testEncryptInvalidIvLength() {
79+
$this->setExpectedException('Exception');
80+
81+
$ciphertext = $this->_instance->encrypt(
82+
'FooBar ', $this->_encryptionKey, $this->macKey, 'Short IV');
83+
84+
$this->assertEquals('This should never execute', base64_encode($ciphertext));
85+
}
86+
87+
public function testGenerateIv() {
88+
$iv = $this->_instance->generateIv();
89+
$secondIv = $this->_instance->generateIv();
90+
$this->assertNotEquals($iv, $secondIv);
91+
}
92+
}

bin/composer.phar

913 KB
Binary file not shown.

0 commit comments

Comments
 (0)