I am having some problems decrypting some text sent from PHP in Java. I have already written code that does this for a windows version of the program in C# but I am unfamiliar with Java so this might be more simple than I am making it.
The encryption code in PHP:
function encryptString($plain)
{
$iv = "12347112549354892543218565456541";
$ftpSalt = "hjjuoelkdploida";
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$padding = $block - (strlen($plain) % $block);
$plain .= str_repeat(chr($padding), $padding);
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $ftpSalt, $plain, MCRYPT_MODE_CBC, $iv);
$crypttext64 = base64_encode($crypttext);
return $crypttext64;
}
The C# function that decrypts:
static public String doDecryptRJ256(string cypher)
{
var sRet = "";
var cypherByte = Decode(cypher);
var encoding = new UTF8Encoding();
var Key = encoding.GetBytes(mKey);
var IV = encoding.GetBytes(mIv);
using (var rj = new RijndaelManaged())
{
try
{
rj.Padding = PaddingMode.PKCS7;
rj.Mode = CipherMode.CBC;
rj.KeySize = 256;
rj.BlockSize = 256;
rj.Key = Key;
rj.IV = IV;
var ms = new MemoryStream(cypherByte);
using (var cs = new CryptoStream(ms, rj.CreateDecryptor(Key, IV), CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
sRet = sr.ReadLine();
}
}
}
finally
{
rj.Clear();
}
}
return sRet;
}
}
I have tried various suggestions from the internet but not found any that actually work. THe latest suggestion I tried to another answer was:
byte[] cipherText = encryptedText.getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(cipher.doFinal(cipherText),"UTF-8");
I know similar questions have been asked previously but the solutions (possibly due to my own ignorance) haven't helped me to get this working. I haven't done any encryption work before so there is probably something that I am just not getting.
The code to encrypt and decrypt are within my control so if someone wants to suggest an alternative method for all three languages I am open to changing the encryption method.
Related
All,I am posting some encrypted xml data(Using AES-128 ) to another application that uses Java to decrypt.When the Java code decrypts the xml,the start tag of the xml is getting truncated and fails validation.I don't have access to their code base .I can decrypt the same data using C# without any data loss.Please see the code I use to encrypt and Decrypt the data . I have researched this and based on the research ,I added the FlushFinalBlocks() and Close() to the CryptoStream in the encryption logic ,but this doesnt seem to fix the issue.
Encryption Code:
public static string Aes128Encrypt(string plainText)
{
string encodedPayload = null;
string base64Iv = null;
string base64Key = null;
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.KeySize = 128;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.BlockSize = 128;
base64Iv = Convert.ToBase64String(aesAlg.IV);
base64Key = Convert.ToBase64String(aesAlg.Key);
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(plainBytes, 0, plainBytes.Length);
csEncrypt.FlushFinalBlock();
encodedPayload = Convert.ToBase64String(msEncrypt.ToArray());
csEncrypt.Close();
}
msEncrypt.Flush();
msEncrypt.Close();
}
}
return encodedPayload ;
}
Decryption Code:
public static string Aes128Decrypt(string base64Key, string base64IV, string encodedPayload)
{
string plainText = null;
byte[] key = Convert.FromBase64String(base64Key);
byte[] iv = Convert.FromBase64String(base64IV);
byte[] encryptedBytes = Convert.FromBase64String(encodedPayload);
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.KeySize = 128;
aesAlg.Mode = CipherMode.CBC;
aesAlg.BlockSize = 128;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = key;
aesAlg.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(encryptedBytes))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plainText = srDecrypt.ReadToEnd();
}
}
}
}
return plainText;
}
Testing Code:
string textXml = #"<person>
<firstName>Rennish</firstName>
<lastName>Joseph</lastName>
<accountNumber>12345678910</accountNumber>
<ssn>123456</ssn>
</person>";
Aes128Encrypt(textXml);
string encodedPayload = "4p6uU7SiqB0uCzsrWXMOStP02HM7mKA6QVzcKoNdu3w1+MYLjYVbW/Ig3XPKRRafeu+WKDMuKJJaEREkrZt/Ycvc50wfe2naJ9d0UT5B7Fre1gIsNfZUIK3SF304+WF8zX730mVsluJABKT3JCkk9AkOGCQWPYzcZvH9dojIqGP7V+2j1+IMOPMWWFIitkAi8B7ALxMuMcepzX2/cxHxH7NeID0ytEGUzGfJXSAzQcvBX9dWwUqdMX3Eip5SRPMsotnWWsFTjDuOiZk/q5fuxxWbS6cuYn/64C/vQjEIuheQKn0ZOIDLNPCUavvWD2u6PWNKMNgW/qUIq13W9PQxzIiQxrT7ZqPFJu75C1KdXXUG5lghU7EBAGehHC/5BqFjs9SuYJkV1RrchMEzytrJIQ7Zp4CnOU6Q1rEhFTaMk/s=";
string encodedKey = "2zpVbIxqvjSfJo7zkXzl2A==";
string encodedIV = "5WOQPdmB/BkECmuPdNTaLw==";
Aes128Decrypt(encodedKey, encodedIV, encodedPayload);
Data after encryption at the JAVA application looks like this
<rson>
<firstName>Rennish</firstName>
<lastName>Joseph</lastName>
<accountNumber>12345678910</accountNumber>
<ssn>123456</ssn>
</person>
Interesting problem.
I think the encryption and decryption works fine on both sides.
If part of the encrypted message was lost in transmission you would not be able to decrypt it due to the avalanche effect. So it appears that characters go missing in the plain text.
This might be an encoding issue in the plain text message. The bytes you have encoded and the bytes they decoded are probably the same. The way they are interpreted might not be.
Now there are two options here:
Either <person> becomes <rson> or it becomes rson> and there was a copy-paste mistake.
If the latter case is true then we're missing 3 bytes. This makes me think that the protocol might presume the presence of a byte order marker andsimply removes the first 3 bytes to get rid of it.
If the former case you'd have some very weird encoding issues. As all missing characters appear to be in the ascii range so they shouldn't have these issues.
Easy to test though:
1. Try sending with a byte order marker.
2. Try sending with <XXperson>
3. Try sending some characters with accents and the like.
I'm currently running into an issue where our decryption portion of our C# site is having trouble with the padding with the encrypted string from java. The .Net code throws this error "Padding is invalid and cannot be removed". The _signKey and _encKey are both 64 bytes.
public String encryptString(String plainText) {
byte[] ciphertext;
byte[] iv = new byte[16];
byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
String _signKey = "****************************************************************";
String _encKey = "****************************************************************";
try {
Mac sha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec shaKS = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256.init(shaKS);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
// Perform Encryption
SecretKeySpec eks = new SecretKeySpec(sessionKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, eks, ivParams);
ciphertext = cipher.doFinal(plainBytes);
System.out.println("ciphertext= " + new String(ciphertext));
// Perform HMAC using SHA-256 on ciphertext
SecretKeySpec hks = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(hks);
ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
outputStream2.write(iv);
outputStream2.write(ciphertext);
outputStream2.flush();
outputStream2.write(mac.doFinal(outputStream2.toByteArray()));
return Base64.encodeBase64String(outputStream2.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}
return plainText;
}
Does does encrypt the string properly as far as I can tell. We cannot change any code on the .Net side to decrypt this because this is being used today.
public static string DecryptString(string ciphertext)
{
using (HMACSHA256 sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_signKey)))
{
// Convert message to bytes
byte[] encBytes = Convert.FromBase64String(ciphertext);
// Get arrays for comparing HMAC tags
byte[] sentTag = new byte[sha256.HashSize / 8];
byte[] calcTag = sha256.ComputeHash(encBytes, 0, (encBytes.Length - sentTag.Length));
// If message length is too small return null
if (encBytes.Length < sentTag.Length + _ivLength) { return null; }
// Copy tag from end of encrypted message
Array.Copy(encBytes, (encBytes.Length - sentTag.Length), sentTag, 0, sentTag.Length);
// Compare tags with constant time comparison, return null if no match
int compare = 0;
for (int i = 0; i < sentTag.Length; i++) { compare |= sentTag[i] ^ calcTag[i]; }
if (compare != 0) { return null; }
using (AesCryptoServiceProvider csp = new AesCryptoServiceProvider())
{
// Set parameters
csp.BlockSize = _blockBits;
csp.KeySize = _keyBits;
csp.Mode = CipherMode.CBC;
csp.Padding = PaddingMode.PKCS7;
// Copy init vector from message
var iv = new byte[_ivLength];
Array.Copy(encBytes, 0, iv, 0, iv.Length);
// Derive session key
byte[] sessionKey = sha256.ComputeHash(Encoding.UTF8.GetBytes(_encKey + iv));
// Decrypt message
using (ICryptoTransform decrypt = csp.CreateDecryptor(sessionKey, iv))
{
return Encoding.UTF8.GetString(decrypt.TransformFinalBlock(encBytes, iv.Length, encBytes.Length - iv.Length - sentTag.Length));
}
}
}
}
If there is anything that sticks out it would be appreciated for the reply.
I didn't read all your code, but this line in Java:
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
does nothing useful or sensible. The "+" operator does string concatenation, but iv is a byte[], not a String. So java uses iv.toString(), which simply returns a String containing something like [B#1188e820 which is meaningless in this context.
Refer four java code and DotNet code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //Java
csp.Padding = PaddingMode.PKCS7; //.Net
You are essentially using different padding, that is the probable source of error; however, there is an alternate view, Refer this great post and this for general fundamentals on padding
The cipher suites supported by deafult Oracle JVM implementation are here
If you notice it does not have 'AES/CBC/PKCS7Padding', a PKCS#7 padding implementation is available in sun.security package, refer this, otherwise you could use Bouncy Castle packages. It would be recommendable to use Bouncy Castle as com.sun package are generally considered unsupported.
I'm trying to convert below java code into nodejs.
public static String encrypt(String accessToken) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
String merchantKey = "11111111111111111111";
String st = StringUtils.substring(merchantKey, 0, 16);
System.out.println(st);
Key secretKey = new SecretKeySpec(st.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedByte = cipher.doFinal(accessToken.getBytes());
// convert the byte to hex format
StringBuffer sb = new StringBuffer();
for (int i = 0; i < encryptedByte.length; i++) {
sb.append(Integer.toString((encryptedByte[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
Here is what I was able to figure out-
function freeChargeEncryptAES(token){
var fcKey = "11111111111111111111".substring(0, 16);
var cipher = crypto.createCipher('aes-128-ecb', fcKey, "");
var encrypted = cipher.update(token,'ascii','hex');
encrypted += cipher.final('hex');
return encrypted;
}
I'm not able to get same output. For example if
token = "abcdefgh"
Java Code output - bc02de7c1270a352a98faa686f155df3
Nodejs Code output - eae7ec6943953aca94594641523c3c6d
I've read from this answer that by default encryption algorithm is aes-ecb which does not need IV. As the key length is 16, I'm assuming aes-128-ecb (16*8 = 128) is the algorithm that I should use.
Can someone help me figure out the problem ??
Just need to change -
crypto.createCipher('aes-128-ecb', fcKey, "");
to
crypto.createCipheriv('aes-128-ecb', fcKey, "");
Reason is simple - createCipher method treats second parameter as Encryption Password while it is an Encryption Key.
My bad, even after reading this answer, I've used wrong method (crypto.createCipher instead of crypto.createCipheriv). Below is proper working code in nodejs. That was all needed.
function freeChargeEncryptAES(token){
var fcKey = "11111111111111111111".substring(0, 16);
var cipher = crypto.createCipheriv('aes-128-ecb', fcKey, "");
var encrypted = cipher.update(token,'ascii','hex');
encrypted += cipher.final('hex');
return encrypted;
}
I am trying to decrypt an AES encrypted string from Java, in C#. When I decrypt, it returns gibberish and does not match the original plain text, which was encrypted via Java code. Pls guide me on what is going wrong here.
Attached the Java code for encryption and the C# code for decryption. Pls let me know if you need more details.
I tried AesCryptoServiceProvider as well and it did not work either. You can see the code tried in the commented code in C#.
Pls note that I can make changes on my C# code only to match the Java code and can not make any edits to Java side.
Java code for Encryption:
/** encrypt cipher */
private static final Cipher ENCRYPT_CIPHER = generateCipher(Cipher.ENCRYPT_MODE);
private static String ENCRYPT_KEY = "key";
/**
* #param val
* #return encrypted value
* #throws Exception
*/
public String encrypt(final String val) throws Exception {
return new String(Base64.encodeBase64(ENCRYPT_CIPHER.doFinal(val.getBytes()), true)).toString();
}
/**
* #param encrypt
* #return cipher
*/
protected static Cipher generateCipher(final int encrypt) {
try {
final Cipher cipher = Cipher.getInstance("AES");
cipher.init(encrypt, SecretKeyFactory.getInstance("AES").generateSecret(new IBMAESKeySpec(Base64.decodeBase64(ENCRYPT_KEY.getBytes()))));
return cipher;
} catch (final Exception e) {
return null;
}
}
C# code for decryption:
private static String ENCRYPT_KEY = "key";
public String decodeString (String encodedStr)
{
/*using (var aesCryptoProvider = new AesCryptoServiceProvider())
{
aesCryptoProvider.BlockSize = 128;
aesCryptoProvider.KeySize = 256;
aesCryptoProvider.Key = Convert.FromBase64String(ENCRYPT_KEY.ToString());
aesCryptoProvider.Padding = PaddingMode.Zeros;
aesCryptoProvider.Mode = CipherMode.ECB;
using (var decryptor = aesCryptoProvider.CreateDecryptor())
using (var memoryStream = new MemoryStream(Convert.FromBase64String(encodedStr)))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream, Encoding.UTF8))
{
decodedStr = streamReader.ReadToEnd();
}
}
*/
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Convert.FromBase64String(ENCRYPT_KEY.ToString()); ;
aesAlg.BlockSize = 128;
aesAlg.KeySize = 256;
aesAlg.Mode = CipherMode.ECB;
aesAlg.Padding = PaddingMode.Zeros;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor();
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(encodedStr)))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
decodedStr = srDecrypt.ReadToEnd();
}
}
}
}
}
This is just a quick answer. Haven't done a ton of research into it, but have you checked to see if the endian-ness matches? It looks like C# (.NET) is little-endian, but the JVM is big-endian. I'm not sure if it swaps it for network transmission, however (then it would just match the hardware). Just an idea. If I find anything additional, I'll update my answer.
Here is the situation:
The encrypted text is done in JAVA (which we have no JAVA background at all)
The method is 3DES
The padded is PKCS#5
Base 64
The decryption will be in C#, and here is the code:
public static string DecryptString(string Message, string Passphrase)
{
byte[] Results;
UTF8Encoding UTF8 = new UTF8Encoding();
MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
TDESAlgorithm.Key = TDESKey;
TDESAlgorithm.Mode = CipherMode.ECB;
TDESAlgorithm.Padding = PaddingMode.PKCS7;
byte[] DataToDecrypt = Convert.FromBase64String(Message);
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
HashProvider.Clear();
}
return UTF8.GetString(Results);
}
However, when tried to decrypt, got the error message: BAD DATA
Where am I missing here?
Thanks in advance.
Added, and here's how the encryption works:
<cffunction name="getToken" returntype="String" output="false">
<cfscript>
plainText = getPlainText();
rawSecretKey = CreateObject("java","sun.misc.BASE64Decoder").decodeBuffer(variables.encryptionKey);
secretKeySpec = CreateObject("java","javax.crypto.spec.SecretKeySpec").init(rawSecretKey,"DESEDE");
cipher = CreateObject("java","javax.crypto.Cipher").getInstance("DESEDE");
cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec);
encrypted = cipher.doFinal(plainText.getBytes()); // a byte array (a binary in CF)
return URLEncodedFormat(ToString(ToBase64(encrypted)));
</cfscript>
</cffunction>
Update:
This issue has been resolved. The problem was that the key needed to be converted from Base64.
The answer:
Instead of:
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
Do this:
byte[] TDESKey = Convert.FromBase64String(Passphrase);
That solves this issue.