c++ to php translation, decryption function - php

So, I'm trying to translate a piece of C++ code to php.
The C++ is from a external source, and my knowledge of both C++ and decryption is lacking, to say the least.
The source C++ is:
void parser_t::decrypt(buffer_t &replay_data, const unsigned char *key_data) {
/*\
|*| Performs an in place decryption of the replay using the given key.
|*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
|*| -# Set the variable previous_block (with size of 16 bytes) to 0
|*| -# Decrypt a block with the given key
|*| -# XOR the block with the previous (decrypted) block
|*| -# Go back to step 2, until there are no more blocks.
\*/
BF_KEY key = {{0}};
BF_set_key(&key, 16, key_data);
const int block_size = 8;
size_t padding_size = (block_size - (replay_data.size() % block_size));
if (padding_size != 0) {
size_t required_size = replay_data.size() + padding_size;
replay_data.resize(required_size, 0);
}
unsigned char previous[block_size] = {0};
for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
unsigned char decrypted[block_size] = { 0 };
BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
std::copy_n(decrypted, block_size, previous);
std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
}
if (padding_size != 0) {
size_t original_size = replay_data.size() - padding_size;
replay_data.resize(original_size, 0);
}
}
What I have got so far:
function decrypt($data){ // $data is a encrypted string
$key = array(0xDE, <.....>, 0xEF); // (16 entries in the array)
//BF_KEY key = {{0}}; // ?
//BF_set_key(&key, 16, key_data); // ?
$block_size = 8;
$padding_size = ($block_size - (strlen($data) % $block_size));
if ($padding_size != 0) {
$required_size = strlen($data) + $padding_size;
//replay_data.resize(required_size, 0);
// Seems unnecessary in php? string lengths are pretty dynamic.
}
$keyString = '';
for($i = 0; $i < count($key); $i++){
$keyString .= chr($key[$i]);
}
$output = '';
for ($i = 0; $i < stlen($data); $i += $block_size) {
$char = array(0, 0, 0, 0, 0, 0, 0, 0); // ?
$decrypted_piece = mcrypt_decrypt(MCRYPT_BLOWFISH, $keyString, $data, "cbc"); // ??
// And this is where I completely get lost.
$output = transform($in, $start, $end, $in2);
}
}
function transform($in, $start, $end, $in2){
$out = ''; // Yea, that won't work...
for($x = $start; $x < $end; $x++){
$out[$x] = $in[$x] ^ $in2[$x];
}
return $output
}
I realize I'm basically asking you guys to do something for me, but I'm really stuck at the contents of that for (auto it....
Hints / explanations that'd really help me along would be:
What does BF_ecb_encrypt do in this case? (In pseudocode or even php?) (slaps self on fingers. "don't ask for finished products")
Am I on the right track with the translation of the transform?
What is {{0}}, BF_set_key(&key, 16, key_data);?
What is reinterpret_cast<unsigned char*>(&(*it))?
I did get a look at these documentation pages, but to no avail:
php mcrypt-decrypt
BF_ecb_encrypt
C++ transform
The full source is available on github.
This specific code comes from src/parser.cpp

The "broken variant of CBC decryption" that the original code is doing can, equivalently, be described as ECB decryption followed by (cumulatively) XORing each plaintext block with the previous one.
For the XOR, you don't need anything fancy: the PHP bitwise XOR operator can operate on strings.
Thus, a simple PHP version of your C++ code could look something like this (warning: untested code):
function decrypt( $ciphertext, $key ) {
$plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );
$block_size = 8; // Blowfish block size = 64 bits = 8 bytes
$blocks = str_split( $plaintext, $block_size );
$previous = str_repeat( "\0", $block_size );
foreach ( $blocks as &$block ) {
$block ^= $previous;
$previous = $block;
}
return implode( $blocks );
}
Note that I haven't implemented any padding for truncated last blocks; there's something very screwy about the padding handling in the original code, and I don't see how it could possibly correctly decrypt messages whose length is not divisible by 8 bytes. (Does it, actually?) Rather than try to guess what the heck is going on and how to translate it to PHP, I just chose to ignore all that stuff and assume that the message length is divisible by the block size.

What does BF_ecb_encrypt do in this case?
BF_ecb_encrypt() is a function for encrypting using blowfish. The PHP equivalent (as previously mentioned by Ilmari Karonen) is $plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );
What is reinterpret_cast(&(*it))?
BF_ecb_encrypt() expects it's first parameter to be a unsigned char*. reinterpret_cast<unsigned char*>(&(*it)) is a type cast, casting 'it' into a unsigned char*. 'it' is an iterator of an unspecified type (at least in the code provided) and the keyword 'auto' is used to initialize 'it' as the correct type automatically. reinterpret_cast<unsigned char*>(&(*it)) is then used to convert it unsigned char* automatically.
What is {{0}}, BF_set_key(&key, 16, key_data);?
This is used to initialize BF_KEY 'key', and then set the key using the value of key_data. This has no PHP equivalent, mcrypt will set the key internally.
Am I on the right track with the translation of the transform?
By the looks of it, the C++ version handles padding in an odd way. This could be intentional to throw a wrench into cracking attempts. Translating into PHP is not really possible unless you fully understand the algorithm of the original C++ - not just the encryption algo, but the full process being used, including pre and post encryption.
Have you considered making a simple PHP extension using the existing C/C++ code rather than converting to PHP? This should be very strait forward, much easier than converting a more complicated algorithm from C++ into PHP. The existing code can more or less be copy-pasted into an extension, with buffer_t &replay_data likely being registered as a PHP resource.

Would this be helpful, using the php-cpp library (see http://www.php-cpp.com):
/**
* Decrypt function made accessible from PHP
*/
/**
* Dependencies
*/
#include <phpcpp.h>
#include <openssl/blowfish.h>
#include <algorithm>
/**
* Define buffer_t to be a vector
*/
typedef std::vector<uint8_t> buffer_t;
/**
* Function that should be ported to PHP
* #param data
* #param key_data
*/
static void decrypt(std::string &replay_data, const unsigned char *key_data) {
/*\
|*| Performs an in place decryption of the replay using the given key.
|*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
|*| -# Set the variable previous_block (with size of 16 bytes) to 0
|*| -# Decrypt a block with the given key
|*| -# XOR the block with the previous (decrypted) block
|*| -# Go back to step 2, until there are no more blocks.
\*/
BF_KEY key = {{0}};
BF_set_key(&key, 16, key_data);
const int block_size = 8;
size_t padding_size = (block_size - (replay_data.size() % block_size));
if (padding_size != 0) {
size_t required_size = replay_data.size() + padding_size;
replay_data.resize(required_size, 0);
}
unsigned char previous[block_size] = {0};
for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
unsigned char decrypted[block_size] = { 0 };
BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
std::copy_n(decrypted, block_size, previous);
std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
}
if (padding_size != 0) {
size_t original_size = replay_data.size() - padding_size;
replay_data.resize(original_size, 0);
}
}
/**
* The PHP function that will take care of this
* #param parameters
* #return Value
*/
static Php::Value php_decrypt(Php::Parameters &params)
{
// check number of parameters
if (params.size() != 2) throw Php::Exception("2 parameters expected");
// read in the parameters
std::string replay_data = params[0];
std::string key_data = params[1];
// decrypt it
decrypt(replay_data, (const unsigned char *)key_data.c_str());
// return the result
return replay_data;
}
/**
* Symbols are exported according to the "C" language
*/
extern "C"
{
// export the "get_module" function that will be called by the Zend engine
PHPCPP_EXPORT void *get_module()
{
// create extension
static Php::Extension extension("my_decrypt","1.0");
// add custom function
extension.add("my_decrypt", php_decrypt);
// return the extension module
return extension.module();
}
}
You can compile the code using the following command:
g++ -std=c++11 -fpic -shared my_decrypt.cpp -o my_decrypt.so -lphpcpp
The my_descript.so should be copied to you PHP extensions directory, and an "extension=my_decrypt.so" line should be added to your php.ini.

Related

what is the issue with encryption in c++ and decryption in php

Here, I have c++ program which encodes the string and I have to decrypt in php. I have verified that the key and the iv are same in both programs, still getting false in openssl_decrypt() command.
int main(int argc, char** args)
{
unsigned char *salt = (unsigned char*)"12345678";
unsigned char *data = (unsigned char*)"123456789123450";
unsigned int count = 5;
int dlen = strlen((char*)data);
unsigned int ksize = 16;
unsigned int vsize = 12;
unsigned char *key = new unsigned char[ksize];
unsigned char *iv = new unsigned char[vsize];
int ret = EVP_BytesToKey( EVP_aes_128_gcm() , EVP_sha1(), salt, data, dlen, count, key, iv);
const EVP_CIPHER* m_cipher = EVP_aes_128_gcm();
EVP_CIPHER_CTX* m_encode;
EVP_CIPHER_CTX* m_decode;
if (!(m_encode = EVP_CIPHER_CTX_new()))
cout << "ERROR :: In encode Initiallization"<< endl;
EVP_EncryptInit_ex(m_encode, m_cipher, NULL, key, iv);
if (!(m_decode = EVP_CIPHER_CTX_new()))
cout << "ERROR :: In decode Initiallization"<< endl;
EVP_DecryptInit_ex(m_decode, m_cipher, NULL, key, iv);
unsigned char* plain = (unsigned char*)"My Name IS DON !!!";
int len = strlen((char*)plain);
unsigned char* encData = new unsigned char[len];
int c_len = len;
int f_len = 0;
EVP_EncryptInit_ex(m_encode, NULL, NULL, NULL, NULL);
EVP_EncryptUpdate(m_encode, encData, &c_len, plain, len);
EVP_EncryptFinal_ex(m_encode, encData + c_len, &f_len);
len = c_len + f_len;
cout << string( encData, encData + len)<< endl;
}
And the following is decryption code in php. "./abc_enc.txt" contains encryption string of c++ code. As I mentioned above I am getting same key and iv for both programs but openssl_decrypt function returns false. Can someone figure out what is the mistake?
<?
function EVP_BytesToKey($salt, $password) {
$ivlen = 12;
$keylen = 16;
$iterations = 5;
$hash = "";
$hdata = "";
while(strlen($hash)<$ivlen+$keylen)
{
$hdata .= $password.$salt;
$md_buf = openssl_digest($hdata, 'sha1');
for ($i = 1; $i < $iterations; $i++) {
$md_buf = openssl_digest ( hex2bin($md_buf),'sha1');
}
$hdata = hex2bin($md_buf);
$hash.= $hdata;
}
return $hash;
}
function decrypt($ivHashCiphertext, $password) {
$method = "aes-128-gcm";
$salt = "12345678";
$iterations = 5;
$ivlen = openssl_cipher_iv_length($method);
$ciphertext = $ivHashCiphertext;
$genKeyData = EVP_BytesToKey($salt, $password);
$keylen = 16;
$key = substr($genKeyData,0,$keylen);
$iv = substr($genKeyData,$keylen,$ivlen);
//var_dump($key);
//var_dump($iv);
$ret = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
var_dump($ret);
return $ret;
}
$file = './abc_enc.txt';
$fileData = (file_get_contents($file));
$encrypted = $fileData;
$decrypted = decrypt($encrypted, '123456789123450');
?>
The GCM-mode provides both, confidentiality and authenticity. To verify authenticity the GCM-mode uses an authentication tag and defines a length between incl. 12 and 16 Byte for the tag. The authentication strength depends on the length of the tag, i.e. the longer the tag, the more secure the proof of authenticity.
However, in the current C++-code the authentication tag is not determined! This means that one of the main functionalities of the GCM-mode, authentication, is not used.
While the decryption in C++ using EVP is independent from the authentication (this means that the decryption is also performed even if the authentication tags differ), the decryption in PHP using openssl_decrypt is only done if the authentication is successful, i.e. in PHP the authentication tag is mandatory for decryption. Therefore, the authentication tag must be determined in the C++-code. For this purpose, the following code must be added after the EVP_EncryptFinal_ex-call:
unsigned int tsize = 16;
unsigned char *tag = new unsigned char[tsize];
EVP_CIPHER_CTX_ctrl(m_encode, EVP_CTRL_GCM_GET_TAG, tsize, tag);
Here a tagsize of 16 Byte is used. In addition, the authentication tag must be used in the PHP-code for decryption. This is done by passing the authentication tag as the 6th parameter of the openssl_decrypt-method:
$ret = openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv, $tag);
Decryption can only be performed if the tag used for decryption matches the tag used for encryption.
For the data in the posted example the C++-code generates the following authentication tag (as hexadecimal string):
f7c18e8b99587f3063383d68230c0e35
Finally, a more detailed explanation for AES-GCM with OpenSSL can be found here for encryption and decryption (including the consideration of the authentication tag).
In short
I'm neither an openSSL nor a PHP specialist. But at first sight, it appears that you might experience problems because you read and write binary data using files in text mode.
More infos about the potential problem
The encrypted data resulting from your C++ code is binary data. Your code does not show how you write the file. But unless you explicitly request the file to be in binary mode, you will get a file in text mode.
This might cause multiple issues when writing the data, since text mode allows for OS-dependent transformations to happen. Typical examples are characters with value 0x0A (new lines), skipping trailing 0x20 (space character), adding a 0x0A at the end of the file if there isn't and similar undesired transformation.
Your PHP code might open the file in an incompatible default mode, which might add further transformation if it's text mode, or avoid reverting transformations if it's binary.
This means that in the end, the string you try to decode might not be the original encrypted string !
How to solve it ?
First inspect with a binary editor the content of the file to see if it matches the expectations. Or check the expected length of the encrypted source data, the length of the file, and the length of the loaded content. If they all match, my answer is not relevant.
If it is relevant, or if you intend sooner or later to allow cross platform exchanges (e.g. windows client communicating with a linux server), then you could:
either add the necessary statements to use binary mode on both sides.
or add a Base64 encoding to transform binary into a robust ascii string on the writing side, and retransforming Base64 into binary on the reading side (openssl provides for base64 encoding and PHP has everything needed as well)

PHP openssl_encrypt not always decryptable with C++

I have a function which encrypts certain strings for transfer to my client application in C++. The function I use in PHP to generate the encrypted string:
<?php
echo EncryptForTransfer("This is a test"); //returns: l4/r5AUDTrPTlIfVyG0=DJKlty0VgWPSNsM2XbzkOZ79ivQA2eBWTd18FKVfgslM6UmP
function EncryptForTransfer($EncryptMe) {
$Key = random_string(32);
$IV = random_string(16);
return openssl_encrypt($EncryptMe, "AES-256-CFB", (string)$Key, false, $IV).$IV.$Key;
}
?>
However, when I try to decrypt this result in C++ with the functions shown underneath, it is not returning me the string correctly (missing last parts).
int Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len;
if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cfb(), NULL, key, iv)) {
handleErrors();
}
if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
handleErrors();
}
plaintext_len = len;
if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) {
handleErrors();
}
plaintext_len += len;
EVP_CIPHER_CTX_free(ctx);
return plaintext_len;
}
string DecryptFromTransfer(string DecryptMe) {
long long DecryptFromTransferLength = DecryptMe.length();
string TransferKey = DecryptMe.substr(DecryptFromTransferLength - 32, 32);
string TransferIV = DecryptMe.substr(DecryptFromTransferLength - 32 - 16, 16);
string DecryptMeOriginal = DecryptMe.substr(0, DecryptFromTransferLength - 32 - 16);
return DecryptThis(DecryptMeOriginal, TransferKey, TransferIV);
}
cout << DecryptFromTransfer("l4/r5AUDTrPTlIfVyG0=DJKlty0VgWPSNsM2XbzkOZ79ivQA2eBWTd18FKVfgslM6UmP"); //returns: This is
Please note that for some strings the decryption in C++ works correctly. However, for other strings the output is as stated above, only partially decrypted and missing the last parts. Maybe it is some kind of padding issue since some strings are decrypted correctly? So, what is wrong with my code?
The used string in the code above is just an example string to clarify the issue.

AES | Encrypt with OpenSSL, decrypt with mcrypt

I am using the following function to encrypt my data via the OpenSSL Library in Qt:
QByteArray Crypto::Encrypt(QByteArray source, QString password)
{
EVP_CIPHER_CTX en;
unsigned char *key_data;
int key_data_len;
QByteArray ba = password.toLatin1();
key_data = (unsigned char*)ba.data();
key_data_len = strlen((char*)key_data);
int nrounds = 28;
unsigned char key[32], iv[32];
EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), NULL, key_data, key_data_len, nrounds, key, iv);
QByteArray bkey = reinterpret_cast<const char*>(key) //EDIT: Contains the key afterwards
QByteArray biv = reinterpret_cast<const char*>(iv) //EDIT: Is Null afterwards
EVP_CIPHER_CTX_init(&en);
EVP_EncryptInit_ex(&en, EVP_aes_256_cbc(), NULL, key, iv);
char *input = source.data();
char *out;
int len = source.size();
int c_len = len + 16, f_len = 0;
unsigned char *ciphertext = (unsigned char *)malloc(c_len);
EVP_EncryptInit_ex(&en, NULL, NULL, NULL, NULL);
EVP_EncryptUpdate(&en, ciphertext, &c_len, (unsigned char *)input, len);
EVP_EncryptFinal_ex(&en, ciphertext+c_len, &f_len);
len = c_len + f_len;
out = (char*)ciphertext;
EVP_CIPHER_CTX_cleanup(&en);
return QByteArray(out, len);
}
"source" is in that case "12345678901234567890123456789012abc".
"password" is "1hA!dh==sJAh48S8Ak!?skiitFi120xX".
So....if I got that right, then EVP_BytesToKey() should generate a key out of the password and supplied data to decrypt the string with later.
To Base64-Encoded that key would be: "aQkrZD/zwMFU0VAqjYSWsrkfJfS28pQJXym20UEYNnE="
I don't use a salt, so no IV (should be null).
So QByteArray bkey in Base64 leaves me with "aQkrZD/zwMFU0VAqjYSWsrkfJfS28pQJXym20UEYNnE="
QByteArray bvi is giving me Null
The encryptet text is "CiUqILbZo+WJBr19IiovRVc1dqGvrastwo0k67TTrs51HB8AbJe8S4uxvB2D7Dkr".
Now I am using the following PHP function to decrypt the ciphertext with the generated key again:
<?php
function decrypt_data($data, $iv, $key) {
$cypher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
//if(is_null($iv)) {
// $ivlen = mcrypt_enc_get_iv_size($cypher);
// $iv = substr($data, 0, $ivlen);
// $data = substr($data, $ivlen);
//}
// initialize encryption handle
if (mcrypt_generic_init($cypher, $key, $iv) != -1) {
// decrypt
$decrypted = mdecrypt_generic($cypher, $data);
// clean up
mcrypt_generic_deinit($cypher);
mcrypt_module_close($cypher);
return $decrypted;
}
return false;
}
$ctext = "CiUqILbZo+WJBr19IiovRVc1dqGvrastwo0k67TTrs51HB8AbJe8S4uxvB2D7Dkr";
$key = "aQkrZD/zwMFU0VAqjYSWsrkfJfS28pQJXym20UEYNnE=";
$res = decrypt_data(base64_decode($ctext), null, base64_decode($key));
echo $res;
?>
Now I'd expect a response like "12345678901234567890123456789012abc".
What I get is "7890123456789012abc".
My string seems to be decrypted in the right way, but it's cut in half and only the last 19 characters are displayed.
Can someone please help me with that?
I'm new to encryption and can't really figure out where exactly I went wrong.
This is probably because of a misinterpretation from your part. You say:
I don't use a salt, so no IV (should be null).
But there is no reason at all why that would be the case. The EVP_BytesToKey method provided both a key and an IV. The key is obviously correct, but the IV is not. This will result in random characters in your plain text (the IV only changes the first block). As this block will likely contain control characters and what more, it may not display well.
Remember that a salt and IV may have a few things in common (should not be repeated, can be public etc.) but that they are entirely different concepts in cryptography.
Please try again with your Qt code, and this time print out the IV as well as the key...
I solved the problem with the empty initialisation vector by trial and error now, though I have no clue why the following was a problem at all.
Maybe someone can explain that to me.
Changing the line: int nrounds = 28; did the trick.
If i put any other number than 28 in there, an IV is generated and when I use it afterwards in mcrypt the ciphertext is decrypted in the correct way.
Why was it a problem to generate the key with 28 rounds with the openssl-function EVP_BytesToKey()?
I reduced it to 5 rounds now, but I'm curious whether this problem might happen again with a password-rounds-combination that has the possibility to generate such a Null-IV.
I don't realy know how the process of the IV generation is handled in this function.

Encryption in Android equivalent to php's MCRYPT_RIJNDAEL_256

I am using the below php code for encryption:
$enc_request = base64_encode(
mcrypt_encrypt(MCRYPT_RIJNDAEL_256,
$this->_app_key,
json_encode($request_params),
MCRYPT_MODE_ECB)
);
Now trying to encrypt the in android and getting the different encrypted string. Below is the android code:
public void enc(){
byte[] rawKey = getRawKey("my_key".getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal("my_message".getBytes());
String result=Base64.encodeToString(encrypted, Base64.DEFAULT);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(256, sr);
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
Could any one help me, where I am wrong? And get same correct encrypted string in android too.
I've created a main method in Java using Bouncy Castle to show the inner workings of mcrypt_encrypt() used in your code sample.
This is mainly to show other developers that PHP's mcrypt_encrypt() is a very dangerous method to use. It won't fail much, but that is because it rather continues where it should have stopped long ago. For instance, it adds or removes values from the key. It emits a warning when it does do this, but it won't directly show in the code.
public static void main(String[] args) throws DataLengthException, IllegalStateException, InvalidCipherTextException {
// just some constants
boolean ENCRYPT = true;
boolean DECRYPT = false;
// the key is either in binary in PHP or a string (dynamic isn't it?), lets assume ASCII
byte[] givenKey = args[0].getBytes(Charset.forName("ASCII"));
// determine the key size dynamically, somebody thought this was a good idea...
// NOTE: PHP will emit a warning if the key size is larger, but will simply use the
// largest key size otherwise
final int keysize;
if (givenKey.length <= 128 / Byte.SIZE) {
keysize = 128;
} else if (givenKey.length <= 192 / Byte.SIZE) {
keysize = 192;
} else {
keysize = 256;
}
// create a 256 bit key by adding zero bytes to the decoded key
byte[] keyData = new byte[keysize / Byte.SIZE];
System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
KeyParameter key = new KeyParameter(keyData);
// create a Rijndael cipher with 256 bit block size, this is not AES
BlockCipher rijndael = new RijndaelEngine(256);
// use a padding method that only works on data that cannot end with zero valued bytes
ZeroBytePadding c = new ZeroBytePadding();
// use ECB mode encryption, which should never be used
PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);
// initialize the cipher using the key (no need for an IV, this is ECB)
pbbc.init(ENCRYPT, key);
// create a plain text byte array
byte[] plaintext = args[1].getBytes(Charset.forName("UTF8"));
// create a buffer for the ciphertext
byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];
int offset = 0;
offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
offset += pbbc.doFinal(ciphertext, offset);
// show the ciphertext
System.out.println(new String(Hex.encode(ciphertext), Charset.forName("ASCII")));
// reverse the encryption
pbbc.init(DECRYPT, key);
byte[] decrypted = new byte[pbbc.getOutputSize(ciphertext.length)];
offset = 0;
offset += pbbc.processBytes(ciphertext, 0, ciphertext.length, decrypted, offset);
offset += pbbc.doFinal(decrypted, offset);
// this will probably print out correctly, but it isn't actually correct
System.out.println(new String(decrypted, Charset.forName("UTF8")));
// check out the zero's at the end
System.out.println(new String(Hex.encode(decrypted), Charset.forName("UTF8")));
// so lets make it a bit shorter... the PHP way
// note that in PHP, the string may *not* contain a null terminator
// add it yourself before printing the string
System.out.println(new String(decrypted, Charset.forName("UTF8")).replaceAll("\\x00+$", ""));
}
Warning: the above code contains ZeroBytePadding. I later discovered that there is a difference between Bouncy Castle and PHP in this respect: Bouncy Castle expects that you always have to pad, while PHP doesn't. So Bouncy adds 1..n bytes while PHP adds 0..(n-1) bytes, where n is the block size (32 bytes for Rijndael-256/256). So you may have to do the padding/unpadding yourself; be sure to test the edge cases!

Blowfish-encrypted messages between NSIS and PHP

For a project I'm working on, I need to encrypt and decrypt a string using Blowfish in a compatible way across NSIS and PHP.
At the moment I'm using the Blowfish++ plugin for NSIS and the mcrypt library with PHP. The problem is, I can't get them both to produce the same output.
Let's start with the NSIS Blowfish++ plugin. Basically the API is:
; Second argument needs to be base64 encoded
; base64_encode("12345678") == "MTIzNDU2Nzg="
blowfish::encrypt "test#test.com***" "MTIzNDU2Nzg="
Pop $0 ; 0 on success, 1 on failure
Pop $1 ; encrypted message on success, error message on failure
There's no mention of whether it's CBC, ECB, CFB, etc. and I'm not familiar enough with Blowfish to be able to tell by reading the mostly undocumented source. I assume it's ECB since the PHP docs for mcrypt tells me that ECB doesn't need an IV.
I've also learned by reading the source code that the Blowfish++ plugin will Base64 decode the second argument to encrypt (I'm not sure why). It also returns a Base64 encoded string.
For the PHP side of things, I'm basically using this code to encrypt:
$plainText = "test#test.com***";
$cipher = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_ECB, '');
$iv = '00000000'; // Show not be used anyway.
$key = "12345678";
$cipherText = "";
if (mcrypt_generic_init($cipher, $key, $iv) != -1)
{
$cipherText = mcrypt_generic($cipher, $plainText);
mcrypt_generic_deinit($cipher);
}
echo base64_encode($cipherText);
However, if I do all these things, I get the following output from each:
NSIS: GyCyBcUE0s5gqVDshVUB8w==
PHP: BQdlPd19zEkX5KT9tnF8Ng==
What am I doing wrong? Is the NSIS plugin not using ECB? If not, what is it using for it's IV?
OK, I've gone through that code and reproduced your results. The problem isn't the cipher mode - NSIS is using ECB. The problem is that the NSIS Blowfish code is simply broken on little-endian machines.
The Blowfish algorithm operates on two 32-bit unsigned integers. To convert between a 64 bit plaintext or ciphertext block and these two integers, the block is supposed to be interpreted as two Big Endian integers. The NSIS Blowfish plugin is instead interpreting them in host byte order - so it fails to do the right thing on little-endian hosts (like x86). This means it'll interoperate with itself, but not with genuine Blowfish implementations (like mcrypt).
I've patched Blowfish++ for you to make it do the right thing - the modified Blowfish::Encrypt and Blowfish::Decrypt are below, and the new version of blowfish.cpp is here on Pastebin.
void Blowfish::Encrypt(void *Ptr,unsigned int N_Bytes)
{
unsigned int i;
unsigned char *Work;
if (N_Bytes%8)
{
return;
}
Work = (unsigned char *)Ptr;
for (i=0;i<N_Bytes;i+=8)
{
Word word0, word1;
word0.byte.zero = Work[i];
word0.byte.one = Work[i+1];
word0.byte.two = Work[i+2];
word0.byte.three = Work[i+3];
word1.byte.zero = Work[i+4];
word1.byte.one = Work[i+5];
word1.byte.two = Work[i+6];
word1.byte.three = Work[i+7];
BF_En(&word0, &word1);
Work[i] = word0.byte.zero;
Work[i+1] = word0.byte.one;
Work[i+2] = word0.byte.two;
Work[i+3] = word0.byte.three;
Work[i+4] = word1.byte.zero;
Work[i+5] = word1.byte.one;
Work[i+6] = word1.byte.two;
Work[i+7] = word1.byte.three;
}
Work = NULL;
}
void Blowfish::Decrypt(void *Ptr, unsigned int N_Bytes)
{
unsigned int i;
unsigned char *Work;
if (N_Bytes%8)
{
return;
}
Work = (unsigned char *)Ptr;
for (i=0;i<N_Bytes;i+=8)
{
Word word0, word1;
word0.byte.zero = Work[i];
word0.byte.one = Work[i+1];
word0.byte.two = Work[i+2];
word0.byte.three = Work[i+3];
word1.byte.zero = Work[i+4];
word1.byte.one = Work[i+5];
word1.byte.two = Work[i+6];
word1.byte.three = Work[i+7];
BF_De(&word0, &word1);
Work[i] = word0.byte.zero;
Work[i+1] = word0.byte.one;
Work[i+2] = word0.byte.two;
Work[i+3] = word0.byte.three;
Work[i+4] = word1.byte.zero;
Work[i+5] = word1.byte.one;
Work[i+6] = word1.byte.two;
Work[i+7] = word1.byte.three;
}
Work = NULL;
}

Categories