I have a problem with ecrypting data using AES-128/ecb/PKCS5Padding+base64. I am using the following code to encrypt my data:
String input = "{\"action\":\"getQuestion\"}";
String key = "4288f0b8060ca1b682bf795f2617cfdc";
byte[] data = input.getBytes();
byte[] encrypted = null;
byte[] keyBytes = new BigInteger(key, 16).toByteArray();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
encrypted = cipher.doFinal(data);
System.out.println(Base64.encodeBytes(encrypted));
I receive 6GuKXA6FFR+yMmO8ksAEOLL5e574a5tLob7tt5IG+jk= after encryption but I can't decrypt on the server using a PHP function.
When I encrypt this data using the PHP function:
function encrypt($encrypt, $key=null)
{
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $encrypt, MCRYPT_MODE_ECB, $iv));
return $encrypted;
}
I receive 6Wc3LPWvfJ7T86iG0igmdQaeZ8xs9qY419mAVWfNH+M= and I can successfully do the decryption using the following PHP function:
function decrypt($decrypt, $key=null)
{
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($decrypt), MCRYPT_MODE_ECB, $iv);
return $decrypted;
}
With base64 encryption and decryption there are no problems; I only encounter the issue when encrypting using AES-128.
The problem isn't with the IV or the padding as I initially thought. It is with how you are handling the key in the PHP code. If you are using the actual string 4288f0b8060ca1b682bf795f2617cfdc as the key passed into mcrypt_encrypt and mcrypt_decrypt then you aren't using the same key as in the Java code. You will need to convert that hex string into bytes. You can do this in the following way:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, pack("H*", $key), base64_decode($decrypt), MCRYPT_MODE_ECB, $iv);
Notice the addition of pack("H*", $key) to convert the value. I found that here in the comments for the PHP bin2hex function. This will fix the current problem. You may run into padding troubles when working with data of different lengths since PHP doesn't do PKCS5 padding. See this comment about implementing that missing function. Also, I'd recommend looking into CBC instead of ECB due to ECB's unsuitability and weaknesses for data encryption.
You can validate the output from your Java method at the command line using openssl. Java will default your IV to 0 if unspecified.
The file "enc.txt" contains "6GuKXA6FFR+yMmO8ksAEOLL5e574a5tLob7tt5IG+jk=" [corrected]
Run
openssl aes-128-ecb -in enc.txt -a -K 4288f0b8060ca1b682bf795f2617cfdc -iv 0 -d
The result is:
{"action":"getQuestion"}
Try your mcrypt_decrypt with an $iv value of 0.
Related
I have a java code working perfectly
public static String encrypt(String message, String sercretKey)
{
String base64EncryptedString = "";
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digestOfPassword = md.digest(sercretKey.getBytes("utf-8"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
byte[] iv = Arrays.copyOf(digestOfPassword, 16);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte[] base64Bytes = Base64.getEncoder().encode(buf);
base64EncryptedString = new String(base64Bytes);
return base64EncryptedString;
}
I have tried using below code to recreate this above code in PHP
function encryptTest($sSecretKey,$sValue)
{
$key = hash('sha256', $sSecretKey,false);
$key = utf8_encode($key);
$key = substr($key, 0, 24);
$iv = substr($key, 0, 16);
$data = $sValue;
$outEnc = openssl_encrypt($data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($outEnc);
}
But showing different results. What I have missed.
(Same types of questions are available in StackOverflow, but pointing my issues)
There are the following issues:
In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.
With these changes, both codes provide the same ciphertext.
Security:
Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.
I have a sample Java function and would like to make an analogue for PHP:
public static string Encrypt(string plainText, string key)
{
byte[] byteKey = new ASCIIEncoding().GetBytes(key);
MemoryStream mStream = new MemoryStream();
// Create a CryptoStream using the MemoryStream
// and the passed key and initialization vector (IV).
CryptoStream cStream = new CryptoStream(mStream,
new TripleDESCryptoServiceProvider().CreateEncryptor(byteKey, byteKey),
CryptoStreamMode.Write);
// Convert the passed string to a byte array.
byte[] toEncrypt = new ASCIIEncoding().GetBytes(plainText);
// Write the byte array to the crypto stream and flush it.
cStream.Write(toEncrypt, 0, toEncrypt.Length);
cStream.FlushFinalBlock();
// Get an array of bytes from the
// MemoryStream that holds the
// encrypted data.
byte[] ret = mStream.ToArray();
// Close the streams.
cStream.Close();
mStream.Close();
return Convert.ToBase64String(ret);
}
Here is my PHP function, but it doesn't give me the exact result:
function Encrypt($data, $secret)
{
//Generate a key from a hash
$key = md5(utf8_encode($secret), true);
//Take first 8 bytes of $key and append them to the end of $key.
$key .= substr($key, 0, 8);
//Pad for PKCS7
$blockSize = mcrypt_get_block_size('tripledes', 'ecb');
$len = strlen($data);
$pad = $blockSize - ($len % $blockSize);
$data .= str_repeat(chr($pad), $pad);
//Encrypt data
$encData = mcrypt_encrypt('tripledes', $key, $data, 'ecb');
return base64_encode($encData);
}
The string I'm trying to encrypt is:
Java generated string: i00KAKI7U0L7fyvrEnDdB7DtaJNO1HuwRh0/J5aWnCQ=
PHP generated string: 6065L3jC5Oji8NAVzipA4OZZ102j1zFd/KfLgAdnRX0=
Any help is greatly appreciated
It seems that are not using the cipher in the same way.
In your Java code, you are using an IV for your encryption, while you are not doing the same in your PHP code.
That is because ECB, the mode you have specified in your PHP code, doesn't need any initialization, while whatever mode you are using in Java does.
What library are you using?
As stated here here in the reference of Microsoft libraries that look very similar to the one you used with Java, the default encryption mode is
CBC (which is generally considered more secure than ECB). It is very likely that CBC is being used in your library too.
You need to use the same mode of encryption if you want the same result. So, either set CBC (and the same IV you used in Java) in your PHP code, or use TripleDESCryptoServiceProvider to set the ECB mode in Java.
I am encrypting a file in perl and want to decrypt in java. here is my encryption code:
== Encryption in Perl ==
$key = "1234567890123456";
$plain_text = "this is foo";
open ($fh, ">" . $output_file_path) || die ("open ($output_file_path):$!");
my $cipher = Crypt::CBC->new( -key => $key, -cipher => "Crypt::OpenSSL::AES");
$cipher->start("");
print $fh $cipher->crypt($plain_text);
And this is the decryption code I am using, but it is not working.
== Decryption in Java ==
String key = "1234567890123456";
byte[] encrypted_bytes = READ_DATA_FROM_FILE
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(key.getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
String plain_text = new String(cipher.doFinal(encrypted_bytes));
Can someone help me in this?
You don't seem to be specifying the IV for the perl encryption, and you are not passing 'encrypt' to the perl start() method. those are the immediate problems that i notice.
this probably isn't the current problem, but will be a problem for working with "non-trivial" text: you are not being careful with your byte <-> char conversions in java (String.getBytes() and new String()). you are using methods in java which use the default platform character encoding, which may not be what you want. it's best to use an explicit charset.
I have been trying to get Encryption/Decryption to work the same in Java (Android) and PHP and produce the same results for client/server communications.
I'm using the code below for encryption, but I don't know what's wrong with it. Running both with the same key and small strings produce the same encrypted value, with longer strings however, I get two different results.
PHP:
$str = 'test1234test1234';
$key = 'TESTKEYTESTKEY12';
$block = mcrypt_get_block_size('des', 'ecb');
$pad = $block - (strlen($str) % $block);
$str .= str_repeat(chr($pad), $pad);
echo base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB));
and in Java ( on Android ):
public static String encryptTest() {
String cleartext = "test1234test1234";
String key = "TESTKEYTESTKEY12";
byte[] raw = key.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted;
encrypted = cipher.doFinal(cleartext.getBytes());
return new String(Base64.encode(encrypted,Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Running this with String test1234 gives: 8i4KEe82TQl0Zdlc14fwAg== in both implementations.
With string test1234test1234 however I get 4s5a0edsvwWt/3/enRe0wgJQD/0zL45NRb/r3p6L/Is= with PHP, and 4s5a0edsvwWt/3/enRe0wgA0jk78zwWJr1xsosZbYUA= with Java. I'm not sure what's wrong and I'm not knowledgeable enough about Cryptography.
The main problem of your Java code is that you don't specify the cipher mode and the used padding algorithm. Therefore which cipher mode and padding algorithm is used depends on the used crypto provider and in this specific detail Android works different to J2SE.
If I execute your Java code on J2SE I get the same result as you got with PHP. This does not change if I change the code to use Cipher.getInstance("AES/ECB/PKCS5Padding");.
As only the last block of your cipher text changes I assume that Android uses a different padding algorithm by default.
The fact you have different results for short versus long strings suggest you are using different padding on each implementation.
Make sure you use the same type of padding on both your java and your php implementations.
My problem is: what I encrypt in Java I can decrypt perfectly in Java, but PHP mcrypt can't decrypt. What I encrypt with mcrypt I can decrypt with mcrypt, but can't in Java.
I want to send and receive encrypted data from a Java application to a PHP page, so I need it to be compatible.
Here's what I have...
JAVA...
public static String crypt(String input, String key){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(Base64.decodeBase64(key), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
}
return Base64.encodeBase64String(crypted);
}
public static String decrypt(String input, String key){
byte[] output = null;
try{
SecretKeySpec skey = new SecretKeySpec(Base64.decodeBase64(key), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
output = cipher.doFinal(Base64.decodeBase64(input));
}catch(Exception e){
}
return new String(output);
}
Running:
public static void main(String[] args) {
String key = "Zvzpv8/PXbezPCZpxzQKzL/FeoPw68jIb+NONX/LIi8=";
String data = "example";
System.out.println(Cpt.decrypt(Cpt.crypt(data, key), key));
}
Output:
example
PHP...
function getEncrypt($sStr, $sKey) {
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_256,
$sKey,
$sStr,
MCRYPT_MODE_ECB
)
);
}
function getDecrypt($sStr, $sKey) {
return mcrypt_decrypt(
MCRYPT_RIJNDAEL_256,
$sKey,
base64_decode($sStr),
MCRYPT_MODE_ECB
);
}
Running:
$crypt = getDecrypt(getEncrypt($str, $key), $key);
echo "<p>Crypt: $crypt</p>";
Output:
Crypt: example�������������������������
Using PHP to crypt "example" with key "Zvzpv8/PXbezPCZpxzQKzL/FeoPw68jIb+NONX/LIi8=" I get "YTYhgp4zC+w5IsViTR5PUkHMX4i7JzvA6NJT1FqhoGY=".
Using Java to crypt the same thing with the same key I get "+tdAZqTE7WAVPXhB3Tp5+g==".
I'm encoding and decoding to base64 in the right order and I tested base64 encode and decode compatibility between Java and PHP and it's working.
BUG#1
MCRYPT_RIJNDAEL_256 is not AES. The 256 in that constant refers to the blocksize, not the keysize. Use MCRYPT_RIJNDAEL_128 to get the same algorithm as AES. The keysize is set just by the number of bytes in the key argument you supply. So supply 32 bytes and you get AES with a 256-bit key.
BUG#2
These two lines are never correct in Java and indicate a fundamental misunderstanding of the nature of the arbitrary binary data produced by cryptographic transforms:
output = cipher.doFinal(Base64.decodeBase64(input));
return new String(output);
There is nothing wrong with transmitting and storing byte[] directly, but if you must use only printable strings then you should base64 encode/decode to do so. As you are already using base64 extensively that would seem like the way to go. I would guess that the correct two lines would be:
output = cipher.doFinal(Base64.decodeBase64(input));
return new String(Base64.encodeBase64(output), "UTF-8");
EDIT:
Just kidding about bug #2. Really, I was wrong, I didn't notice it was the decrypt direction. Of course, if you know the decrypted byte[] is a valid string then it is perfectly correct to do what your code does.
I know this is an old topic, but I will add my working solution.
You have to rewrite PHP side of the script:
function getEncrypt($sStr, $sKey) {
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
base64_decode($sKey),
$sStr,
MCRYPT_MODE_ECB
)
);
}
function getDecrypt($sStr, $sKey) {
return mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
base64_decode($sKey),
base64_decode($sStr),
MCRYPT_MODE_ECB
);
}
You should base64_decode($sKey) because your key is base64 encoded.
$key = "Zvzpv8/PXbezPCZpxzQKzL/FeoPw68jIb+NONX/LIi8=";
Then, you need to create this function (credit goes to beltrachi from http://www.php.net/manual/en/function.mcrypt-decrypt.php):
function pkcs5_pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
Use this code do encode/decode:
$decrypt = getDecrypt("6XremNEs1jv/Nnf/fRlQob6oG1jkge+5Ut3PL489oIo=", $key);
echo $decrypt;
echo "\n\n";
echo getEncrypt(pkcs5_pad("My very secret text:)", 16), $key);
I hope this will be useful for someone! :)
Please see here:
Difference in PHP encryption from iOS and .NET
AES Encrypt in C#, decrypt in PHP
DES Encryption in PHP and C#
The problem you're encountering is a padding-issue. I don't know Java, but AES/ECB/PKCS5Padding looks like you're using a PKCS#5 (that's essentially the same as PKCS#7) padding while PHP natively only support NULL-padding. That's what PKCS#5/7 does:
Pad the input with a padding string of
between 1 and 8 bytes to make the
total length an exact multiple of 8
bytes. The value of each byte of the
padding string is set to the number of
bytes added - i.e. 8 bytes of value
0x08, 7 bytes of value 0x07, ..., 2
bytes of 0x02, or one byte of value
0x01.
So the PHP code to do the padding right is trivial:
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$padding = $blockSize - (strlen($data) % $blockSize);
$data .= str_repeat(chr($padding), $padding);
Keep in mind to have the same encoding for the strings. Try to convert the strings in both languages to UTF-8, e.g., and than convert to binary data that is encoded:
PHP (s. utf8_encode() function):
$strAndBlob = utf8_encode("My string");
Java:
String str = "My string";
byte[] blob = str.getBytes("utf-8");
PHP, e.g., must not use UTF-8 by default.