Using phpseclib AES to encrypt string - php

Ok so here's the sample code from the page http://phpseclib.sourceforge.net/crypt/examples.html
<?php
include('Crypt/AES.php');
include('Crypt/Random.php');
$cipher = new Crypt_AES(); // could use CRYPT_AES_MODE_CBC
// keys are null-padded to the closest valid size
// longer than the longest key and it's truncated
//$cipher->setKeyLength(128);
$cipher->setKey('abcdefghijklmnop');
// the IV defaults to all-NULLs if not explicitly defined
$cipher->setIV(crypt_random_string($cipher->getBlockLength() >> 3));
$size = 10 * 1024;
$plaintext = str_repeat('a', $size);
echo $cipher->decrypt($cipher->encrypt($plaintext));
?>
Before anything, why is the line //$cipher->setKeyLength(128); is being commented out?
And if I want to set my key to '1234568790', is there anything I should do? Because it's much shorter than they length of the key in the example above (abcdefghijklmnop).
And finally if the plaintext that I want to encrypt is short, something like "my name is james bond", is there anything extra that I should do? Because from the code above, it seems the plaintext's length should be 10 x 1024. (Why is that?)

Before anything, why is the line //$cipher->setKeyLength(128); is being commented out?
Say you pass a 17 byte key to phpseclib via setKey. What do you suppose ought to happen? In the 1.0 and 2.0 versions if setKeyLength isn't called then the key is null-padded to 24 bytes (eg. 192-bits). But if setKeyLength is called it'll be truncated to 16 bytes. In the master branch (which, as I understand it, will eventually become the 3.0 branch), an exception is thrown if the key length isn't valid. In that version it may still be desirable to call setKeyLength if, for example, if you want to set the key length as being 128-bits in one part of the code and then actually set the key in another part of the code. ie. you could do the length checking or phpseclib could do the length checking.
And if I want to set my key to '1234568790', is there anything I should do? Because it's much shorter than they length of the key in the example above (abcdefghijklmnop).
1234568790 isn't technically long enough to be a valid key. It's 80 bytes long. The smallest key AES supports is 128-bits. Maybe you should consider doing something like setPassword('1234568790') instead of setKey('1234568790'). setPassword will use a key-derivation function to generate a key from the password.
In phpseclib 1.0 and 2.0 setKey will, none-the-less, accept that as a key. It'll just null pad the key. The master branch otoh will throw an exception since the key isn't actually long enough.
And finally if the plaintext that I want to encrypt is short, something like "my name is james bond", is there anything extra that I should do? Because from the code above, it seems the plaintext's length should be 10 x 1024. (Why is that?)
10 * 1024 is just an example. phpseclib, by default, will use PKCS7 padding to make strings long enough. eg. your string is 21 bytes long (if I counted correctly). So if you call $cipher->encrypt('my name is james bond') then chr(11) will be appending to the plaintext 11 times, by phpseclib and then the string will be encrypted. This is because block algorithms need to have the the plaintext be a multiple of the block length. So the ciphertext will be 32 bytes long. When you then call $cipher->decrypt('...') you'll get the orig 21-byte string back. I could elaborate further on PKCS7 padding, but I think I've answered your immediate question.

Related

encryption result differs in PHP and Node.js

I'm encrypting the string "aaaaaaaaaaaaaaaa" (a 16 byte UTF8 string) using AES 128 CBC with a blank iv and key (16 0's) and getting different results
in PHP:
echo base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128,pack("H*", "00000000000000000000000000000000"),"aaaaaaaaaaaaaaaa",MCRYPT_MODE_CBC,pack("H*", "00000000000000000000000000000000")))
returns "kmwP6gWv1l9ZMdKanGs/nA=="
in Node.js:
let cipher = require('crypto').createCipheriv('aes-128-cbc',Buffer.alloc(16),Buffer.alloc(16))
console.log(cipher.update('aaaaaaaaaaaaaaaa','utf8','base64') + cipher.final('base64'))
returns "kmwP6gWv1l9ZMdKanGs/nHeUidae8Z4dK0HU7p2z+1c="
the first bit (bolded) is identical to PHP, but the PHP value has an extra 'A=' and then Node value has a whole extra 'HeUidae8Z4dK0HU7p2z+1c'
I'll admit that I'm pretty shaky on what's going on here - what am I missing here?
edit ... but not so shaky that I don't understand that what I'm doing here isn't particularly secure. Don't worry about whether this is the 'right' way to do encryption - please focus on the fact that the results ought to line up.
edit2 - I can, however, get close using hex rather than base64 -
PHP: 926c0fea05afd65f5931d29a9c6b3f9c
Node: 926c0fea05afd65f5931d29a9c6b3f9c779489d69ef19e1d2b41d4ee9db3fb57
the second chunk of Node hex is returned by the .final method, and I don't understand what it's for.
AES works on a block size of 16 bytes. You are encrypting the string "hello world", which is 11 bytes long in UTF8. Thus, padding is used to increase the length of your string to 16 bytes.
Node, and as it should, uses PKCS5 Padding to pad your plain-text to 16 bytes and then encrypts it.
mcrypt, as it shouldn't, uses zero bytes to pad your plain-text. mcrypt is deprecated and has been abandoned for a decade.
Because your two padding schemes are different, the plain-text is actually different before we even get to the point where we apply AES.
My advice: use the openssl_* functions in PHP instead. And don't use a static IV. Using a static IV makes your program vulnerable to some of the same vulnerabilities as ECB mode, which is not good!

AES-128 output bytes in codeigniter 3.0

According to the codeigniter documentation, the encryption key for an AES-128 MUST be set to 128bits/16bytes (16 characters) random string. I tested my output using var_dump function
(assuming I have set a 16 character key to the config file and already loaded the library)
My code:
$plain_text = 'Hello World';
$encrypted = $this->encryption->encrypt($plain_text);
var_dump($encrypted);
var_dump($this->encryption->decrypt($encrypted));
die();
The output
string(176) (A 176 character encrypted data appears)
string(11) Hello World
Sorry I'm not able to put the exact encrypted data but I believe is doesn't make sense to show it.
I would like to know if the encryption output is perfectly normal output considering its size(176 bytes)
AES is a block cipher so padding is to be expected.
A block cipher (as opposed to a stream cipher) can only operate on a pre-determined size and must pad a smaller plaintext in order reach that size before it can operate on it.
With that said, 176 bytes (11 x 16-byte blocks) is a lot bigger than 11 bytes and my guess is that part of the overhead may be because codeigniter encodes its own information (i.e. structured data such as type of variable, length of string, etc.) before encrypting it.
Since you are able to decrypt the ciphertext using the same key, it's probably not a bug in the implementation and is working as intended, though I can't speak for whether it was implemented properly and securely.

What is the password parameter to openssl_encrypt?

The PHP documentation for the openssl_encrypt functions states
string openssl_encrypt ( string $data , string $method , string
$password [, int $options = 0 [, string $iv = "" ]] )
Can somebody help me understand what the argument named $password is?
An answer could include a confirmation or rejection of the idea, that besides named $password the parameter indeed is used as the key for the encryption.
What is the password parameter to openssl_encrypt? Is it a password string (with only printable characters) or is it a key (with non-prinatable characters and ASCII-Z terminators)?
Explanation
I am stuck with the documention of PHP's openssl_encrypt. Being a nice guy and trying to do the "RTM" I cannot make much sense with the imho unsatisfying documentation.
The problem is that for me there is a difference between a password and a key when it comes to encryption. A key is directly the parameter used for encryption and hence necessarily of a specific size - "the keylength" - i.e. 128/256/512 bits depending on the cipher and keylength desired. A password on the other hand is a to my understanding a human readable string entered via the keyboard which may in difference be of any length and which is before being used to encrypt first converted into a key.
hence schematic difference:
password => key => encryption
key => encryption
Unfortunatelly in the PHP openssl_encrypt documentation I cannot find any information how to use a key. The only thing suggested is a parameter "password".
Can anybody give me a glue how a key can be used?
Surely I donot want to enter the key as the password parameter as I want a specific key to be used in encryption. I do not want this key to be simply missunderstood as a parameter and serve for another key being calculated from my "key mistaken as password".
Additionally the mistery continues looking at the documention regarding the initialization vector parameter in the same openssl_encrypt function. It simply states:
iv A non-NULL Initialization Vector.
and that iv should be a string. Given that the iv is normaly a binary data of a certain length and for instance a string terminating \0 (hex 0x00) can be occuring inside I am puzzled what format is desired.
In essence I feel very much left alone with the PHP documentation which also states
WARNING This function is currently not documented; only its argument
list is available.
Update
I did some testing, and "trying around" to help me figure out what the password parameter is.
using this code:
$pass="0123456789abcdefghijklmnob";
$iv="0123456789abcdef";
echo "using $pass results:\n";
echo openssl_encrypt("test secret string", 'aes-128-cbc', $pass,NULL,$iv);
I get this result:
using 0123456789abcdefghijklmnob results:
XjEeaLucY38Y6XEUceYMYKTebR4kOp3s727ipMl5KNc=
Then changing the length of the "password" parameter:
$pass="0123456789abcdefg"; //hijklmnob";
$iv="0123456789abcdef";
echo "using $pass results:\n";
echo openssl_encrypt("test secret string", 'aes-128-cbc', $pass,NULL,$iv);
I get still the same encrypted code:
using 0123456789abcdefg results:
XjEeaLucY38Y6XEUceYMYKTebR4kOp3s727ipMl5KNc
It seems by way of testing yet not by way of being informed by the documentation that
the password is indeed only considered up to the first 16 bytes which seem to be the 128 bit that would be the key.
It frightens me that in such a sensitive function (used for encryption) the documentation is bad and excessive input of one poorly documented parameter is not even warned about. Also I am quite convinced that password should rather be named key as it seems those 16 bytes do directly represent the key.
The $password parameter is indeed the key. The key size depends on the AES mode you're using (as you know).
As you noted in your update, for AES-128, only the first 16 characters/bytes count for the key. For AES-256, it would be the first 32 characters.
When one uses the openssl_encrypt() and openssl_decrypt() functions, one can simply pass a 32 character human-readable password for the $password/key parameter.
For example, my input for the password parameter might be $password = "This is 32 characters long......";
Most people don't like having to write up a plaintext password that is a fixed length, so they might compute a hash and truncate it to the correct length. They would use this hash as their encryption key/password.
For example, I could compute an MD5 hash of a password/phrase of any length that I would like and then use that as my AES password/key:
$plaintextPass = 'This is my password. This password is not exactly 32 bytes, but that is okay because I am hashing this.';
$password = hash('md5', $plaintextPass); /* the encryption key */
With that in place, no matter what plaintext password I use, I can have a valid 32 character/byte string as my encryption key/password. This does reduce the entropy of the encryption key/password, though, because a normal 32 character string has a larger key space than an MD5 hash output (256 possibilities per byte vs. 16 possibilities per byte); however, 16^32 is still certainly out of the range of brute force.
Off topic: In some of my personal programs, I have been working on using 32 randomly generated bytes with values between 0-255. This would make the entropy of the encryption key 256^32 which is infeasible to bruteforce. This will also be resistant to dictionary attacks because the 32-bytes are randomly generated using a cryptographically secure pseudo-random number generator (CSPRNG).
So, to sum this all up, yes, the $password parameter is indeed the key used for encryption. I agree with you that is should be written as $key in the documentation, but oh well. The password/key which you select for encryption can be humanly readable as long as it satisfies the length requirements of the hashing key. To satisfy these length requirements, one can hash a human-readable/plaintext password and use the hash of that password as the key ($password parameter), but the human-readable password should still be long and unique.
The documentation is not very clear, however this comment on the openssl_encrypt() PHP doc page really helped me when I was trying to understand the function. The comment provides an example as well as useful information. To direct quote the author of the comment:
Because the password parameter documented here is not the password.
...
It is the key!
[Comment minorly edited]

Can memcached keys contain spaces?

I seem to have problems with memcached keys that have spaces, though I can't pinpoint exactly what.
A more explicit answer (referred to by Dustin, but not referenced):
Keys
Data stored by memcached is identified with the help of a key. A key
is a text string which should uniquely identify the data for clients
that are interested in storing and retrieving it. Currently the
length limit of a key is set at 250 characters (of course, normally
clients wouldn't need to use such long keys); the key must not include
control characters or whitespace.
Source: protocol.txt (Specific Version)
No. Memcached keys cannot contain spaces.
Memcached clients seem not to validate keys in favor of performance.
What I usually do is create a method named createWellFormedKey($key) and pass the returned result to the set() and get() methods of the memcached client.
I do not use md5 and sha1 hashing unless the base64 version exceeds 250 characters. This is because md5 and sha1 are more expensive operations performance wise.
A sample PHP code looks like this:
/**
* Generates a well formed key using the following algorithm:
* 1. base64_encode the key first to make sure all characters are valid
* 2. Check length of result, less than 250 then return it
* 3. Length of result more than 250 then create a key that is md5($validKey).sha1($validKey).strlen($validKey)
*/
private function createWellFormedKey($key) {
// Get rid of all spaces, control characters, etc using base64
$validKey = base64_encode($key);
$validKeyLength = strlen($validKey);
// 250 is the maximum memcached can handle
if (strlen($validKey) < 250) {
return $validKey;
}
$validKey = md5($validKey).sha1($validKey).$validKeyLength;
return $validKey;
}
At the moment I'm playing around with memcached with PHP and the Problem described IMHO can be easily solved by using hash algorithms like md5 and sha1 (or any other).
I'm using a combination of a md5-hash, sha1-hash and sha256 + the length of the key given.
Obviously this method can be reduced to two hash-methods + length of the key, so you can easily avoid using space or other characters that should not be in the key.
In my opinion the hash-collions are avoided because the chance that both hash algorithms have a collision is nearly 0. By additionally using the key length in the key the problem of a collision is 0.
Applications using the memcached binary protocol can use whitespace-containing keys, though there is still a 250-byte length limit.

Which Blowfish algorithm is the most 'correct'?

I'm running Windows Server 2k8 (maybe that's half the problem?) Anyway, I'm getting different values out of different Blowfish modules in various languages. Is there one which can be relied upon as the standard?
For the following examples, assume the key is password and the plaintext 12345678.
a. The Online Encrypt Tool with Algorithm set to Blowfish mode ECB and Base64 Encode the output checked gives 2mADZkZR0VM=. I've been using this a my reference point, wise or otherwise.
b. The following Perl code uses Crypt::ECB and MIME::Base64
use MIME::Base64;
use Crypt::ECB;
$crypt = Crypt::ECB->new;
$crypt->padding(PADDING_NONE);
$crypt->cipher('Blowfish') || die $crypt->errstring;
$crypt->key('password');
$enc = $crypt->encrypt("12345678");
print encode_base64($enc);
This outputs 2mADZkZR0VM= with PADDING_NONE (which compares well with 'a.' above). However, when padding is set to PADDING_AUTO it outputs 2mADZkZR0VOZ5o+S6D3OZw== which is, in my mind at least, a bug as the plaintext is 8 characters long and in no need of padding.
c. If I use Crypt::Blowfish as below
#! c:\perl\bin
use Crypt::Blowfish;
use MIME::Base64;
my $key;
my $plaintext;
$key = "password";
$plaintext = "12345678";
my $cipher = new Crypt::Blowfish $key;
my $ciphertext = $cipher->encrypt($plaintext);
my $encoded = encode_base64( $ciphertext );
print $encoded;
then I get 2mADZkZR0VM= which matches 'a.' above. Trouble with this module though is that one has to segment things into 8 byte chunks for encoding; it has no chunker of its own.
d. If I use the source at http://linux.die.net/man/3/bf_ecb_encrypt (which I did for a recent PHP ext project) then I get the same answer as 'a.'. I'm inclined to trust this code the most as it's in use in SSLeay and OpenSSL.
e. The BlowfishEx.EXE in DI Management's Blowfish: a Visual Basic version with PKCS#5 padding gives 2mADZkZR0VOZ5o+S6D3OZw== which is the same as the Crypt::ECB results with PADDING_AUTO. With Padding set to None I get 2mADZkZR0VM= which matches 'a.'
I've partly answered my own question writing this: looks like I just have to modify DI Management's code for the VB6 project. And maybe suggest the same to the writer of Crypt::ECB.
But the question remains: is there a trustworthy blowfish reference platform?
Your problem doesn't seem to be with whether they implement the Blowfish algorithm correctly. In the "no padding" variant, they all seem to produce identical results.
That leaves a question of whether an 8-byte input should be padded at all. The answer to this is pretty simple: PKCS #7 requires that there is always at least one byte of padding added to the input. The reason for this is simple: on the receiving end, there needs to be an unambiguous way to remove the padding. In the case of PKCS#7, the value of the padding bytes is always the number of bytes of padding that needs to be stripped off by the recipient.
So, when you receive material that's been padded with PKCS7, you decrypt the block, then look at the last byte in the decrypted output, and remove that many bytes from the block. Without a block of padding on the end, the receiver would normally look at the last byte of actual data, and whatever value that byte happened to contain, strip off that many bytes (though it should probably tell you there's a problem if the number is larger than 8). In any case, to assure correct operation, there must always be at least one byte of padding. In your case, that means the input gets expanded out to 16 bytes instead of 8.

Categories