I'm new to java and really need some help. I created a command line tool in order to get an MD5 hash of a file. This worked so I then tailored my code to put it in GUI form. The two programs give different hashes of the same file which is confusing. I have looked into UTF-8 but as far as I can tell that would only work for strings and not a file instance. Can anyone tell me why they are providing different hash values and point me in the right direction?
First method (command line)...
public static void main(String args[]) throws IOException, NoSuchAlgorithmException {
System.out.println("Please enter file path: \n");
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String dir = stdin.readLine();
File file = new File(dir);
FileInputStream iStream = null;
try {iStream = new FileInputStream(file);}
catch (FileNotFoundException e) {
String MD5Output = "There has been an error: " + e.toString();
}
byte[] dataBytes = new byte[1024];
MessageDigest md = MessageDigest.getInstance("MD5");
int numRead = iStream.read(dataBytes);
md.update(dataBytes, 0, numRead);
iStream.close();
dataBytes = md.digest();
md.update(dataBytes);
System.out.println("MD5: " + new BigInteger(1, md.digest()).toString(16));
}
Second method (built for gui)...
public void doMD5() throws IOException, NoSuchAlgorithmException {
File file = new File(jTxtMD51.getText());
FileInputStream iStream = null;
try {iStream = new FileInputStream(file);}
catch (FileNotFoundException e) {
String MD5Output = "There has been an error: " + e.toString();
}
byte[] dataBytes = new byte[1024];
MessageDigest md = MessageDigest.getInstance("MD5");
int numRead = iStream.read(dataBytes);
md.update(dataBytes, 0, numRead);
iStream.close();
byte[] MD5checksum = md.digest();
md.update(dataBytes);
BigInteger bigInt = new BigInteger(1, md.digest());
String MD5Hash = bigInt.toString(16);
jTextOutput.append("MD5 is : " + MD5Hash);
}
you only make one read call from the stream. you need to loop when reading an InputStream (assuming you want to read the whole thing, which you generally want). additionally, you seem to make 2 calls to digest.update() using the same bytes.
also, typically when a hash value is printed, since it is a binary value, it is printed using base64 encoding.
In addition to #jtahlborn's comment, you don't need the md.update(databytes); call in both methods, and your second method should have this at the end:
BigInteger bigInt = new BigInteger(1, MD5checksum);
You first method doesn't do this second call to digest(), whose values changes when you make the call to update()
Related
This question already has answers here:
How to convert Java String into byte[]?
(9 answers)
Closed 4 years ago.
I have the following code to zip and unzip the String:
public static void main(String[] args) {
// TODO code application logic here
String Source = "hello world";
byte[] a = ZIP(Source);
System.out.format("answer:");
System.out.format(a.toString());
System.out.format("\n");
byte[] Source2 = a.toString().getBytes();
System.out.println("\nsource 2:" + Source2.toString() + "\n");
String b = unZIP(Source2);
System.out.println("\nunzip answer:");
System.out.format(b);
System.out.format("\n");
}
public static byte[] ZIP(String source) {
ByteArrayOutputStream bos= new ByteArrayOutputStream(source.length()* 4);
try {
GZIPOutputStream outZip= new GZIPOutputStream(bos);
outZip.write(source.getBytes());
outZip.flush();
outZip.close();
} catch (Exception Ex) {
}
return bos.toByteArray();
}
public static String unZIP(byte[] Source) {
ByteArrayInputStream bins= new ByteArrayInputStream(Source);
byte[] buf= new byte[2048];
StringBuffer rString= new StringBuffer("");
int len;
try {
GZIPInputStream zipit= new GZIPInputStream(bins);
while ((len = zipit.read(buf)) > 0) {
rString.append(new String(buf).substring(0, len));
}
return rString.toString();
} catch (Exception Ex) {
return "";
}
}
When "Hello World" have been zipped, it's will become [B#7bdecdec in byte[] and convert into String and display on the screen. However, if I'm trying to convert the string back into byte[] with the following code:
byte[] Source2 = a.toString().getBytes();
the value of variable a will become to [B#60a1807c instead of [B#7bdecdec . Does anyone know how can I convert the String (a value of byte but been convert into String) back in byte[] in JAVA?
Why doing byte[] Source2 = a.toString().getBytes(); ?
It seems like a double conversion; you convert a byte[] to string the to byte[].
The real conversion of a byte[] to string is new String(byte[]) hoping that you're in the same charset.
Source2 should be an exact copy of a hence you should just do byte[] Source2 = a;
Your unzip is wrong because you are converting back a string which might be in some other encoding (let's say UTF-8):
public static String unZIP(byte[] source) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length*2);
try (ByteArrayInputStream in = new ByteArrayInputStream(source);
GZIPInputStream zis = new GZIPInputStream(in)) {
byte[] buffer = new buffer[4096];
for (int n = 0; (n = zis.read(buffer) != 0; ) {
bos.write(buffer, 0, n);
}
}
return new String(bos.toByteArray(), StandardCharsets.UTF_8);
}
This one, not tested, will:
Store byte from the gzip stream into a ByteArrayOutputStream
Close the gzip/ByteArrayInputStream using try with resources
Convert the whole into a String using UTF-8 (you should always use encoding and unless rare case, UTF-8 is the way to go).
You must not use StringBuffer for two reasons:
The most important one: this will not behave well with multi bytes string such as UTF-8 or UTF-16.
And second, StringBuffer is synchronized: you should use StringBuilder whenever possible and whenever it should be used (eg: not here!). StringBuffer should be reserved for case where your share the StringBuffer with several threads, otherwise it is useless.
With those change, you will also need to change the ZIP as per David Conrad comment and because the unZIP use UTF-8:
public static byte[] ZIP(String source) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length()* 4);
try (GZIPOutputStream zip = new GZIPOutputStream(bos)) {
zip.write(source.getBytes(StandardCharsets.UTF_8));
}
return bos.toByteArray();
}
As for the main, printing a byte[] will result in the default toString.
I have an encrypted file that was done using reference from this question .I got the file encrypted.Now my issue is when trying to read the contents out, am getting an empty strings from the returned read(). Below is my call method and the method to decrypt the encrypted text to a string variable.
Calling Method:
File encryptedCFG = new File(homeDir + "/" + folder_name + "/twCGF.txt");
dc.ReadEncryptedFile(encryptedCFG);
Method:
public void ReadEncryptedFile(File deInFile) {
try {
FileInputStream fis = new FileInputStream(deInFile);
int length = (int) deInFile.length();
byte[] filebyte = new byte[length]
// Decrypt the byte contents from the file using the cipher setup
byte[] tmpTxT = mDecipher.doFinal(filebyte);
fis.read(tmpTxT);
fis.close();
// Read into a string since we got the contents
String plaintxt = new String(tmpTxt, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
}
Any pointers why am not getting the contents of the encrypted file correctly?
At the line where you're decrypting the byte array, it's still empty. You haven't read the file in, yet. You have to switch the operations.
byte[] filebyte = new byte[length]
fis.read(filebyte);
byte[] tmpTxt = mDecipher.doFinal(filebyte);
fis.close();
String plaintxt = new String(tmpTxt, "UTF-8");
I have an application where I am generating a "target file" based on a Java "source" class. I want to regenerate the target when the source changes. I have decided the best way to do this would be to get a byte[] of the class contents and calculate a checksum on the byte[].
I am looking for the best way to get the byte[] for a class. This byte[] would be equivalent to the contents of the compiled .class file. Using ObjectOutputStream does not work. The code below generates a byte[] that is much smaller than the byte contents of the class file.
// Incorrect function to calculate the byte[] contents of a Java class
public static final byte[] getClassContents(Class<?> myClass) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try( ObjectOutputStream stream = new ObjectOutputStream(buffer) ) {
stream.writeObject(myClass);
}
// This byte array is much smaller than the contents of the *.class file!!!
byte[] contents = buffer.toByteArray();
return contents;
}
Is there a way to get the byte[] with the identical contents of the *.class file? Calculating the checksum is the easy part, the hard part is obtaining the byte[] contents used to calculate an MD5 or CRC32 checksum.
THis is the solution that I ended up using. I don't know if it's the most efficient implementation, but the following code uses the class loader to get the location of the *.class file and reads its contents. For simplicity, I skipped buffering of the read.
// Function to obtain the byte[] contents of a Java class
public static final byte[] getClassContents(Class<?> myClass) throws IOException {
String path = myClass.getName().replace('.', '/');
String fileName = new StringBuffer(path).append(".class").toString();
URL url = myClass.getClassLoader().getResource(fileName);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (InputStream stream = url.openConnection().getInputStream()) {
int datum = stream.read();
while( datum != -1) {
buffer.write(datum);
datum = stream.read();
}
}
return buffer.toByteArray();
}
I don't get what you means, but i think you are looking for this, MD5.
To check MD5 of a file, you can use this code
public String getMd5(File file)
{
DigestInputStream stream = null;
try
{
stream = new DigestInputStream(new FileInputStream(file), MessageDigest.getInstance("MD5"));
byte[] buffer = new byte[65536];
read = stream.read(buffer);
while (read >= 1) {
read = stream.read(buffer);
}
}
catch (Exception ignored)
{
int read;
return null;
}
return String.format("%1$032x", new Object[] { new BigInteger(1, stream.getMessageDigest().digest()) });
}
Then, you can store the md5 of a file in any way for exmaple XML. An exmaple of MD5 is 49e6d7e2967d1a471341335c49f46c6c so once the file name and size change, md5 will change. You can store md5 of each file in XML format and next time your run a code to check md5 and compare the md5 of each file in the xml file.
If you really want the contents of the .class file, you should read the contents of .class file, not the byte[] representation that is in memory. So something like
import java.io.*;
public class ReadSelf {
public static void main(String args[]) throws Exception {
Class classInstance = ReadSelf.class;
byte[] bytes = readClass(classInstance);
}
public static byte[] readClass(Class classInstance) throws Exception {
String name = classInstance.getName();
name = name.replaceAll("[.]", "/") + ".class";
System.out.println("Reading this: " + name);
File file = new File(name);
System.out.println("exists: " + file.exists());
return read(file);
}
public static byte[] read(File file) throws Exception {
byte[] data = new byte[(int)file.length()]; // can only read a file of size INT_MAX
DataInputStream inputStream =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(file)));
int total = 0;
int nRead = 0;
try {
while((nRead = inputStream.read(data)) != -1) {
total += nRead;
}
}
finally {
inputStream.close();
}
System.out.println("Read " + total
+ " characters, which should match file length of "
+ file.length() + " characters");
return data;
}
}
I am writting a program where I take a string, encrypt it and then write it in a file. Then later, I read from the file the string, decrypt it and then modify it. Here's my code for DES encryption/decryption:
/* class for crypting and decrypting a file */
class DESEncrypter
{
private Cipher encryptionCipher;
private Cipher decryptionCipher;
public DESEncrypter (SecretKey key) throws Exception
{
encryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
decryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
decryptionCipher.init(Cipher.DECRYPT_MODE, key);
}
/* write to 'out' the encryption of the information read from 'in' */
public String encrypt(String unencryptedString)
{
String encryptedString = "";
try {
byte[] unencryptedByteArray = unencryptedString.getBytes("UTF8");
byte[] encryptedBytes = this.encryptionCipher.doFinal(unencryptedByteArray);
encryptedString = new sun.misc.BASE64Encoder().encode(encryptedBytes);
} catch (Exception ex) {
Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
}
return encryptedString;
}
private static String bytes2String(byte[] bytes)
{
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++)
{
stringBuffer.append((char) bytes[i]);
}
return stringBuffer.toString();
}
/* write to 'out' the information obtained by decrypting the information read from 'in' */
public String decrypt (String encryptedString) throws UnsupportedEncodingException
{
byte[] unencryptedByteArray = new byte[4096];
try {
// Encode bytes to base64 to get a string
byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(encryptedString);
// Decrypt
unencryptedByteArray = this.decryptionCipher.doFinal(decodedBytes);
} catch (Exception ex) {
Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
}
return bytes2String(unencryptedByteArray);
}
}
And this is the function where I write a encrypted String in a file:
public void writeToFileEncrypted(String filename, String owner, String departament)
{
try
{
BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
String crypt = "";
String aux;
while ((aux = br.readLine()) != null)
{
crypt += aux;
}
br.close();
String info = this.server.crypt.decrypt(crypt);
info += filename + " " + owner + " " + departament + "\n";
/* delete the old encryption */
File temp = new File("files_encrypted");
temp.delete();
String infoCrypt = this.server.crypt.encrypt(info);
File newFiles = new File("files_encrypted");
if (newFiles.createNewFile() == false)
{
log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
return;
}
BufferedWriter bw = new BufferedWriter(new FileWriter(newFiles));
bw.write(infoCrypt);
bw.close();
}
catch (Exception e)
{
log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
e.printStackTrace();
return;
}
}
While the server runs, I can make modification to that String from file(run that function many time). The problem is when I close the server and then I open it again because I get the error:
javax.crypto.BadPaddingException: Given final block not properly padded
This is how I read from file when the server opens:
BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
String crypto = new String();
String aux;
while ((aux = br.readLine()) != null)
{
crypto += aux;
readBytes++;
}
br.close();
System.out.println(readBytes);
info = this.crypt.decrypt(crypto);
Why do I get that error? What I'm doing wrong? I must write the encrypted String in file some other way?
LATER EDIT:
I've changed the function that read a String from a file, decrypt it, modify it , encrypt it and then write it in file.
public void writeToFileEncrypted(String filename, String owner, String departament)
{
try
{
File f = new File("files_encrypted");
int nrRead = 0;
String info = null;
FileInputStream fis = new FileInputStream(f);
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fis.read()) != -1)
{
sb.append((char)ch);
nrRead++;
}
fis.close();
StringBuilder sba = null;
if (nrRead != 0)
{
info = this.server.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));
sba = new StringBuilder(info);
sba.append(filename + " " + owner + " " + departament + " ");
}
else
{
sba = new StringBuilder(filename + " " + owner + " " + departament + " ");
}
/* delete the old encryption */
File temp = new File("files_encrypted");
temp.delete();
//System.out.println("before: " + sba.toString());
String infoCrypt = this.server.crypt.encrypt(sba.toString());
//System.out.println("after: " + infoCrypt);
File newFiles = new File("files_encrypted");
if (newFiles.createNewFile() == false)
{
log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
return;
}
FileOutputStream fos = new FileOutputStream(newFiles);
fos.write(infoCrypt.getBytes("UTF-8"));
fos.flush();
fos.close();
}
catch (Exception e)
{
log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
e.printStackTrace();
return;
}
}
I've also modified where I read the info from file when server opens for the first time:
FileInputStream fis = new FileInputStream(f);
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fis.read()) != -1)
{
sb.append((char)ch);
readBytes++;
}
fis.close();
if (readBytes != 0)
{
System.out.println("on: " + sb.toString());
info = this.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));
System.out.println("load: " + info);
}
}
At the System.out.println with "on: " what I read from file is exactly what I've written encrypted, without any spaces or new lines. If I read with read(buffer), where buffer is byte[], it seems that adds a lot of spaces.
Although I've made all this modifications I still get the error javax.crypto.BadPaddingException: Given final block not properly padded
Has somebody any idea what's going on here?
There are a few things here.
private static String bytes2String(byte[] bytes)
Is dodgy, you are casting a byte to a char in this method so there is no character encoding specified here. To convert bytes to characters you should just use the String constructor that takes an array of bytes and an encoding. e.g.
byte[] tmp = new byte[10];
String a = new String(tmp, "UTF-8");
Be careful using BufferedReaders + .readLine() - this will strip out any newline characters from your file as you read it unless you add them back into your buffer. Although I don't think this is your problem.
But I think the best way to simplify your code is to write the encoded bytes via an OutputStream directly to the file. Unless you need to send the contents of the file over a transport that doesn't like binary data, there is no need to base64 encode. Just use Input/OutputStreams to write the encrypted bytes direct to disk.
RESPONSE TO LATER EDIT:
You are still mixing up your use of binary data (bytes) and character data (String/chars). You can't do things like:
int ch;
while ((ch = fis.read()) != -1)
{
sb.append((char)ch);
The input stream is retuning bytes, a byte is not a character and just casting it to one is going to cause problems. When using encryption the output from the encryption operation is binary data, and the input to the decryption operation is also binary data. The fact that your are encrypting text is something you deal with before the encryption occurs, and after the decryption occurs. You basic operation should go along the following lines.
Take the text you want to encrypt and convert it to bytes, specifying an encoding using the .getBytes(String charsetName) on your String.
Pass these bytes into your encryption routine
Write the resulting bytes directly to disk
To decrypt:
Read the bytes from the file
Pass the bytes to your decryption routine (as bytes! no Strings/ text involved)
Take the out put bytes and re-construct you String using new String(byte[] bytes, String charsetName) specifying the same encoding as before.
You might find the following (untested, but should work) methods useful:
public byte[] readBinaryFile(File f) throws IOException
{
byte[] contents = new byte[(int)f.length()];
BufferedInputStream bis = null;
try
{
bis = new BufferedInputStream(new FileInputStream(f));
DataInputStream dis = new DataInputStream(bis);
dis.readFully(contents);
}
finally
{
if(bis != null)
{
bis.close();
}
}
return contents;
}
public void writeBinaryFile(byte[] contents, File f) throws IOException
{
BufferedOutputStream bos = null;
try
{
bos = new BufferedOutputStream(new FileOutputStream(f));
bos.write(contents);
}
finally
{
if(bos != null)
{
bos.close();
}
}
}
So you will also need to change the interface, and internals of your encrypt and decrypt methods so they take and return byte arrays, and ditch the base64 encoding.
You have several problems. The reading and decrypting process should be symmetric with the encrypting and writing process. But
you transform your String into a byte[] using getBytes("UTF8"), which is fine, but you don't use new String(byte[], "UTF8") to do the reverse operation.
you write a whole String to a file, including potential line breaks, but you read it line by line and concatenate each line, thus losing the line breaks in the process. You must read each and every char that has been written.
Also, relying on undocumented, unsupported classes like sun.misc.Base64Encoder/Decoder shouldn't be done. Use Apache commons-codec to find a documented Base64 encoding, guaranteed to still be there when the next JDK comes out, and which can be used on every JVM, including non-Sun JVMs.
I think it is in the initialization
SecureRandom sr = new SecureRandom();
cipher.init( Cipher.DECRYPT_MODE, desKey ,sr);
Not sure this is the primary problem, but when you return the decrypted String from decrypt(), you should be using:
return new String(unencryptedByteArray, "UTF-8");
Here is the weird thing which has already taken me a whole day:
If a write a simple String like "1" to a file and read it immediately, the string fetched equals the original String.
But if the String is generated by some hash function, the String fetched is no longer the same.
The follow code prints true false, and I want to know the trick behind the scene.
Thank you very much.
public static void main(String[] args) {
try {
String s1 = "1";
File f1 = new File("f1");
write (s1, f1);
System.out.println(read(f1).equals(s1));
MessageDigest md = MessageDigest.getInstance("SHA-512");
String s2 = foo(new File("1.jpg"), md);
File f2 = new File("f2");
write (s2, f2);
System.out.println(read(f2).equals(s2));
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Hash <i>f</i> by <i>md</i>
static String foo (File f, MessageDigest md) throws IOException {
FileInputStream fis = new FileInputStream(f);
DigestInputStream dis = new DigestInputStream(fis, md);
byte[] b = new byte[1024];
while (dis.read(b, 0, 1024) != -1) {
}
md = dis.getMessageDigest();
String s = new String(md.digest());
dis.close();
fis.close();
return s;
}
static void write (String s, File f) throws IOException {
FileWriter fw = new FileWriter(f);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(s);
bw.newLine();
bw.close();
fw.close();
}
static String read (File f) throws IOException {
FileReader fr = new FileReader(f);
BufferedReader bf = new BufferedReader(fr);
String s;
s = bf.readLine();
bf.close();
fr.close();
return s;
}
This is your first problem:
String s = new String(md.digest());
You're creating a string with arbitrary binary data in the platform default encoding. It may well not be valid text data in the platform default encoding. In other words, you're losing data. Encode it with base-64 instead - that way you'll always have a string with ASCII characters, and can get back to the original binary data reliably.
Your second general problem is using FileReader and FileWriter. These always use the default platform encoding, which is a terrible API decision as it makes them almost useless in my view. You should almost always be specifying an encoding - I tend to use UTF-8. Use FileInputStream/FileOutputStream and InputStreamReader/InputStreamWriter to read/write text with files. (Or use the Guava helper routines.)
The digest value of the hashed file most likely contains a newline or line feed character (0x10 or 0x13) which breaks the way you read the string using BufferedReader.readLine().