Need Java equvalent for 3DES decryption of PHP code - java

This is the PHP code I have.
function decrypt($s_input, $s_key, $s_iv) {
$s_decrypted = pack("H*" , $s_input); // Hex to binary
$s_decrypted = mcrypt_decrypt (MCRYPT_3DES, $s_key, $s_decrypted, MCRYPT_MODE_CBC, $s_iv); // 3des decryption
return $s_decrypted;
}
echo encrypt('c37551bb77f741d0bcdc16497b4f97b1','123456781234567812345678','12345678' );
what it basically does is to decrypt a 3des encrypted string (first it convert the hex string to binary using pack function and then does the actual decryption).
This perfectly works in PHP-4 and prints the "Hello World" message.
However, if I run the equivalent java code (jdk 1.6), it prints garbage output as - ¬ªmjV=7xl_ÓÄ^›*?.
Can someone help to troubleshoot this? Why Java is not properly decrypting the hex string.
private static String decrypt(String inputStr, String keyStr, String ivStr) throws Exception {
IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes());
SecretKeySpec key = new SecretKeySpec(keyStr.getBytes(), "DESede");
inputStr = hexToString(inputStr, 2);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = cipher.doFinal(inputStr.getBytes());
return new String(decrypted);
}
private static String hexToString(String input, int groupLength) {
StringBuilder sb = new StringBuilder(input.length() / groupLength);
for (int i = 0; i < input.length() - groupLength + 1; i += groupLength) {
String hex = input.substring(i, i + groupLength);
sb.append((char) Integer.parseInt(hex, 16));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
String decryptSignature = decrypt("c37551bb77f741d0bcdc16497b4f97b1", "123456781234567812345678", "12345678");
System.out.println(decryptSignature);
}

There are a few things you should check. You might find Encryption using AES-128 in Java to be of some assistance. There could be issues with differences between how you are handling keys in the PHP and Java code. Calling getBytes() on a String in Java without an encoding is almost always a bad idea. Plus the padding used could be a problem. From what I've seen PHP pads with null characters by default, which does not correspond to NoPadding in Java. Finally, the hexToString method should return a byte[] instead of a String. Add the result of calling Integer.parseInt(hex, 16) into an array:
byte[] results = new byte[input.length() / groupLength];
...
//inside the loop
results[i / groupLength] = (byte) Integer.parseInt(hex, 16);
...
return results;

Related

Java Encryption Conversion from .NET

I was given this .NET code and I need to convert it to Java. I have found a bunch of articles but nothing I try works.
public string EncryptNVal(string vVal, string accountNumber, string sharedSecret)
{
byte[] IV = Convert.FromBase64String(vVal);
byte[] Key = Convert.FromBase64String(sharedSecret);
SymmetricAlgorithm sa = Rijndael.Create();
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, sa.CreateEncryptor(Key, IV),
CryptoStreamMode.Write))
{
byte[] data = Encoding.UTF8.GetBytes(accountNumber);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
ms.Position = 0;
return Convert.ToBase64String(ms.ToArray());
}
}
This is some of my attempt which you can guess throws exceptions.
public String EncryptNVal(String vVal, String accountNumber, String sharedSecret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] IV = Base64.decodeBase64(vVal);
byte[] Key =Base64.decodeBase64(sharedSecret);
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(Base64.decodeBase64(sharedSecret).toString().toCharArray());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = factory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(IV,salt,1000);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(accountNumber.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(ciphertext);
In the same project I have this code which I think I got correct, but any confirmation on that would be helpful as well.
.NET code I was given
public string GetMd5Hash(string input)
{
using (var md5Hash = MD5.Create())
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
foreach (var t in data)
{
sBuilder.Append(t.ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
What I wrote in Java:
public String GetHash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] inputBytes = input.getBytes("UTF-8");
md.update(inputBytes);
byte inputHash[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < inputHash.length; i++) {
hexString.append(Integer.toHexString(0xFF & inputHash[i]));
}
return hexString.toString();
}
On a side note I can use either MD5 or SHA256, the .NET code was using MD5, so I was trying to follow that since my knowledge of encryption is about null. I am willing to use the SHA256 if someone can give me good advice.
I took me some time, and a lot of errors to figure this out, but here's a java method which yields exactly the same output as your C# method.
I'm no expert at all in crypto stuff, but this seems to do the work.
public static String EncryptNVal(String vVal, String accountNumber, String sharedSecret){
try {
byte[] vValAsBytes = java.util.Base64.getDecoder().decode(vVal);
byte[] sharedSecretAsBytes = java.util.Base64.getDecoder().decode(sharedSecret);
byte[] accountNumberAsBytes = accountNumber.getBytes();
SecretKeySpec key = new SecretKeySpec(sharedSecretAsBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(vValAsBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(accountNumberAsBytes);
String signature = java.util.Base64.getEncoder().encodeToString(output);
return signature;
}
catch(Exception e){
e.printStackTrace();
return "<dummy>";
}
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
A few additional info :
Rijndael is not AES, in the sense that it's not implementing the actual AES standard. If this is for production use, I'd suggest moving to actual AES (take a look at this link for info).
AES/CBC/PKCS5Padding is the closest match I could find for C#'s Rijndael . Although C# seems to use PKCS7, this doesn't seem available for Java (at least not on my system). PKCS5Padding yields the same result, but it will probably not work in 100% of the cases.
It only required trial and error, no deep knowledge of either C# or Java. You'll probably face similar challenges (or related problems which will be induced by your particular use case), and I'd suggest you apply a similar workflow as mine, when it comes to translating from a language to another :
Use and abuse of printing capabilities, to ensure all steps of both codes have the same state
Arrange the code with the same logical flow on both sides
Thoroughly read the documentation of your source function that you want to translate

Encrypt string using given modulus and exponent

I need to replicate the functionality of the following JAVA code that receives a string with the exponent and modulus of a public key to generate a public key with said parameters and encrypt a string:
package snippet;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
public class Snippet {
public static void main(String ... strings) {
try {
// Needed if you don't have this provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//String and received public key example
String ReceivedString = "1234";
String publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
EncryptFunc(ReceivedString, publicRSA);
//The result obtained from the ReceivedString and the publicRSA is as follows:
//Result in hex [1234] -> [777786fe162598689a8dc172ed9418cb]
} catch (Exception ex) {
System.out.println("Error: " );
ex.printStackTrace();
}
}
public static String EncryptFunc(String ReceivedString, String clavePublica) throws Exception {
String result = "";
//We separate the received public string into exponent and modulus
//We receive it as "exponent|modulus"
String[] SplitKey = clavePublica.split("\\|");
KeyFactory keyFactory = KeyFactory.getInstance("RSA","BC");
RSAPublicKeySpec ks = new RSAPublicKeySpec(new BigInteger(hex2byte(SplitKey[1])), new BigInteger(hex2byte(SplitKey[0])));
//With these specs, we generate the public key
RSAPublicKey pubKey = (RSAPublicKey)keyFactory.generatePublic(ks);
//We instantiate the cypher, with the EncryptFunc and the obtained public key
Cipher cipher= Cipher.getInstance("RSA/None/NoPadding","BC");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//We reverse the ReceivedString and encrypt it
String ReceivedStringReverse = reverse(ReceivedString);
byte[] cipherText2 = cipher.doFinal(ReceivedStringReverse.getBytes("UTF8"));
result = byte2hex(cipherText2);
System.out.println("result in hex ["+ReceivedString+"] -> ["+result+"]");
return result;
}
public static byte[] hex2byte(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static String byte2hex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte aByte : bytes) {
result.append(String.format("%02x", aByte));
// upper case
// result.append(String.format("%02X", aByte));
}
return result.toString();
}
public static String reverse(String source) {
int i, len = source.length();
StringBuilder dest = new StringBuilder(len);
for (i = (len - 1); i >= 0; i--){
dest.append(source.charAt(i));
}
return dest.toString();
}
}
I've tried several approaches with this one, And I have done some searching here, here, here, here and here.
I Managed to create the public key with the given parameters, but the results are always different when I encrypt the string:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace RSACypherTest
{
public class Program
{
public static RSACryptoServiceProvider rsa;
static void Main(string[] args)
{
string str = "1234";
string publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
string encrypted = "";
Console.WriteLine("Original text: " + str);
encrypted = Encrypt(str, publicRSA);
Console.WriteLine("Encrypted text: " + encrypted);
Console.ReadLine();
}
public static string Encrypt(string str, string PublicRSA)
{
string[] Separated = PublicRSA.Split('|');
RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);
IAsymmetricBlockCipher eng = new Pkcs1Encoding(new RsaEngine());
eng.Init(true, pubParameters);
byte[] plaintext = Encoding.UTF8.GetBytes(Reverse(str));
byte[] encdata = eng.ProcessBlock(plaintext, 0, plaintext.Length);
return ByteArrayToString(encdata);
}
public static string Reverse(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
public static byte[] StringToByteArray(string hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
{
var modulus = new BigInteger(modulusHexString, 16);
var exponent = new BigInteger(exponentHexString, 16);
return new RsaKeyParameters(isPrivateKey, modulus, exponent);
}
}
}
I'm trying to use BouncyCastle because it seems to be the most effcient way of dealing with the key generation and everything. Any help concerning this would be very much appreciated.
Thanks in advance.
This is not the answer to your question but may help you in understanding RSA encryption.
I setup a sample encryption program in C# and used your given public key (converted the BigInteger modulus & exponent to Base64 values and further just wrote the XML-String representation of the public to use this key for encryption. The keylength is good for a length of maximum 5 byte data.
When running the encryption 5 times you will receive different encodedData (here in Base64 encoding) each run. So it's the expected behavior of the RSA encryption.
As C# allows me to "build" a short key it is not possible to generate a fresh keypair of such length and I doubt that Bouncy Castle would do (but here on SO there are many colleagues with a much better understanding of BC :-).
If you would like the program you can use the following external link to the program: https://jdoodle.com/ia/40.
Result:
load a pre created public key
publicKeyXML2: lxUtcDSotIOD09uiDEPQSQ==AQAB
encryptedData in Base64: JIFfO7HXCvdi0nSxKb0eLA==
encryptedData in Base64: dvtRw0U0KtT/pDJZW2X0FA==
encryptedData in Base64: CqJJKZevO6jWH6DQ1dnkhQ==
encryptedData in Base64: G7cL6BBwxysItvD/Rg0PuA==
encryptedData in Base64: HcfZJITu/PzN84WgI8yc6g==
code:
using System;
using System.Security.Cryptography;
using System.Text;
class RSACSPSample
{
static void Main()
{
try
{
//Create byte arrays to hold original, encrypted, and decrypted data.
byte[] dataToEncrypt = System.Text.Encoding.UTF8.GetBytes("1234");
byte[] encryptedData;
//Create a new instance of RSACryptoServiceProvider to generate
//public and private key data.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
Console.WriteLine("load a pre created public key");
string publicKeyXML = "<RSAKeyValue><Modulus>AJcVLXA0qLSDg9PbogxD0Ek=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
RSA.FromXmlString(publicKeyXML);
string publicKeyXML2 = RSA.ToXmlString(false);
Console.WriteLine("publicKeyXML2: " + publicKeyXML2);
Console.WriteLine();
//Pass the data to ENCRYPT, the public key information
//(using RSACryptoServiceProvider.ExportParameters(false),
//and a boolean flag specifying no OAEP padding.
for (int i = 0; i < 5; i++)
{
encryptedData = RSAEncrypt(dataToEncrypt, RSA.ExportParameters(false), false);
string encryptedDataBase64 = Convert.ToBase64String(encryptedData);
Console.WriteLine("encryptedData in Base64: " + encryptedDataBase64);
}
}
}
catch (ArgumentNullException)
{
//Catch this exception in case the encryption did
//not succeed.
Console.WriteLine("Encryption failed.");
}
}
public static byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
//Import the RSA Key information. This only needs
//toinclude the public key information.
RSA.ImportParameters(RSAKeyInfo);
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
}
While I won't mark my own answer as the correct one, I've found that there's the possibility to recreate the entire functionality of the java code mentioned in my question.
As Michael Fehr mentions in his answer, Its absolutely logical that any encryption method will try to avoid creating repeating or predictable patterns, as this answer perfectly describes.
Since in this particular situation the aim is to replicate the java code functionality, and said functionality revolves around getting the same results when encrypting a string with a given public key, we can use the answer in this post to generate a pice of code like the following:
private static string EncryptMessage(string str, string publicRSA)
{
string[] Separated = publicRSA.Split('|');
RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);
var eng = new RsaEngine();
eng.Init(true, pubParameters);
string x = Reverse(str);
byte[] plaintext = Encoding.UTF8.GetBytes(x);
var encdata = ByteArrayToString(eng.ProcessBlock(plaintext, 0, plaintext.Length));
return encdata;
}
private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
{
byte[] mod = StringToByteArray(modulusHexString);
byte[] exp = StringToByteArray(exponentHexString);
var modulus = new BigInteger(mod);
var exponent = new BigInteger(exp);
return new RsaKeyParameters(isPrivateKey, modulus, exponent);
}
To recap:
As Michael Fehr says, it is not only normal but expected of a crypyography engine to NOT generate repeatable/predictable patterns
To deliver on the previous point, they add random "padding" to the messages
It's possible (but not recommended) to use BouncyCastle to generate a No-padding engine, emulating the functionality of Java code such as this Cipher rsa = Cipher.getInstance("RSA/ECB/nopadding");

AES Encryption in Java to match with C# Output

I am trying to do AES Encryption using JAVA, I have made multiple attempts, tried a lot of codes and did many changes to finally reach to a place where my encrypted text matches with the encrypted text generated using C# code BUT PARTIALLY. The last block of 32 bits is different. I do not have access to the C# code since it is a 3rd Party Service. Can anyone guide what am I missing?
Conditions Mentioned are to use:
Use 256-bit AES encryption in CBC mode and with PKCS5 padding to encrypt the entire query string using your primary key and initialization vector. (Do not include a message digest in the query string.) The primary key is a 64-digit hexadecimal string and the initialization vector is a 32-digit hexadecimal string.
The sample values I used are:
Aes_IV = 50B666AADBAEDC14C3401E82CD6696D4
Aes_Key = D4612601EDAF9B0852FC0641DC2F273E0F2B9D6E85EBF3833764BF80E09DD89F (my KeyMaterial)
Plain_Text = ss=brock&pw=123456&ts=20190304234431 (input)
Encrypted_Text = 7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A072AF44AADB62FA66F047EACA5C6A018 (output)
My Output =
7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A38E71E5C846BAA6C31F996AB05AFD089
public static String encrypt( String keyMaterial, String unencryptedString, String ivString ) {
String encryptedString = "";
Cipher cipher;
try {
byte[] secretKey = hexStrToByteArray( keyMaterial );
SecretKey key = new SecretKeySpec( secretKey, "AES" );
cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" );
IvParameterSpec iv;
iv = new IvParameterSpec( hexStrToByteArray( ivString ) );
cipher.init( Cipher.ENCRYPT_MODE, key, iv );
byte[] plainText = unencryptedString.getBytes( "UTF-8") ;
byte[] encryptedText = cipher.doFinal( plainText );
encryptedString = URLEncoder.encode(byteArrayToHexString( encryptedText ),"UTF-8");
}
catch( InvalidKeyException | InvalidAlgorithmParameterException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e ) {
System.out.println( "Exception=" +e.toString() );
}
return encryptedString;
}
I have used this for conversions.
public static byte[] hexStrToByteArray ( String input) {
if (input == null) return null;
if (input.length() == 0) return new byte[0];
if ((input.length() % 2) != 0)
input = input + "0";
byte[] result = new byte[input.length() / 2];
for (int i = 0; i < result.length; i++) {
String byteStr = input.substring(2*i, 2*i+2);
result[i] = (byte) Integer.parseInt("0" + byteStr, 16);
}
return result;
}
public static String byteArrayToHexString(byte[] ba) {
String build = "";
for (int i = 0; i < ba.length; i++) {
build += bytesToHexString(ba[i]);
}
return build;
}
public static String bytesToHexString ( byte bt) {
String hexStr ="0123456789ABCDEF";
char ch[] = new char[2];
int value = (int) bt;
ch[0] = hexStr.charAt((value >> 4) & 0x000F);
ch[1] = hexStr.charAt(value & 0x000F);
String str = new String(ch);
return str;
}
Any Suggestions, what should I do to match the outputs?
If only the last block of ECB / CBC padding is different then you can be pretty sure that a different block cipher padding is used. To validate which padding is used you can try (as Topaco did in the comments below the question) or you can decrypt the ciphertext without padding. For Java that would be "AES/CBC/NoPadding".
So if you do that given the key (and IV) then you will get the following output in hexadecimals:
73733D62726F636B2670773D3132333435362674733D3230313930333034323334343331000000000000000000000000
Clearly this is zero padding.
Zero padding has one big disadvantage: if your ciphertext ends with a byte valued zero then this byte may be seen as padding and stripped from the result. Generally this is not a problem for plaintext consisting of an ASCII or UTF-8 string, but it may be trickier for binary output. Of course, we'll assume here that the string doesn't use a null terminator that is expected to be present in the encrypted plaintext.
There is another, smaller disadvantage: if your plaintext is exactly the block size then zero padding is non-standard enough that there are two scenarios:
the padding is always applied and required to be removed, which means that if the plaintext size is exactly a number of times the block size that still a full block of padding is added (so for AES you'd have 1..16 zero valued bytes as padding);
the padding is only applied if strictly required, which means that no padding is applied if the plaintext size is exactly a number of times the block size (so for AES you'd have 0..15 zero valued bytes as padding).
So currently, for encryption, you might have to test which one is expected / accepted. E.g. Bouncy Castle - which is available for C# and Java - always (un)pads, while the horrid PHP / mcrypt library only pads where required.
You can always perform your own padding of course, and then use "NoPadding" for Java. Remember though that you never unpad more than 16 bytes.
General warning: encryption without authentication is unfit for transport mode security.

Porting Java encryption routine to C#

I'm attempting with little success to port over Google's code to generate a secure token for their captcha (https://github.com/google/recaptcha-java/blob/master/appengine/src/main/java/com/google/recaptcha/STokenUtils.java):
The original utility has the following:
private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";
private static String encryptAes(String input, String siteSecret) {
try {
SecretKeySpec secretKey = getKey(siteSecret);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static SecretKeySpec getKey(String siteSecret){
try {
byte[] key = siteSecret.getBytes("UTF-8");
key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static void main(String [] args) throws Exception {
//Hard coded the following to get a repeatable result
String siteSecret = "12345678";
String jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
System.out.println(" json token: " + jsonToken);
System.out.println(" siteSecret: " + siteSecret);
System.out.println(" Encrypted stoken: " + encryptAes(jsonToken, siteSecret));
Given the values I hardcoded, I get Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns" back as my encrypted token.
My Java and crypto skills are more than a little rusty, and there aren't always direct analogs in C#. I attempted to merge encrypeAes() and getKey() with the following, which isn't correct:
public static string EncryptText(string PlainText, string siteSecret)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
var bytes = Encoding.UTF8.GetBytes(siteSecret);
SHA1 sha1 = SHA1.Create();
var shaKey = sha1.ComputeHash(bytes);
byte[] targetArray = new byte[16];
Array.Copy(shaKey, targetArray, 16);
aes.Key = targetArray;
ICryptoTransform encrypto = aes.CreateEncryptor();
byte[] plainTextByte = ASCIIEncoding.UTF8.GetBytes(PlainText);
byte[] CipherText = encrypto.TransformFinalBlock(plainTextByte, 0, plainTextByte.Length);
return HttpServerUtility.UrlTokenEncode(CipherText); //Equivalent to java's BaseEncoding.base64Url()?
}
}
The C# version produces the incorrect value of: Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
Your code almost works as expected. It's just that you somehow mixed up the outputs of the Java version (and possibly the C# version).
If I execute your Java code (JDK 7 & 8 with Guava 18.0), I get
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
and if I execute your C# code (DEMO), I get
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1
So, the C# version has an additional "1" at the end. It should be a padding character, but isn't. This means that HttpServerUtility.UrlTokenEncode() doesn't provide a standards conform URL-safe Base64 encoding and you shouldn't use it. See also this Q&A.
The URL-safe Base64 encoding can be easily derived from the normal Base64 encoding (compare tables 1 and 2 in RFC4648) as seen in this answer by Marc Gravell:
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes)
.TrimEnd(padding).Replace('+', '-').Replace('/', '_');
with:
static readonly char[] padding = { '=' };
That's not all. If we take your Java output of
Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
and decrypt it, then we get the following token:
{"session_id":"4182e173-3a24-4c10-b76c-b85a36be1173","ts_ms":1445786965574}
which is different from the token that you have in your code:
{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}
The main remaining problem is that you're using invalid JSON. Strings and keys in JSON need to be wrapped in " and not '.
Which means that the encrypted token actually should have been (using a valid version of the token from your code):
D9rOP07fYgBfza5vbGsvdPe8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBsAWBDgtdSozv4jS_auBU-CgjlrJ_430LgYcathLLd9U
Here's a C# implementation that reproduces the same result as your Java code:
class Program
{
public static byte[] GetKey(string siteSecret)
{
byte[] key = Encoding.UTF8.GetBytes(siteSecret);
return SHA1.Create().ComputeHash(key).Take(16).ToArray();
}
public static string EncryptAes(string input, string siteSecret)
{
var key = GetKey(siteSecret);
using (var aes = AesManaged.Create())
{
if (aes == null) return null;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key;
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
var enc = aes.CreateEncryptor(key, new byte[16]);
return UrlSafeBase64(enc.TransformFinalBlock(inputBytes,0,input.Length));
}
}
// http://stackoverflow.com/a/26354677/162671
public static string UrlSafeBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes).TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
static void Main(string[] args)
{
string siteSecret = "12345678";
string jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
Console.WriteLine(" json token: " + jsonToken);
Console.WriteLine(" siteSecret: " + siteSecret);
Console.WriteLine(EncryptAes(jsonToken, siteSecret));
Console.ReadLine();
}
}
I don't know why you said you're getting Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns from the Java program because I'm not getting that output. The output I'm getting from both the C# version and the Java version is this:
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
As you can see here:
The code for both versions is available here
Live demo of the C# version.
The Java version was copy/pasted from your code and is using guava-18.0 and compiled with JDK8 x64 (I'm not a java expert so I'm just adding these in case it makes any difference).

JAVA a reliable equivalent for php's MCRYPT_RIJNDAEL_256

I need to access some data that used PHP encryption. The PHP encryption is like this.
base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($cipher), $text, MCRYPT_MODE_ECB));
As value of $text they pass the time() function value which will be different each time that the method is called in. I have implemented this in Java. Like this,
public static String md5(String string) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Huh, MD5 should be supported?", e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Huh, UTF-8 should be supported?", e);
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
int i = (b & 0xFF);
if (i < 0x10) hex.append('0');
hex.append(Integer.toHexString(i));
}
return hex.toString();
}
public static byte[] rijndael_256(String text, byte[] givenKey) throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException{
final int keysize;
if (givenKey.length <= 192 / Byte.SIZE) {
keysize = 192;
} else {
keysize = 256;
}
byte[] keyData = new byte[keysize / Byte.SIZE];
System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
KeyParameter key = new KeyParameter(keyData);
BlockCipher rijndael = new RijndaelEngine(256);
ZeroBytePadding c = new ZeroBytePadding();
PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);
pbbc.init(true, key);
byte[] plaintext = text.getBytes(Charset.forName("UTF8"));
byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];
int offset = 0;
offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
offset += pbbc.doFinal(ciphertext, offset);
return ciphertext;
}
public static String encrypt(String text, String secretKey) throws Exception {
byte[] givenKey = String.valueOf(md5(secretKey)).getBytes(Charset.forName("ASCII"));
byte[] encrypted = rijndael_256(text,givenKey);
return new String(Base64.encodeBase64(encrypted));
}
I have referred this answer when creating MCRYPT_RIJNDAEL_256 method."
Encryption in Android equivalent to php's MCRYPT_RIJNDAEL_256
"I have used apache codec for Base64.Here's how I call the encryption function,
long time= System.currentTimeMillis()/1000;
String encryptedTime = EncryptionUtils.encrypt(String.valueOf(time), secretkey);
The problem is sometimes the output is not similar to PHP but sometimes it works fine.
I think that my MCRYPT_RIJNDAEL_256 method is unreliable.
I want to know where I went wrong and find a reliable method so that I can always get similar encrypted string as to PHP.
The problem is likely to be the ZeroBytePadding. The one of Bouncy always adds/removes at least one byte with value zero (a la PKCS5Padding, 1 to 16 bytes of padding) but the one of PHP only pads until the first block boundary is encountered (0 to 15 bytes of padding). I've discussed this with David of the legion of Bouncy Castle, but the PHP zero byte padding is an extremely ill fit for the way Bouncy does padding, so currently you'll have to do this yourself, and use the cipher without padding.
Of course, as a real solution, rewrite the PHP part to use AES (MCRYPT_RIJNDAEL_128), CBC mode encryption, HMAC authentication, a real Password Based Key Derivation Function (PBKDF, e.g. PBKDF2 or bcrypt) and PKCS#7 compatible padding instead of this insecure, incompatible code. Alternatively, go for OpenSSL compatibility or a known secure container format.

Categories

Resources