Replacing JAVA with PHP for PKCS5 encryption - java

I have been tasked with replacing a legacy java system with something which runs PHP.
I am getting a little stuck on replacing the java cryptography with PHP code.
cipherAlgorythm = "PBEWithMD5AndDES";
cipherTransformation = "PBEWithMD5AndDES/CBC/PKCS5Padding";
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, iterations);
SecretKeyFactory kf = SecretKeyFactory.getInstance(cipherAlgorythm);
SecretKey key = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(password.toCharArray()));
Cipher encryptCipher = Cipher.getInstance(cipherTransformation);
encryptCipher.init(Cipher.ENCRYPT_MODE, key, ps);
byte[] output = encryptCipher.doFinal("This is a test string".getBytes("UTF-8"));
Seems to be the guts of the Java
In PHP I am doing
$hashed_key = pbkdf2('md5', $this->key, $this->salt, $this->reps , <GUESS 1>, TRUE);
$output = mcrypt_encrypt(MCRYPT_DES, $hashed_key, "This is a test string", MCRYPT_MODE_CBC, <GUESS 2>);
pbkdf2 is from here.
So <GUESS 1> is the key size and <GUESS 2> is the IV. I have played around with these to no avail. Does anyone have suggestion for such values? As far as I can see the encryption itself should be portable but I am unsure about what is going on in some of the Java methods.
It looks like java is creating an IV somewhere, but I don't understand how or where.
RELATED
Decrypt ( with PHP ) a Java encryption ( PBEWithMD5AndDES )

You may want to look at http://us3.php.net/manual/en/ref.mcrypt.php#69782, but basically he implemented a DIY padding solution:
function pkcs5_pad ($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
That may be your best bet, but if you look at this comment, his suggestions on how to verify that each step is correct may be useful for you.
https://stackoverflow.com/a/10201034/67566
Ideally you should move away from DES and since this padding is going to be a problem in PHP, why not see if you can change the encryption algorithm to something less troublesome and more secure?
To help you can show this page: http://www.ietf.org/rfc/rfc4772.txt, where it is succinctly expressed that DES is susceptible to brute force attacks, so has been deprecated and replaced with AES.

Both existing answers helped, but I'll post the complete solution here.
I have not seen it documented anywhere but after looking at implementations for this encryption scheme I found the key is the first 8 bytes of the encrypted hash and the IV is the last 8.
public function get_key_and_iv($key, $salt, $reps) {
$hash = $key . $salt;
for ($i = 0; $i< $reps; $i++) {
$hash = md5($hash, TRUE);
}
return str_split($hash,8);
}
seems to do the trick. Which replaces pbkdf2 in my question, negates the need for <GUESS 1> and gives a value for <GUESS 2>
Then I got caught with the padding problem which James Black mentioned and managed to fix. So final code is
list($hashed_key, $iv) = get_key_and_iv($key, $salt, $reps);
// 8 is DES block size.
$pad = 8 - (strlen($plaintext) % 8);
$padded_string = $plaintext . str_repeat(chr($pad), $pad);
return mcrypt_encrypt(MCRYPT_DES, $hashed_key, $padded_string, MCRYPT_MODE_CBC, $iv);

You can use hash_pbkdf2 PHP (5.5) function too instead of using PBKDF2 PHP libraries.
According to PHP docs the GUESS 1 is the length of the created derived key
length
The length of the output string. If raw_output is TRUE this corresponds to the
byte-length of the derived key, if raw_output is
FALSE this corresponds to twice the byte-length of the derived key (as
every byte of the key is returned as two hexits).
If 0 is passed, the entire output of the supplied algorithm is used.
Maybe this post (what is an optimal Hash size in bytes?) result interesting for you.
GUESS 2 or IV is a random initialization vector used to create an unique salt to generate the hash.
You can create the IV with mycript_create_iv function.
Take a look at the complete sample in PHP.net
<?php
$password = "password";
$iterations = 1000;
// Generate a random IV using mcrypt_create_iv(),
// openssl_random_pseudo_bytes() or another suitable source of randomness
$salt = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
$hash = hash_pbkdf2("sha256", $password, $salt, $iterations, 20);
echo $hash;
?>

Related

openssl_encrypt AES encrypt with out vector

I need a token to access some source. The source provider gave me a java example that show me how to create it. Here is how it encrypt text:
private static final String ALGO = "AES";
public static String encrypt(byte[] keyValue, String token_text) throws Exception {
Key key = new SecretKeySpec(keyValue, ALGO);
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = c.doFinal(token_text.getBytes());
byte[] buf = Base64.encodeBase64(bytes);
return new String(buf);
}
I wish to translate this pice of java program to php.So, here is my code:
public static function generate_token($token_text, $key) {
$iv = self::hex_to_str("00000000000000000000000000000000");
$token = openssl_encrypt($token_text, "AES-128-CBC", $key, 0, $iv);
return $token;
}
private static function hex_to_str($hex)
{
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2)
{
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}
But I can't get same result. I searched around. It looks like cipher.init called without vector. If i do the same thing to openssl_encrypt, it will gave me an error:
openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended
Some one said the default vector for Cipher is 0. I tried so, still can't get the same result, but very close:
java: v8GhW0lu8DzNyqsfQTg4g7H6pwXCAAgy9vqFdz5OmXY=
php : v8GhW0lu8DzNyqsfQTg4g6If77f+8YVCcq8VcQGNe68=
I am stuck here for a whole day. I would be very grateful if anyone can help.
# java -version
openjdk version "1.8.0_101"
OpenJDK Runtime Environment (build 1.8.0_101-b13)
OpenJDK 64-Bit Server VM (build 25.101-b13, mixed mode)
# php -v
PHP 5.6.24 (cli) (built: Jul 21 2016 07:42:08)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
Under the assumption that the Java side uses the SunJCE-Provider the default mode is the ECB-mode and the default padding is the PKCS5Padding i.e. Cipher.getInstance("AES") is identical to Cipher.getInstance("AES/ECB/PKCS5Padding") see e.g. https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#ciphertrans.
The ECB-mode doesn't use an IV. Thus, you have to replace in your PHP generate_token-method
$iv = self::hex_to_str("00000000000000000000000000000000");
$token = openssl_encrypt($token_text, "AES-128-CBC", $key, 0, $iv);
with
$token = openssl_encrypt($token_text, "AES-128-ECB", $key, 0);
Example:
encrypt("This is the key!".getBytes(), "This is a plain text that needs to be encrypted...");
provides
fLSh/HoQkrsIVBtZJVnuIRqcz4ztUBDkDG9Pi3xe49Q9hh9zDzWZDRHEO70ixfLf2WbWYSeDOQ/ONFTWHW9i0Q==
which is identical to the result of the PHP code (for the same key and plain text).
Generally, the ECB-mode is not secure if you have more than one block (from your examples I infer that your token consists of at least two blocks). Then, a better choice would be the CBC- or GCM-mode, see e.g. https://crypto.stackexchange.com/questions/20941/why-shouldnt-i-use-ecb-encryption and https://security.stackexchange.com/questions/6500/aes-ecb-mode-for-single-block-random-data-encryption. But since the Java encrypt-method seems to be your reference there is probably no way to change it.
EDIT:
The Java cipher-class determines automatically with the keysize if AES-128, AES-192 or AES-256 must be used. Therefore, you also have to know the size of the key in the context of the Java code. If the keysize is 16 byte then the choice of AES-128-ECB is correct, otherwise you have to adjust your PHP code accordingly (e.g. AES-192-ECB or AES-256-ECB for a 24 byte or a 32 byte keysize, respectively).

Java PBEWITHSHA1ANDDESEDE encryption equivalent in node.js?

I have inheritied Java web application and am supposed to convert that to node.js.
Part of that is encryption of data. In Java it is done like in attached code. How would I do that in node using crypto?
I am not strong in cryptography at all, sorry if this is really basic question and thanks in advance.
private final String ALGORITHM = "PBEWITHSHA1ANDDESEDE";
private final int ITERATION_COUNT = 20;
private final byte[] SALT = {
(byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
(byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
};
and later...
PBEKeySpec pbeKeySpec = new PBEKeySpec("password".toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(SALT, ITERATION_COUNT);
// Create PBE Cipher
Cipher pbeCipher = Cipher.getInstance(ALGORITHM);
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
byte[] encrypted = pbeCipher.doFinal("text to be encrypted");
Edit:
This is my failed attempt to implement that in node.js based on various searches:
var crypto = require('crypto');
var SALT = new Buffer('c773218c7ec8ee99', 'hex');
var pass = new Buffer('password');
//digest is by default SHA-1 thats what I need
var key = crypto.pbkdf2Sync(pass, SALT, 20, 56); //[, digest]
//var cipher = crypto.createCipher('des-ede-cbc', key);
var cipher = crypto.createCipher('des-ede', key);
//var cipher = crypto.createCipheriv('des-ede-cbc', key, new Buffer('00000000', 'binary'));
cipher.update(new Buffer('This is to be encoded'));
var encrypted = cipher.final('binary');
console.log(encrypted);
fs.writeFileSync('encrypted.file', encrypted);
When I am trying to use crypto.createCipheriv I have no idea what to put there as IV.
When used without IV, it produces some 'encrypted' gibberish however when saved to file, it can't be read and decoded on Java side. Sigh.
The reason that you can't get these to interoperate is the Java side is using PBE and the node.js side is using PBKDF2, which serve similar purposes and come from the same standards document (PKCS #5), but have very different mechanisms under the hood.
Because these are different key generation algorithms, you generate different keys on each end, and thus get different results when you decrypt.
There is also something of an mismatch in the level of abstraction between java and node.js in your code. the Java API you are using is very high level, and uses OpenSSL-like constructs. Meanwhile, the node.js code is at a much lower level, gluing pieces together bit-by-bit. This can cause issues when, for example, the java code introduces a particular padding structure or cipher mode of operation.
If this is just for learning or something non-critical, I would recommend changing the java code to be at the same lower level as the node.js code, and put pieces together one by one: generate the key on both sides and make sure they are the same, ecrypt on both sides and get the same output, etc. If you can't change the java code, use something like forge to get your node.js code at the same higher level as the java code. But as the comments on this question indicate, you may not be able to do PBE anyways.
If this is for something "real" where you actually want the files saved to be secure, call out to an external program like gpg to handle the encryption, instead of "rolling your own" file encryption system.

perl CBC DES equivalent in java

We are migrating some code from perl to java/scala and we hit a roadblock.
We're trying to figure out how to do this in Java/scala:
use Crypt::CBC;
$aesKey = "some key"
$cipher = new Crypt::CBC($aesKey, "DES");
$encrypted = $cipher->encrypt("hello world");
print $encrypted // prints: Salted__�,%�8XL�/1�&�n;����쀍c
$decrypted = $cipher->decrypt($encrypted);
print $decrypted // prints: hello world
I tried a few things in scala but didn't really get it right, for example something like this:
val secretKey = new SecretKeySpec("some key".getBytes("UTF-8"), "DES")
val encipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
encipher.init(Cipher.ENCRYPT_MODE, secretKey)
val encrypted = encipher.doFinal(bytes)
println("BYTES:" + bytes)
println("ENCRYPTED!!!!!!: " + encrypted)
println(toString(encrypted))
Any help or direction in Java/scala would very much be appreciated
Assuming that Crypt module is the one I find at https://metacpan.org/pod/Crypt::CBC it is documented as by default doing (the same as) openssl, apparently meaning commandline 'enc' (openssl library has MANY other options). That is not encryption
with the specified key (and IV) directly, but instead 'password-based' encryption (PBE) with a key and IV derived from the specified 'key' (really passphrase) plus (transmitted) salt, using a twist on the original (now unrecommended) PKCS#5 v1.5 algorithm, retronymed PBKDF1. See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html (or the man page on a Unix system with openssl installed) and rfc2898 (or the original RSA Labs PKCS documents now somewhere at EMC).
You say you cannot change the perl sender. I hope the users/owners/whoever realize that original DES,
retronymed single-DES for clarity, has been practically brute-forceable for well over a decade, and
PBE-1DES may be even weaker; the openssl twist doesn't iterate as PKCS#5 (both KDF1 and KDF2) should.
Java (with the Suncle providers) does implement PBEWithMD5AndDES, which initted with PBEParameterSpec (salt, 1)
does successfully decrypt data from 'openssl enc -des-cbc', and thus I expect also your perl sender (not tested).
FWIW if you could change to triple-DES, Java implements PBEWithMD5AndTripleDES using an apparently nonstandard
extension of PBKDF1 (beyond hash size) that is quite unlike openssl's nonstandard extension, and thus incompatible if the perl module is in fact following openssl.
You would have to do the key-derivation yourself and then direct 3DES-CBC-pad, which isn't very hard.
Also note encrypted data from any modern computer algorithm is binary. "Printing" it as if it were text
in perl, or Java or nearly anything else, is likely to cause data corruption if you try to use it again.
If you are only looking to see 'is there any output at all, and is it visibly not the plaintext' you're okay.

How to generate the same AES key in Java(Android) as in .Net?

I need to generate an AES key in Java (Android) from salt and password given from .Net WebService. I need to have the same key as the key generated in .net with the same password and salt (using Rfc2898DeriveBytes and AesManaged()).
Here is my code in Android:
char[] passwordAsCharArray = password.toCharArray();
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwordAsCharArray, salt, 1000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKeySpec secretKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
Here is code in .net:
byte[] keyBytes = Encoding.Unicode.GetBytes(key);
Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(key, keyBytes);
AesManaged rijndaelCSP = new AesManaged();
rijndaelCSP.BlockSize = 128;
rijndaelCSP.KeySize = 256;
rijndaelCSP.Key = derivedKey.GetBytes(rijndaelCSP.KeySize / 8);
rijndaelCSP.IV = derivedKey.GetBytes(rijndaelCSP.BlockSize / 8);
ICryptoTransform decryptor = rijndaelCSP.CreateDecryptor();
When I compare both keys they are different. Any ideas how to generate on Android the same key as in .Net? (I know that the key which have been generated in .net is correct).
Number of iterations in .Net is 1000, salt and password are also the same as in Android.
Ok, it turned out that I dont need exactly the same key (as a byte array). I needed this to decrypt a file (in Java) which have been encrypted in .Net - with this key it gaves me Bad Padding Exception so I think the key was different and that causes the problem, but all I needed to do was to generate IV like a key - that solved my problem. Thanks for response!
It looks like you used the "key" (which should be a password) as a salt in your .NET code, whereas the Java part uses a specified salt. Furthermore, you specified the Unicode character set for decoding your salt, which is weird, the salt should be a random octet string (== byte array) from the beginning.
I would recommend you transform your password and random salt to byte arrays first, compare both using a hexadecimal representation (on your console, or in your debugger) and only then use those as input parameters for the PBKDF2 function in each. I would recommend an UTF-8 encoding for your password.
Always specify all parameters in cryptography, try not to use default, e.g. for the iteration count. If your input is off by a single bit, the output will be completely incorrect, and there is no way to tell which parameter was responsible.
It looks like the Java and .NET PBKDF2 "primitive" is identical on both platforms, there is working code out on the internet.

How to decrypt a Jasypt-encrypted string in PHP?

This may be an impossible question, but I am migrating a legacy system from Java over to PHP, and I need to be able to decrypt strings encrypted with Jasypt in PHP.
According to the documentation, Jasypt uses the following algorithm:
Append a random salt (I think that is the same as an initialization vector for the cipher) to the data to be encrypted
Encrypt repeatedly 1000 times
Prepend the unencrypted salt/IV to the encrypted string
Base64 encode the entire string
The legacy application uses the PBEWithMD5AndDES Jasypt algorithm. I am fully aware that MD5 isn't designed to be decrypted, and that's not what I'm trying to do.
I simply want to DES-decrypt the string so that all I'm left with is the MD5 hash. I can't seem to get anything but binary garbage out of PHP. What am I missing?
<?php
#jasypt.algorithm=PBEWithMD5AndDES
$secret = 'secret-password';
$encrypted = 'xh/roK2diJPDfZGlT9DlwuG2TsS7t7F+';
$cipher = MCRYPT_DES;
$modes = array(
'ecb' => MCRYPT_MODE_ECB,
'cbc' => MCRYPT_MODE_CBC,
'cfb' => MCRYPT_MODE_CFB,
'ofb' => MCRYPT_MODE_OFB,
'nofb' => MCRYPT_MODE_NOFB,
'stream' => MCRYPT_MODE_STREAM,
);
foreach($modes as $mode => $mc) {
$iv_len = 0; //mcrypt_get_iv_size($cipher, $mode);
$password = base64_decode($encrypted);
$salt = substr($password, 0, $iv_len);
$data = substr($password, $iv_len);
for($i = 0; $i < 1000; $i++) {
$data = #mcrypt_decrypt($cipher, $secret, $data, $mode, $salt);
}
var_dump("$mode: $i: $data");
}
You are not understanding the "PBEWithMD5AndDES" meaning.
PBEWithMD5AndDES means that the encryption password (a String) is hashed with MD5 in order to obtain an array of bytes used as encryption key input to the DES algorithm along with the text to be encrypted.
So, there is no way to unencrypt with DES in order to get a MD5 hash. That makes no sense. You simply need to decrypt that encrypted data using exactly that same algorithm, but in a PHP implementation.
And by the way, "PBEWithMD5AndDES" is not a "jasypt algorithm". It is a Java Cryptography Extension (JCE) algorithm. Jasypt does not implement any algorithms itself.
Hope this helps.
Php for Java simplified encryption here: https://github.com/kevwis/Phpsypt
You're missing generating the key.
I had to do the same thing for a customer of mine and wrote a few lines of code to help with issue: https://github.com/kevinsandow/PBEWithMD5AndDES

Categories