I have encrypted files using AES encryption in php with following code.
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
openssl_encrypt($contenuto, $ALGORITHM, $password, 0, $IV);
Now I am trying to decrypt it in Android but always I face InvalidKeyException: Key length not 128/192/256 bits error. Here is android code:
String initializationVector = "12dasdq3g5b2434b";
String password = "123";
FileInputStream fis = new FileInputStream(cryptFilepath);
FileOutputStream fos = new FileOutputStream(outputFilePath);
byte[] key = (password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key,16);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(initializationVector.getBytes()));
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[16];
while((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();
Can anyone suggest me how can I do it. Any help would be appreciated.
The original code posted in the question uses streams to read, decrypt and write the respective files. This makes it possible to process files that are larger than the available memory.
However, the originally posted code lacks the Base64 decoding, which is necessary because the ciphertext of the PHP code is Base64 encoded.
Base64 decoding can be comfortably achieved using the Base64InputStream class of Apache Commons Codec, which operates between FileInputStream and CipherInputStream and is therefore easy to integrate:
import org.apache.commons.codec.binary.Base64InputStream;
...
public static void decrypt(String ciphertextFilepath, String decryptedFilePath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16];
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
System.arraycopy(passwordBytes, 0, key, 0, passwordBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis);
CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
int read;
byte[] buffer = new byte[16]; // 16 bytes for testing, in practice use a suitable size (depending on your RAM size), e.g. 64 Mi
while((read = cis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
}
}
The other fixed bugs / optimized points are (see also the other answers / comments):
Use of the CBC mode analogous to the PHP code
Use of the key from the PHP code
Explicit specification of the used encoding
Edit: Consideration of an IV, see #Michael Fehr's comment.
Usually a new random IV is generated for each encryption. The IV is not secret and is commonly placed before the ciphertext and the result is Base64 encoded. The recipient can separate both parts, because the size of the IV is known (corresponds to the blocksize). This construct can also be used in combination with the Base64InputStream class, where the IV must be determined between the Base64InputStream instantiation and the Cipher instantiation/initialization:
...
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
Base64InputStream b64is = new Base64InputStream(fis)){
byte[] iv = b64is.readNBytes(16); // 16 bytes for AES
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
try (CipherInputStream cis = new CipherInputStream(b64is, cipher);
FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
...
If during encryption IV and ciphertext are Base64 encoded separately and then concatenated, delimited by a separator (see #Michael Fehr's comment), the determination of the IV must be done between the FileInputStream and Base64InputStream instantiation (the separator must also be flushed).
The following full working examples show how to deal with the password issue and do Base64-decoding, the examples just work with string instead of files. Please keep in mind what #Topaco stated as openssl encodes the output in a Base64-encoding that needs to get converted to a byte-format before the file can get used with CipherInputStream for decryption!
A third point (not realy a bug) is that on Java/Android side you don't set a charset for the conversion from string to byte arrays - just add the StandardCharsets.UTF_8 and your're fine with that point.
Beware that there is no proper exception handling !
This is my sample PHP-code:
<?php
// https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
$plaintext = "my content to encrypt";
echo 'plaintext: ' . $plaintext . PHP_EOL;
$ciphertext = openssl_encrypt($plaintext, $ALGORITHM, $password, 0, $IV);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
$decryptedtext = openssl_decrypt($ciphertext, $ALGORITHM, $password, 0, $IV);
echo 'decryptedtext: ' . $decryptedtext . PHP_EOL;
?>
Output on PHP-side:
plaintext: my content to encrypt
ciphertext: DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4=
decryptedtext: my content to encrypt
Sample Java-code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class DecryptInJava {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android");
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
String ciphertext = "DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4="; // password 123 in openssl
// openssl encodes the output in base64 encoding, so first we have to decode it
byte[] ciphertextByte = Base64.getDecoder().decode(ciphertext);
// creating a key filled with 16 'x0'
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
// copying the password to the key, leaving the x0 at the end
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
// don't use just AES because that defaults to AES/ECB...
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtext = cipher.doFinal(ciphertextByte);
System.out.println("decryptedtext: " + new String(decryptedtext));
}
}
Output on Java-side:
decryptedtext: my content to encrypt
With the help of Michael's answer, I wrote down the code which successfully decrypts the file.
Add following dependencies in your app level build.gradle file:
implementation 'commons-io:commons-io:2.7'
implementation 'commons-codec:commons-codec:1.13'
Java code:
public static void decrypt(String path, String outPath) throws Exception {
String password = "123";
String initializationVector = "12dasdq3g5b2434b";
byte[] key = new byte[16]; // 16 bytes for aes cbc 128
System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] input_file;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
input_file = Files.readAllBytes(Paths.get(path));
} else {
input_file = org.apache.commons.io.FileUtils.readFileToByteArray(new File(path));
}
byte[] decodedBytes;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
decodedBytes = Base64.getDecoder().decode(input_file);
} else {
decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(input_file);
}
byte[] decryptedtext = cipher.doFinal(decodedBytes);
FileOutputStream fos = new FileOutputStream(outPath);
fos.write(decryptedtext);
fos.flush();
fos.close();
}
I hope it will help.
Good answers but want to add my 2 cents regarding Usually a new random IV is generated for each encryption. Change Usually to NEED TO or MUST. I don't know of a reason NOT to use a new IV each time. If you don't and happen to use the same key, the same data will encrypt to the same output each time. This means that an attacker can tell if the decrypted values are the same just by comparing the encrypted values. Encryption protects the original data from prying eyes, you still have to make sure they can't determine anything about the original data as well. If you encrypt enough data, using the same Key and IV for each record, that can give an hacker a place to start an attacking your systems.
Related
The GST Council has approved the implementation of ‘e-Invoicing’ or ‘electronic invoicing’ of Business to Business (B2B) invoices to GST System if company turnover is greater that 500 crore. Ref of gst portal API :einv-apisandbox.nic.in/index.html
I have to decrypt the encrypted SEK using APP key and encode json data using decrypted SEK to post for Einvoice Generation and i found sample code for java and C# and i have converted in PHP but unfortunately failed to get desired output
In my case encrypted SEK is: oRvKfBtmgNTSuk/oXUhiLOjXi45jiWA2oKNxhhQM3UH2o/32YWGLbUjK1/dohPe3
APP key: fao1PoKaLgd11xMrWTiL2cggAfx9QMwM
Symmetric decryption (AES) (in java)
public static String decrptyBySyymetricKey(String encryptedSek, byte[] appKey)
{
Key aesKey = new SecretKeySpec(appKey, "AES"); // converts bytes(32 byte random generated) to key
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // encryption type = AES with padding PKCS5
cipher.init(Cipher.DECRYPT_MODE, aesKey); // initiate decryption type with the key
byte[] encryptedSekBytes = Base64.decodeBase64(encryptedSek); // decode the base64 encryptedSek to bytes
byte[] decryptedSekBytes = cipher.doFinal(encryptedSekBytes); // decrypt the encryptedSek with the initialized cipher containing the key(Results in bytes)
String decryptedSek = Base64.encodeBase64String(decryptedSekBytes); // convert the decryptedSek(bytes) to Base64 StriNG
return decryptedSek; // return results in base64 string
}catch(Exception e) {
return "Exception; "+e;
}
}
Symmetric encryption (AES) (in java)
public static string EncryptBySymmetricKey(string text, string sek)
{
//Encrypting SEK
try
{
byte[] dataToEncrypt = Convert.FromBase64String(text);
var keyBytes = Convert.FromBase64String(sek);
AesManaged tdes = new AesManaged();
tdes.KeySize = 256;
tdes.BlockSize = 128;
tdes.Key = keyBytes;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
pICryptoTransform encrypt__1 = tdes.CreateEncryptor();
byte[] deCipher = encrypt__1.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
tdes.Clear();
string EK_result = Convert.ToBase64String(deCipher);
return EK_result;
}
catch (Exception ex)
{
throw ex;
}
}
Symmetric encryption (AES) (in PHP)
function encrypt($data, $key)
{
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, hash('SHA256', $key, true), $data, MCRYPT_MODE_ECB));
}
Symmetric decryption (AES) (in PHP)
function decrypt($key, $str)
{
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, hash('SHA256', $key, true), $str, MCRYPT_MODE_ECB);
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len-1]);
return base64_encode( substr($str, 0, strlen($str) - $pad));
}
The decryption of the session key (SEK) with the AppKey is possible in PHP as follows:
function decrptyBySymmetricKey($encSekB64, $appKey) {
$sek = openssl_decrypt($encSekB64, "aes-256-ecb", $appKey, 0); // the SEK
$sekB64 = base64_encode($sek); // the Base64 encoded SEK
return $sekB64;
}
The encryption of data with the SEK is possible in PHP as follows:
function encryptBySymmetricKey($dataB64, $sekB64){
$data = base64_decode($dataB64); // the data to encrypt
$sek = base64_decode($sekB64); // the SEK
$encDataB64 = openssl_encrypt($data, "aes-256-ecb", $sek, 0); // the Base64 encoded ciphertext
return $encDataB64;
}
Both functions can be tested with the following data:
$appKey = 'fao1PoKaLgd11xMrWTiL2cggAfx9QMwM'; // the 32 bytes AppKey
$encSekB64 = 'oRvKfBtmgNTSuk/oXUhiLOjXi45jiWA2oKNxhhQM3UH2o/32YWGLbUjK1/dohPe3'; // the Base64 encoded encrypted SEK
$dataB64 = 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='; // the base64 encoded data
$sekB64 = decrptyBySymmetricKey($encSekB64, $appKey); // the Base64 encoded SEK
$encDataB64 = encryptBySymmetricKey($dataB64, $sekB64); // the Base64 encoded ciphertext
echo $sekB64 . "\n"; // zVoede7m2nnvMHcWYIfKhrvsilSFEZYiltJmxVQQnAQ=
echo $encDataB64; // JS+hxYf64FMHThrhoIejqk3VjGwFw+GTYzUyVKc6GEOLKERVuvaNY91zPdo829r0
The Java method decryptBySymmetricKey returns with
byte[] appKey = "fao1PoKaLgd11xMrWTiL2cggAfx9QMwM".getBytes(StandardCharsets.UTF_8);
String encSekB64 = "oRvKfBtmgNTSuk/oXUhiLOjXi45jiWA2oKNxhhQM3UH2o/32YWGLbUjK1/dohPe3";
String sekB64 = decryptBySymmetricKey(encSekB64, appKey);
the same value for the Base64 encoded SEK (zVoede7m2nnvMHcWYIfKhrvsilSFEZYiltJmxVQQnAQ=).
Likewise, the C# method EncryptBySymmetricKey (erroneously labeled in the question as Java function) returns with
string dataB64 = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==";
string sekB64 = "zVoede7m2nnvMHcWYIfKhrvsilSFEZYiltJmxVQQnAQ=";
string encDataB64 = EncryptBySymmetricKey(dataB64, sekB64);
the same value for the Base64 encoded ciphertext (JS+hxYf64FMHThrhoIejqk3VjGwFw+GTYzUyVKc6GEOLKERVuvaNY91zPdo829r0).
Note:
The deprecated mcrypt is not used. Instead openssl is applied.
Besides the security aspect, openssl has the advantage over mcrypt, that PKCS7 padding is implicitly used, analogous to the C#/Java code. mcrypt applies Zero padding, so a user defined padding was necessary, which is obsolete with openssl.
The key is not derived from the passed key via SHA256, but applied directly, analogous to the C#/Java code.
You also might need to try with postman tool. I achieve the same and able to connect the NIC system but as per the document they have provided it's not possible to achieve the same encryption using OpenSSL or other encryption tool.
Now I moved on to GSP based API connectivity solution you might check this stuff from
https://github.com/sujianalytics/gst-e-invoicing-sap
It's open source but not relevant for your issue, might need to upgrade little bit as per your requirement.
I have to decrypt a pass phrase that was encoded in Java using
byte[] phraseBlock = [-9, -101, -14, -63, 26, 127, -128, 121];
SecretKey secretKey = new SecretKeySpec(keyBytes, "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedPhaseBlock = cipher.doFinal(phraseBlock );
Now after encrypting this it is send to my PHP code
Hex encryption phraseBlock = F79BF2C11A7F8079
Hex encrypted encryptedPhaseBlock = A8B0E35246430D25
openssl_decrypt(hex2bin('F79BF2C11A7F8079'), 'DES-ECB', hex2bin('A8B0E35246430D25'))
After doing this i get false.
I don't know what "byte[] phraseBlock" means, but if You try like this:
bin2hex(openssl_decrypt(hex2bin('F79BF2C11A7F8079'), 'des-ecb', hex2bin('A8B0E35246430D25'), OPENSSL_RAW_DATA + OPENSSL_ZERO_PADDING));
You get result
f699257c6ec30770
I am attempting to reproduce an encryption operation using AES-256-CCM that is currently performed in Java with the Bouncy Castle provider. When attempting the same operation in PHP using openssl I cannot find a set of parameters that produces the same output.
As the AEAD modes were recently added to PHP (7.1), documentation on how this works is scarce.
A minimum example of the "working" encryption in Java looks like:
public static void main(String args[]) {
try {
java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.decodeBase64("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = Base64.decodeBase64("rcFcdcgZ3Q/A+uHW".getBytes());
SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));
byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: 411d89ff74205c106d8d85a8
}
catch (Throwable e) {
e.printStackTrace();
}
}
As I am trying to re-produce this using different two different libraries and languages I have set the key and iv to known values.
When trying to re-produce this using PHP and openssl I am trying with the following code
$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = base64_decode('rcFcdcgZ3Q/A+uHW');
$data = 'test';
$tag = null;
$encrypted = openssl_encrypt($data,'aes-256-ccm', $key,OPENSSL_RAW_DATA, $iv, $tag,"",8);
echo(bin2hex($encrypted . $tag));
// d1a7403799b8c37240f36edb
Clearly the results do not match. In search of an answer as to what is incorrect I created the same operation using SJCL in javascript. The example for that is:
var data = "test";
var key = sjcl.codec.base64.toBits("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
var iv = sjcl.codec.base64.toBits("rcFcdcgZ3Q/A+uHW");
var p = {
adata: "",
iter: 0,
mode: "ccm",
ts: 64,
ks: 256,
iv: iv,
salt: ""
};
var encrypted = sjcl.encrypt(key, data, p, {});
console.log(encrypted);
// Output: {"iv":"rcFcdcgZ3Q/A+uHW","v":1,"iter":0,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"","ct":"QR2J/3QgXBBtjYWo"}
// QR2J/3QgXBBtjYWo === 411d89ff74205c106d8d85a8
The Bouncy Castle and SJCL libraries produce the same output but I can't tell what is different.
I have tried pre-processing the key with PBKDF2 as suggested in Encrypt in Javascript with SJCL and decrypt in PHP with no success. I have tried SHA256'ing the key with no success.
Why is the output in php/openssl different than Bouncy Castle and SJCL?
When I stumbled upon a similar problem, I discovered that the problem resided in the IV, more precisely: the length of it. As far as You use an IV with the length under 12, it results with the same hashes. You can try it with your own code:
java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.getDecoder().decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = "12345678901".getBytes();
SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));
byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: e037af9889af21e78252ab58
and same with PHP:
$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = "12345678901";
$tag = null;
$encrypted = openssl_encrypt("test", "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, null, 8);
print bin2hex($encrypted . $tag);
# e037af9889af21e78252ab58
If you would extend the IV, you'll see the results will differ.
NB! Keep in mind that if you'd shorten the AES key (to 128 bytes), then Java will automatically switch to aes-128, but in PHP you have to change the algorithm manually.
I have a PHP application that needs to encrypt a challenge string that is consumed by an ASP.net web service however the output of my PHP implementation is not properly decrypted by .net. Exactly as in this question relating to iOS and .net: AES128 bit encryption string is not similar as on .net
Why are the outputs different and what can I do to my PHP to output the same as .net?
My PHP code looks like this:
$key = '128bit-16bytekey';
$value = '128bit-16byteval';
function fnEncrypt($value, $key)
{
$ivsize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($ivsize);
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
$key, $value,
MCRYPT_MODE_ECB,$iv
));
}
.net like this
public static string EncryptData(string plainText)
{
string encryptionKey = AppConstants.AES_ENCRYPTDECRYPT_KEY;
// Convert our plaintext into a byte array.
// Let us assume that plaintext contains UTF8-encoded characters.
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
// Use the password to generate pseudo-random bytes for the encryption
// key. Specify the size of the key in bytes (instead of bits).
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] keyBytes = new byte[16];
byte[] tempKey = encoding.GetBytes(encryptionKey);
for (int i = 0; i < keyBytes.Length; i++)
{
if (i < tempKey.Length)
{
keyBytes[i] = tempKey[i];
}
else
{
keyBytes[i] = 0;
}
}
// Create uninitialized Rijndael encryption object.
RijndaelManaged symmetricKey = new RijndaelManaged();
//AesManaged symmetricKey = new AesManaged();
//symmetricKey.Padding = PaddingMode.PKCS7;
// It is reasonable to set encryption mode to Cipher Block Chaining
// (CBC). Use default options for other symmetric key parameters.
symmetricKey.Mode = CipherMode.ECB;
// Generate encryptor from the existing key bytes and initialization
// vector. Key size will be defined based on the number of the key
// bytes.
//ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes,initVectorBytes);
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, null);
// Define memory stream which will be used to hold encrypted data.
MemoryStream memoryStream = new MemoryStream();
// Define cryptographic stream (always use Write mode for encryption).
CryptoStream cryptoStream = new CryptoStream(memoryStream,
encryptor,
CryptoStreamMode.Write);
// Start encrypting.
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
// Finish encrypting.
cryptoStream.FlushFinalBlock();
// Convert our encrypted data from a memory stream into a byte array.
byte[] cipherTextBytes = memoryStream.ToArray();
// Close both streams.
memoryStream.Close();
cryptoStream.Close();
// Convert encrypted data into a base64-encoded string.
string cipherText = Convert.ToBase64String(cipherTextBytes);
// Return encrypted string.
return cipherText;
}
Sample outputs
PHP : Q54nP/tXq2rDTUwWw4ckkg==
.net : Q54nP/tXq2rDTUwWw4ckkpSt9CQiIzsg2xsQEndcqc8=
PHP : DQZdAB/lABXVOOoCdNM6HQ==
.net : DQZdAB/lABXVOOoCdNM6HZSt9CQiIzsg2xsQEndcqc8=
As in the question ref'd above the right hand side of the .net output are always the same and left side are consistent between the two implementations. I am pretty sure the IV is irrelevant and that it is something to do with how padding is handled in mcrypt.
If I decrypt either of the outputs in PHP it returns the same correct result.
Can anyone shed any light? I am unable to change the .net app. Thanks!
This happens due to the padding that is applied in .net. You can overcome this by adding the following line to your .net code:
symmetricKey.Padding = PaddingMode.None;
Or you can change the PHP code to get the same encrypted string:
// Add the lines below to your fnEncrypt function
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$len = strlen($value);
$padding = $block - ($len % $block);
$value .= str_repeat(chr($padding),$padding);
A similar issue is described in one of the comments from PHP manual pages: http://www.php.net//manual/en/function.mcrypt-encrypt.php#47973
Hi i have c# sample of code but i can't turn it to php.
İ tried to rewrite code but i can't do it.
In my project other server encrypts data with c# and i have to decrypt it using PHP.
I have password and salt value.
Here is C# code includes encrypt and decrypt function.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace EncryptionSample
{
public static class CipherUtility
{
public static string Encrypt(string plainText, string password, string salt)
{
if (plainText == null || plainText.Length <= 0)
{
throw new ArgumentNullException("plainText");
}
if (String.IsNullOrEmpty(password))
{
throw new ArgumentNullException("password");
}
if (String.IsNullOrEmpty(salt))
{
throw new ArgumentNullException("salt");
}
byte[] encrypted;
byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
using (Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(password, saltBytes))
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = derivedBytes.GetBytes(32);
aesAlg.IV = derivedBytes.GetBytes(16);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
}
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string cipherValue, string password, string salt)
{
byte[] cipherText = Convert.FromBase64String(cipherValue);
if (cipherText == null
|| cipherText.Length <= 0)
{
throw new ArgumentNullException("cipherValue");
}
if (String.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
if (String.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("salt");
}
string plaintext = null;
byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
using (Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, saltBytes))
{
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = deriveBytes.GetBytes(32);
aesAlg.IV = deriveBytes.GetBytes(16);
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
}
return plaintext;
}
}
}
My php code is here but i think i am totally wrong.
function decrypt($encrypted, $password, $salt) {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// print_r($iv);die();
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
//print_r($encrypted);die();
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any \0 character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
return $decrypted;
}
On first glance, I can see that your keys are going to be different. Your C# code generates your key using Rfc2898DeriveBytes, which is a key generator based on PBKDF2. Your php code, on the other hand, is using SHA256 to generate the key. These are going to return different values. With different keys, you are done before you even start.
Also, I don't know that CryptoStream is going to append the IV on the beginning of the ciphertext, nor a MAC value at the end of the ciphertext. Stripping out that text will make your plaintext garbled if it will decrypt at all. Note in the C# decryption method you derive the IV based on the key derivation object (which is not smart, since the same key will generate the same IV for every message, which reduces the security of the first block of your ciphertext, but that's an entirely separate issue).
Do you know for a fact that the C# server is generating the ciphertext exactly the same as your code sample? You need to know the exact parameters of the cryptography being used on the server side
I would suggest that you actually try to research and understand the format of the ciphertext that C# is going to emit, then figure out how to consume that in PHP. Cryptography can be very tricky to work with, especially when trying to integrate heterogenous systems.
I'm no crypto expert, but I think you might find phpseclib useful.