pollita                                  Wed, 19 May 2010 21:18:16 +0000

Revision: http://svn.php.net/viewvc?view=revision&revision=299513

Log:
MFH: Add IV to openssl_(en|de)crypt()
Add openssl_cipher_iv_length()

Changed paths:
    U   php/php-src/branches/PHP_5_3/NEWS
    U   php/php-src/branches/PHP_5_3/ext/openssl/openssl.c
    U   php/php-src/branches/PHP_5_3/ext/openssl/tests/011.phpt
    U   
php/php-src/branches/PHP_5_3/ext/openssl/tests/openssl_decrypt_error.phpt

Modified: php/php-src/branches/PHP_5_3/NEWS
===================================================================
--- php/php-src/branches/PHP_5_3/NEWS	2010-05-19 20:45:56 UTC (rev 299512)
+++ php/php-src/branches/PHP_5_3/NEWS	2010-05-19 21:18:16 UTC (rev 299513)
@@ -10,6 +10,10 @@
   mcrypt_filter). (Stas)
 - Added full_special_chars filter to ext/filter (Rasmus)
 - Added backlog socket context option for stream_socket_server(). (Mike)
+- Added fifth parameter to openssl_encrypt()/openssl_decrypt()
+  (string $iv) to use non-NULL IV.
+  Made implicit use of NULL IV a warning. (Sara)
+- Added openssl_cipher_iv_length(). (Sara)

 - Changed namespaced classes so that the ctor can only be named
   __construct now. (Stas)

Modified: php/php-src/branches/PHP_5_3/ext/openssl/openssl.c
===================================================================
--- php/php-src/branches/PHP_5_3/ext/openssl/openssl.c	2010-05-19 20:45:56 UTC (rev 299512)
+++ php/php-src/branches/PHP_5_3/ext/openssl/openssl.c	2010-05-19 21:18:16 UTC (rev 299513)
@@ -99,6 +99,7 @@
 PHP_FUNCTION(openssl_digest);
 PHP_FUNCTION(openssl_encrypt);
 PHP_FUNCTION(openssl_decrypt);
+PHP_FUNCTION(openssl_cipher_iv_length);

 PHP_FUNCTION(openssl_dh_compute_key);
 PHP_FUNCTION(openssl_random_pseudo_bytes);
@@ -347,6 +348,7 @@
     ZEND_ARG_INFO(0, method)
     ZEND_ARG_INFO(0, password)
     ZEND_ARG_INFO(0, raw_output)
+    ZEND_ARG_INFO(0, iv)
 ZEND_END_ARG_INFO()

 ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_decrypt, 0, 0, 3)
@@ -354,8 +356,13 @@
     ZEND_ARG_INFO(0, method)
     ZEND_ARG_INFO(0, password)
     ZEND_ARG_INFO(0, raw_input)
+    ZEND_ARG_INFO(0, iv)
 ZEND_END_ARG_INFO()

+ZEND_BEGIN_ARG_INFO(arginfo_openssl_cipher_iv_length, 0)
+    ZEND_ARG_INFO(0, method)
+ZEND_END_ARG_INFO()
+
 ZEND_BEGIN_ARG_INFO(arginfo_openssl_dh_compute_key, 0)
     ZEND_ARG_INFO(0, pub_key)
     ZEND_ARG_INFO(0, dh_key)
@@ -408,6 +415,7 @@
 	PHP_FE(openssl_digest,				arginfo_openssl_digest)
 	PHP_FE(openssl_encrypt,				arginfo_openssl_encrypt)
 	PHP_FE(openssl_decrypt,				arginfo_openssl_decrypt)
+	PHP_FE(openssl_cipher_iv_length,	arginfo_openssl_cipher_iv_length)
 	PHP_FE(openssl_sign,				arginfo_openssl_sign)
 	PHP_FE(openssl_verify,				arginfo_openssl_verify)
 	PHP_FE(openssl_seal,				arginfo_openssl_seal)
@@ -4590,19 +4598,54 @@
 }
 /* }}} */

-/* {{{ proto string openssl_encrypt(string data, string method, string password [, bool raw_output=false])
+static zend_bool php_openssl_validate_iv(char **piv, int *piv_len, int iv_required_len)
+{
+	char *iv_new;
+
+	/* Best case scenario, user behaved */
+	if (*piv_len == iv_required_len) {
+		return 0;
+	}
+
+	iv_new = ecalloc(1, iv_required_len + 1);
+
+	if (*piv_len <= 0) {
+		/* BC behavior */
+		*piv_len = iv_required_len;
+		*piv     = iv_new;
+		return 1;
+	}
+
+	if (*piv_len < iv_required_len) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "IV passed is only %d bytes long, cipher expects an IV of precisely %d bytes, padding with \\0", *piv_len, iv_required_len);
+		memcpy(iv_new, *piv, *piv_len);
+		*piv_len = iv_required_len;
+		*piv     = iv_new;
+		return 1;
+	}
+
+	php_error_docref(NULL TSRMLS_CC, E_WARNING, "IV passed is %d bytes long which is longer than the %d expected by selected cipher, truncating", *piv_len, iv_required_len);
+	memcpy(iv_new, *piv, iv_required_len);
+	*piv_len = iv_required_len;
+	*piv     = iv_new;
+	return 1;
+
+}
+
+/* {{{ proto string openssl_encrypt(string data, string method, string password [, bool raw_output=false [, string $iv='']])
    Encrypts given data with given method and key, returns raw or base64 encoded string */
 PHP_FUNCTION(openssl_encrypt)
 {
 	zend_bool raw_output = 0;
-	char *data, *method, *password;
-	int data_len, method_len, password_len;
+	char *data, *method, *password, *iv = "";
+	int data_len, method_len, password_len, iv_len = 0;
 	const EVP_CIPHER *cipher_type;
 	EVP_CIPHER_CTX cipher_ctx;
-	int i, outlen, keylen, ivlen;
-	unsigned char *outbuf, *key, *iv;
+	int i, outlen, keylen;
+	unsigned char *outbuf, *key;
+	zend_bool free_iv;

-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|b", &data, &data_len, &method, &method_len, &password, &password_len, &raw_output) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|bs", &data, &data_len, &method, &method_len, &password, &password_len, &raw_output, &iv, &iv_len) == FAILURE) {
 		return;
 	}
 	cipher_type = EVP_get_cipherbyname(method);
@@ -4620,14 +4663,15 @@
 		key = (unsigned char*)password;
 	}

-	ivlen = EVP_CIPHER_iv_length(cipher_type);
-	iv = emalloc(ivlen);
-	memset(iv, 0, ivlen);
+	if (iv_len <= 0) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Using an empty Initialization Vector (iv) is potentially insecure and not recommended");
+	}
+	free_iv = php_openssl_validate_iv(&iv, &iv_len, EVP_CIPHER_iv_length(cipher_type));

 	outlen = data_len + EVP_CIPHER_block_size(cipher_type);
 	outbuf = emalloc(outlen + 1);

-	EVP_EncryptInit(&cipher_ctx, cipher_type, key, iv);
+	EVP_EncryptInit(&cipher_ctx, cipher_type, key, (unsigned char *)iv);
 	EVP_EncryptUpdate(&cipher_ctx, outbuf, &i, (unsigned char *)data, data_len);
 	outlen = i;
 	if (EVP_EncryptFinal(&cipher_ctx, (unsigned char *)outbuf + i, &i)) {
@@ -4650,25 +4694,28 @@
 	if (key != (unsigned char*)password) {
 		efree(key);
 	}
-	efree(iv);
+	if (free_iv) {
+		efree(iv);
+	}
 }
 /* }}} */

-/* {{{ proto string openssl_decrypt(string data, string method, string password [, bool raw_input=false])
+/* {{{ proto string openssl_decrypt(string data, string method, string password [, bool raw_input=false [, string $iv = '']])
    Takes raw or base64 encoded string and dectupt it using given method and key */
 PHP_FUNCTION(openssl_decrypt)
 {
 	zend_bool raw_input = 0;
-	char *data, *method, *password;
-	int data_len, method_len, password_len;
+	char *data, *method, *password, *iv = "";
+	int data_len, method_len, password_len, iv_len = 0;
 	const EVP_CIPHER *cipher_type;
 	EVP_CIPHER_CTX cipher_ctx;
-	int i, outlen, keylen, ivlen;
-	unsigned char *outbuf, *key, *iv;
+	int i, outlen, keylen;
+	unsigned char *outbuf, *key;
 	int base64_str_len;
 	char *base64_str = NULL;
+	zend_bool free_iv;

-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|b", &data, &data_len, &method, &method_len, &password, &password_len, &raw_input) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|bs", &data, &data_len, &method, &method_len, &password, &password_len, &raw_input, &iv, &iv_len) == FAILURE) {
 		return;
 	}

@@ -4698,14 +4745,12 @@
 		key = (unsigned char*)password;
 	}

-	ivlen = EVP_CIPHER_iv_length(cipher_type);
-	iv = emalloc(ivlen);
-	memset(iv, 0, ivlen);
+	free_iv = php_openssl_validate_iv(&iv, &iv_len, EVP_CIPHER_iv_length(cipher_type));

 	outlen = data_len + EVP_CIPHER_block_size(cipher_type);
 	outbuf = emalloc(outlen + 1);

-	EVP_DecryptInit(&cipher_ctx, cipher_type, key, iv);
+	EVP_DecryptInit(&cipher_ctx, cipher_type, key, (unsigned char *)iv);
 	EVP_DecryptUpdate(&cipher_ctx, outbuf, &i, (unsigned char *)data, data_len);
 	outlen = i;
 	if (EVP_DecryptFinal(&cipher_ctx, (unsigned char *)outbuf + i, &i)) {
@@ -4719,13 +4764,42 @@
 	if (key != (unsigned char*)password) {
 		efree(key);
 	}
-	efree(iv);
+	if (free_iv) {
+		efree(iv);
+	}
 	if (base64_str) {
 		efree(base64_str);
 	}
 }
 /* }}} */

+/* {{{ proto int openssl_cipher_iv_length(string $method) */
+PHP_FUNCTION(openssl_cipher_iv_length)
+{
+	char *method;
+	int method_len, iv_len;
+	const EVP_CIPHER *cipher_type;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &method, &method_len) == FAILURE) {
+		return;
+	}
+
+	if (!method_len) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown cipher algorithm");
+		RETURN_FALSE;
+	}
+
+	cipher_type = EVP_get_cipherbyname(method);
+	if (!cipher_type) {
+		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown cipher algorithm");
+		RETURN_FALSE;
+	}
+
+	RETURN_LONG(EVP_CIPHER_iv_length(cipher_type));
+}
+/* }}} */
+
+
 /* {{{ proto string openssl_dh_compute_key(string pub_key, resource dh_key)
    Computes shared sicret for public value of remote DH key and local DH key */
 PHP_FUNCTION(openssl_dh_compute_key)

Modified: php/php-src/branches/PHP_5_3/ext/openssl/tests/011.phpt
===================================================================
--- php/php-src/branches/PHP_5_3/ext/openssl/tests/011.phpt	2010-05-19 20:45:56 UTC (rev 299512)
+++ php/php-src/branches/PHP_5_3/ext/openssl/tests/011.phpt	2010-05-19 21:18:16 UTC (rev 299513)
@@ -8,11 +8,16 @@
 $method = "AES-128-CBC";
 $password = "openssl";

-$encrypted = openssl_encrypt($data, $method, $password);
-$output = openssl_decrypt($encrypted, $method, $password);
+$ivlen = openssl_cipher_iv_length($method);
+$iv    = '';
+srand(time() + ((microtime(true) * 1000000) % 1000000));
+while(strlen($iv) < $ivlen) $iv .= chr(rand(0,255));
+
+$encrypted = openssl_encrypt($data, $method, $password, false, $iv);
+$output = openssl_decrypt($encrypted, $method, $password, false, $iv);
 var_dump($output);
-$encrypted = openssl_encrypt($data, $method, $password, true);
-$output = openssl_decrypt($encrypted, $method, $password, true);
+$encrypted = openssl_encrypt($data, $method, $password, true, $iv);
+$output = openssl_decrypt($encrypted, $method, $password, true, $iv);
 var_dump($output);
 ?>
 --EXPECT--

Modified: php/php-src/branches/PHP_5_3/ext/openssl/tests/openssl_decrypt_error.phpt
===================================================================
--- php/php-src/branches/PHP_5_3/ext/openssl/tests/openssl_decrypt_error.phpt	2010-05-19 20:45:56 UTC (rev 299512)
+++ php/php-src/branches/PHP_5_3/ext/openssl/tests/openssl_decrypt_error.phpt	2010-05-19 21:18:16 UTC (rev 299513)
@@ -8,8 +8,11 @@
 $method = "AES-128-CBC";
 $password = "openssl";
 $wrong = "wrong";
+$iv = str_repeat("\0", openssl_cipher_iv_length($method));

 $encrypted = openssl_encrypt($data, $method, $password);
+var_dump($encrypted); /* Not passing $iv should be the same as all-NULL iv, but with a warning */
+var_dump(openssl_encrypt($data, $method, $password, false, $iv));
 var_dump(openssl_decrypt($encrypted, $method, $wrong));
 var_dump(openssl_decrypt($encrypted, $wrong, $password));
 var_dump(openssl_decrypt($wrong, $method, $password));
@@ -21,6 +24,10 @@
 var_dump(openssl_decrypt($encrypted, $method, array()));
 ?>
 --EXPECTF--
+
+Warning: openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended in %s on line %d
+string(44) "yof6cPPH4mLee6TOc0YQSrh4dvywMqxGUyjp0lV6+aM="
+string(44) "yof6cPPH4mLee6TOc0YQSrh4dvywMqxGUyjp0lV6+aM="
 bool(false)

 Warning: openssl_decrypt(): Unknown cipher algorithm in %s on line %d
-- 
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to