views:

151

answers:

3

I am trying to move a project from C# to Java for a learning exercise. I am still very new to Java, but I have a TripleDES class in C# that encrypts strings and returns a string value of the encrypted byte array. Here is my C# code:

using System;
using System.IO;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace tDocc.Classes
{
    /// <summary>
    /// Triple DES encryption class
    /// </summary>
    public static class TripleDES
    {
        private static byte[] key = { 110, 32, 73, 24, 125, 66, 75, 18, 79, 150, 211, 122, 213, 14, 156, 136, 171, 218, 119, 240, 81, 142, 23, 4 };
        private static byte[] iv = { 25, 117, 68, 23, 99, 78, 231, 219 };

        /// <summary>
        /// Encrypt a string to an encrypted byte array
        /// </summary>
        /// <param name="plainText">Text to encrypt</param>
        /// <returns>Encrypted byte array</returns>
        public static byte[] Encrypt(string plainText)
        {
            UTF8Encoding utf8encoder = new UTF8Encoding();
            byte[] inputInBytes = utf8encoder.GetBytes(plainText);
            TripleDESCryptoServiceProvider tdesProvider = new TripleDESCryptoServiceProvider();
            ICryptoTransform cryptoTransform = tdesProvider.CreateEncryptor(key, iv);
            MemoryStream encryptedStream = new MemoryStream();
            CryptoStream cryptStream = new CryptoStream(encryptedStream, cryptoTransform, CryptoStreamMode.Write);
            cryptStream.Write(inputInBytes, 0, inputInBytes.Length);
            cryptStream.FlushFinalBlock();
            encryptedStream.Position = 0;
            byte[] result = new byte[encryptedStream.Length];
            encryptedStream.Read(result, 0, (int)encryptedStream.Length);
            cryptStream.Close();
            return result;
        }

        /// <summary>
        /// Decrypt a byte array to a string
        /// </summary>
        /// <param name="inputInBytes">Encrypted byte array</param>
        /// <returns>Decrypted string</returns>
        public static string Decrypt(byte[] inputInBytes)
        {
            UTF8Encoding utf8encoder = new UTF8Encoding();
            TripleDESCryptoServiceProvider tdesProvider = new TripleDESCryptoServiceProvider();
            ICryptoTransform cryptoTransform = tdesProvider.CreateDecryptor(key, iv);
            MemoryStream decryptedStream = new MemoryStream();
            CryptoStream cryptStream = new CryptoStream(decryptedStream, cryptoTransform, CryptoStreamMode.Write);
            cryptStream.Write(inputInBytes, 0, inputInBytes.Length);
            cryptStream.FlushFinalBlock();
            decryptedStream.Position = 0;
            byte[] result = new byte[decryptedStream.Length];
            decryptedStream.Read(result, 0, (int)decryptedStream.Length);
            cryptStream.Close();
            UTF8Encoding myutf = new UTF8Encoding();
            return myutf.GetString(result);
        }

        /// <summary>
        /// Decrypt an encrypted string
        /// </summary>
        /// <param name="text">Encrypted text</param>
        /// <returns>Decrypted string</returns>
        public static string DecryptText(string text)
        {
            if (text == "")
            {
                return text;
            }
            return Decrypt(Convert.FromBase64String(text));
        }

        /// <summary>
        /// Encrypt a string
        /// </summary>
        /// <param name="text">Unencrypted text</param>
        /// <returns>Encrypted string</returns>
        public static string EncryptText(string text)
        {
            if (text == "")
            {
                return text;
            }
            return Convert.ToBase64String(Encrypt(text));
        }
    }

    /// <summary>
    /// Random number generator
    /// </summary>
    public static class RandomGenerator
    {
        /// <summary>
        /// Generate random number
        /// </summary>
        /// <param name="length">Number of randomizations</param>
        /// <returns>Random number</returns>
        public static int GenerateNumber(int length)
        {
            byte[] randomSeq = new byte[length];
            new RNGCryptoServiceProvider().GetBytes(randomSeq);
            int code = Environment.TickCount;

            foreach (byte b in randomSeq)
            {
                code += (int)b;
            }

            return code;
        }
    }

    /// <summary>
    /// Hash generator class
    /// </summary>
    public static class Hasher
    {
        /// <summary>
        /// Hash type
        /// </summary>
        public enum eHashType
        {
            /// <summary>
            /// MD5 hash. Quick but collisions are more likely. This should not be used for anything important
            /// </summary>
            MD5 = 0,

            /// <summary>
            /// SHA1 hash. Quick and secure. This is a popular method for hashing passwords
            /// </summary>
            SHA1 = 1,

            /// <summary>
            /// SHA256 hash. Slower than SHA1, but more secure. Used for encryption keys
            /// </summary>
            SHA256 = 2,

            /// <summary>
            /// SHA348 hash. Even slower than SHA256, but offers more security
            /// </summary>
            SHA348 = 3,

            /// <summary>
            /// SHA512 hash. Slowest but most secure. Probably overkill for most applications
            /// </summary>
            SHA512 = 4,

            /// <summary>
            /// Derrived from MD5, but only returns 12 digits
            /// </summary>
            Digit12 = 5
        }

        /// <summary>
        /// Hashes text using a specific hashing method
        /// </summary>
        /// <param name="text">Input text</param>
        /// <param name="hash">Hash method</param>
        /// <returns>Hashed text</returns>
        public static string GetHash(string text, eHashType hash)
        {
            if (text == "")
            {
                return text;
            }
            if (hash == eHashType.MD5)
            {
                MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
                return ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
            }
            else if (hash == eHashType.SHA1)
            {
                SHA1Managed hasher = new SHA1Managed();
                return ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
            }
            else if (hash == eHashType.SHA256)
            {
                SHA256Managed hasher = new SHA256Managed();
                return ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
            }
            else if (hash == eHashType.SHA348)
            {
                SHA384Managed hasher = new SHA384Managed();
                return ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
            }
            else if (hash == eHashType.SHA512)
            {
                SHA512Managed hasher = new SHA512Managed();
                return ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
            }
            else if (hash == eHashType.Digit12)
            {
                MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
                string newHash = ByteToHex(hasher.ComputeHash(Encoding.ASCII.GetBytes(text)));
                return newHash.Substring(0, 12);
            }
            return "";
        }

        /// <summary>
        /// Generates a hash based on a file's contents. Used for detecting changes to a file and testing for duplicate files
        /// </summary>
        /// <param name="info">FileInfo object for the file to be hashed</param>
        /// <param name="hash">Hash method</param>
        /// <returns>Hash string representing the contents of the file</returns>
        public static string GetHash(FileInfo info, eHashType hash)
        {
            FileStream hashStream = new FileStream(info.FullName, FileMode.Open, FileAccess.Read);
            string hashString = "";
            if (hash == eHashType.MD5)
            {
                MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
                hashString = ByteToHex(hasher.ComputeHash(hashStream));
            }
            else if (hash == eHashType.SHA1)
            {
                SHA1Managed hasher = new SHA1Managed();
                hashString = ByteToHex(hasher.ComputeHash(hashStream));
            }
            else if (hash == eHashType.SHA256)
            {
                SHA256Managed hasher = new SHA256Managed();
                hashString = ByteToHex(hasher.ComputeHash(hashStream));
            }
            else if (hash == eHashType.SHA348)
            {
                SHA384Managed hasher = new SHA384Managed();
                hashString = ByteToHex(hasher.ComputeHash(hashStream));
            }
            else if (hash == eHashType.SHA512)
            {
                SHA512Managed hasher = new SHA512Managed();
                hashString = ByteToHex(hasher.ComputeHash(hashStream));
            }
            hashStream.Close();
            hashStream.Dispose();
            hashStream = null;
            return hashString;
        }

        /// <summary>
        /// Converts a byte array to a hex string
        /// </summary>
        /// <param name="data">Byte array</param>
        /// <returns>Hex string</returns>
        public static string ByteToHex(byte[] data)
        {
            StringBuilder builder = new StringBuilder();
            foreach (byte hashByte in data)
            {
                builder.Append(string.Format("{0:X1}", hashByte));
            }
            return builder.ToString();
        }

        /// <summary>
        /// Converts a hex string to a byte array
        /// </summary>
        /// <param name="hexString">Hex string</param>
        /// <returns>Byte array</returns>
        public static byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i <= returnBytes.Length - 1; i++)
            {
                returnBytes[i] = byte.Parse(hexString.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
            }
            return returnBytes;
        }
    }
}

And her is what I've got for Java code so far, but I'm getting the error "Input length must be multiple of 8 when decrypting with padded cipher" when I run the test on this:

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.tdocc.utils.Base64;

public class TripleDES {
    private static byte[] keyBytes = { 110, 32, 73, 24, 125, 66, 75, 18, 79, (byte)150, (byte)211, 122, (byte)213, 14, (byte)156, (byte)136, (byte)171, (byte)218, 119, (byte)240, 81, (byte)142, 23, 4 };
    private static byte[] ivBytes = { 25, 117, 68, 23, 99, 78, (byte)231, (byte)219 };

    public static String encryptText(String plainText) {
        try {
            if (plainText.isEmpty()) return plainText;
            return Base64.decode(TripleDES.encrypt(plainText)).toString();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static byte[] encrypt(String plainText) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException {
        try {
            final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
            final IvParameterSpec iv = new IvParameterSpec(ivBytes);
            final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);

            final byte[] plainTextBytes = plainText.getBytes("utf-8");
            final byte[] cipherText = cipher.doFinal(plainTextBytes);

            return cipherText;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static String decryptText(String message) {
        try {
            if (message.isEmpty()) return message;
            else return TripleDES.decrypt(message.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static String decrypt(byte[] message) {
        try {
            final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
            final IvParameterSpec iv = new IvParameterSpec(ivBytes);
            final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key, iv);

            final byte[] plainText = cipher.doFinal(message);

            return plainText.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}
A: 

This problem seems similar to your problem. Hope it helps.

Kyra
A: 

Your cipher specifies PKCS#5 padding, which requires the ciphertext to be multiples of the block size (8 bytes for Triple DES).

The root cause is that the ciphertext is corrupted. Possible causes are,

  1. The ciphertext is binary and it might be encoded into text in database (like Base64 or hex). You need to decode it into binary to decrypt it.

  2. The ciphertext is treated as text and char encoding messes it up. For example, if you read binary as UTF-8 and you may ended up with lots question marks.

Also this Java code doesn't do what you want,

       return plainText.toString();

It should be

       return new String(plainText, "UTF-8");
ZZ Coder
A: 

You are not correctly doing base64 en/decoding in your Java code. Your C# code encrypts the data, then base64 encodes it. Therefore your Java decryptText method must first base64 decode the base64 string into a byte array, and then decrypt the byte array with your decrypt method. Your encrypt methods look similarly wrong, but I am not familiar with that particular base64 utility.

GregS