openssl_encrypt AES encrypt with out vector - java

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).

Related

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.

Compatible AES algorithm for Java and Javascript

I need to encrypt some values in Java application using AES algorithm, and decrypt the same in my Javascript module of my application.
I saw some examples over the internet but it seems there's some differences in compatibility.
like below issue :
AES encryption in javascript and decrypting in java
Can someone please point me some code examples to solve this issue.
Thanks.
AES is an exactly specified algorithm, so all AES implementations must be "compatible". Having said that, AES is a variable-key-length block-cipher, operating on 128-bit blocks. To actually use this in a piece of software, you have to make a bunch of other choices: how to deal with input consisting of more than 1 block (this is called the "mode"), in some modes you need an initialization vector, you need to deal with input not consisting of an exact number of blocks (padding), how to encode characters into bytes, and how to represent bytes in a context (like a source file) that doesn't support that. All those things need to be compatible.
Below is a tested example. It uses the standard Java crypto functions (and Apache Commons Codec), and JavaScript crypto library crypto-js. Choices are as follows: 128-bit key, cipher-block-chaining mode (which needs an initialization vector), PKCS5/7 padding, UTF-8 for character encoding, Base64 to represent byte arrays.
This piece of Java will output Base64-encoded ciphertext:
String plainText = "Hello, World! This is a Java/Javascript AES test.";
SecretKey key = new SecretKeySpec(
Base64.decodeBase64("u/Gu5posvwDsXUnV5Zaq4g=="), "AES");
AlgorithmParameterSpec iv = new IvParameterSpec(
Base64.decodeBase64("5D9r9ZVzEYYgha93/aUK2w=="));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
System.out.println(Base64.encodeBase64String(cipher.doFinal(
plainText.getBytes("UTF-8"))));
This piece of JavaScript correctly decrypts it:
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var encrypted = CryptoJS.enc.Base64.parse('3Q7r1iqtaRuJCo6QHA9/GhkTmbl4VkitV9ZsD3K2VB7LuBNg4enkJUA1cF8cHyovUH2N/jFz3kbq0QsHfPByCg==');
var key = CryptoJS.enc.Base64.parse('u/Gu5posvwDsXUnV5Zaq4g==');
var iv = CryptoJS.enc.Base64.parse('5D9r9ZVzEYYgha93/aUK2w==');
document.write(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(
{ ciphertext: encrypted },
key,
{ mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: iv, })));
</script>

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.

Replacing JAVA with PHP for PKCS5 encryption

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;
?>

Using openssl encryption with Java

I have a legacy C++ module that offers encryption/decryption using the openssl library (DES encryption). I'm trying to translate that code into java, and I don't want to rely on a DLL, JNI, etc...
C++ code looks like:
des_string_to_key(reinterpret_cast<const char *>(key1), &initkey);
des_string_to_key(reinterpret_cast<const char *>(key2), &key);
key_sched(&key, ks);
// ...
des_ncbc_encrypt(reinterpret_cast<const unsigned char *>(tmp.c_str()),
reinterpret_cast< unsigned char *>(encrypted_buffer), tmp.length(), ks, &initkey,
DES_ENCRYPT);
return base64(reinterpret_cast<const unsigned char *>(encrypted_buffer), strlen(encrypted_buffer));
Java code looks like:
Cipher ecipher;
try {
ecipher = Cipher.getInstance("DES");
SecretKeySpec keySpec = new SecretKeySpec(key, "DES");
ecipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] utf8 = password.getBytes("UTF8");
byte[] enc = ecipher.doFinal(utf8);
return new sun.misc.BASE64Encoder().encode(enc);
}
catch {
// ...
}
So I can do DES encryption in Java pretty easily, but how can I get the same result as with the above code with methods that are completely different? What bothers me in particular is the fact that the C++ version uses 2 keys while the Java version uses only 1 key.
The answer about DES in CBC mode is quite satisfying but I can't get it to work yet.
Here are more details about the original code:
unsigned char key1[10]= {0};
unsigned char key2[50]= {0};
int i;
for (i=0;i<8;i++)
key1[i] = 31+int((i*sqrt((double)i*5)))%100;
key1[9]=0;
for (i=0;i<48;i++)
key2[i] = 31+int((i*i*sqrt((double)i*2)))%100;
key2[49]=0;
...
// Initialize encrypted buffer
memset(encrypted_buffer, 0, sizeof(encrypted_buffer));
// Add begin Text and End Text to the encrypted message
std::string input;
const char beginText = 2;
const char endText = 3;
input.append(1,beginText);
input.append(bufferToEncrypt);
input.append(1,endText);
// Add padding
tmp.assign(desPad(input));
des_ncbc_encrypt(reinterpret_cast<const unsigned char *>(tmp.c_str()),
reinterpret_cast< unsigned char *>(encrypted_buffer), tmp.length(), ks, &initkey,
DES_ENCRYPT);
...
From what I've read, the key should be 56 (or 64, it's not clear to me) bits long, but here it's 48 bytes long.
I'm not an OpenSSL expert, but I'd guess the C++ code is using DES in CBC mode thus needing an IV (that's what the initKey probably is, and that's why you think you need two keys). If I'm right, you need to change your Java code to use DES in CBC mode too, then the Java code too will require an encryption key and an IV.
Also, keep in mind that you really shouldn't use sun.misc.* classes in your code. This could break in other VMs as these are not public APIs. Apache Commons Codecs (among others) have implementations of Base64 that don't bear this problem.
I'm not really sure why single DES would ever use multiple keys. Even if you were using Triple-DES, I believe you would use a single key (with more bytes of data) rather than using separate keys with the Java Cryptography API.
The algorithms should match; if you're getting different results it may have to do with the way you're handling the keys and the text. Also keep in mind that Java characters are 2 bytes long, which C++ chars are 1 byte, so that may have something to do with it.

Categories