Skip to content

Commit ae29e57

Browse files
committed
feat: improved hash calculation for large files, refactoring and removed the need to copy files
1 parent f75274e commit ae29e57

File tree

6 files changed

+262
-146
lines changed

6 files changed

+262
-146
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.codedead.deadhash.domain.objects.hashgenerator;
2+
3+
import android.content.ContentResolver;
4+
import android.net.Uri;
5+
6+
import com.codedead.deadhash.domain.utils.HashUtil;
7+
8+
import java.util.List;
9+
10+
public final class FileHashGenerator extends HashGenerator {
11+
12+
private final Uri uri;
13+
private final ContentResolver contentResolver;
14+
15+
/**
16+
* Initialize a new HashGenerator
17+
*
18+
* @param uri The Uri of the file that should be hashed
19+
* @param hashAlgorithms The List of HashingAlgorithm enums that should be used to calculate hashes
20+
* @param compare The compare String for the calculated hashes
21+
*/
22+
public FileHashGenerator(final Uri uri, final ContentResolver contentResolver, final List<HashAlgorithm> hashAlgorithms, final String compare) {
23+
super(hashAlgorithms, compare);
24+
25+
if (uri == null)
26+
throw new NullPointerException("File cannot be null!");
27+
if (contentResolver == null)
28+
throw new NullPointerException("ContentResolver cannot be null!");
29+
30+
this.uri = uri;
31+
this.contentResolver = contentResolver;
32+
}
33+
34+
/**
35+
* Generate the List of HashData for the given input data
36+
*
37+
* @return The List of HashData for the given input data
38+
*/
39+
@Override
40+
public List<HashData> generateHashes() {
41+
for (final HashAlgorithm algorithm : super.getHashAlgorithms()) {
42+
switch (algorithm) {
43+
case md5 -> {
44+
final String md5 = HashUtil.calculateHash(uri, contentResolver, "MD5");
45+
getHashData().add(new HashData("MD5", md5, getCompare()));
46+
}
47+
case sha1 -> {
48+
final String sha1 = HashUtil.calculateHash(uri, contentResolver, "SHA-1");
49+
getHashData().add(new HashData("SHA-1", sha1, getCompare()));
50+
}
51+
case sha224 -> {
52+
final String sha224 = HashUtil.calculateHash(uri, contentResolver, "SHA-224");
53+
getHashData().add(new HashData("SHA-224", sha224, getCompare()));
54+
}
55+
case sha256 -> {
56+
final String sha256 = HashUtil.calculateHash(uri, contentResolver, "SHA-256");
57+
getHashData().add(new HashData("SHA-256", sha256, getCompare()));
58+
}
59+
case sha384 -> {
60+
final String sha384 = HashUtil.calculateHash(uri, contentResolver, "SHA-384");
61+
getHashData().add(new HashData("SHA-384", sha384, getCompare()));
62+
}
63+
case sha512 -> {
64+
final String sha512 = HashUtil.calculateHash(uri, contentResolver, "SHA-512");
65+
getHashData().add(new HashData("SHA-512", sha512, getCompare()));
66+
}
67+
case crc32 -> {
68+
final String crc32 = HashUtil.calculateCRC32(uri, contentResolver);
69+
getHashData().add(new HashData("CRC32", crc32, getCompare()));
70+
}
71+
}
72+
}
73+
74+
return getHashData();
75+
}
76+
}
Lines changed: 15 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,49 @@
11
package com.codedead.deadhash.domain.objects.hashgenerator;
22

3-
import com.codedead.deadhash.domain.utils.HashUtil;
4-
5-
import java.io.File;
6-
import java.io.FileInputStream;
7-
import java.io.IOException;
83
import java.util.ArrayList;
94
import java.util.List;
105

11-
public final class HashGenerator {
12-
13-
private final byte[] data;
6+
public abstract class HashGenerator implements IHashGenerator {
147
private final List<HashAlgorithm> hashAlgorithms;
158
private final List<HashData> hashData;
169
private final String compare;
1710

1811
/**
1912
* Initialize a new HashGenerator
2013
*
21-
* @param data The byte array that should be hashed
2214
* @param hashAlgorithms The List of HashingAlgorithm enums that should be used to calculate hashes
2315
* @param compare The compare String for the calculated hashes
2416
*/
25-
public HashGenerator(final byte[] data, final List<HashAlgorithm> hashAlgorithms, final String compare) {
17+
public HashGenerator(final List<HashAlgorithm> hashAlgorithms, final String compare) {
2618
hashData = new ArrayList<>();
27-
this.data = data;
28-
2919
this.hashAlgorithms = hashAlgorithms;
3020
this.compare = compare;
3121
}
3222

3323
/**
34-
* Initialize a new HashGenerator
24+
* Get the List of HashData for the given input data
3525
*
36-
* @param data The byte array that should be hashed
37-
* @param hashAlgorithms The List of HashingAlgorithm enums that should be used to calculate hashes
38-
* @param compare The compare String for the calculated hashes
39-
* @throws IOException When the File could not be read
26+
* @return The List of HashData for the given input data
4027
*/
41-
public HashGenerator(final File data, final List<HashAlgorithm> hashAlgorithms, final String compare) throws IOException {
42-
hashData = new ArrayList<>();
43-
this.data = readFileToBytes(data);
44-
this.hashAlgorithms = hashAlgorithms;
45-
this.compare = compare;
28+
public List<HashAlgorithm> getHashAlgorithms() {
29+
return hashAlgorithms;
4630
}
4731

4832
/**
49-
* Read a file and return a byte array that represents the given File
33+
* Get the List of HashData for the given input data
5034
*
51-
* @param file The File that should be read
52-
* @return The byte array that represents the given File
53-
* @throws IOException When the File could not be read
35+
* @return The List of HashData for the given input data
5436
*/
55-
private byte[] readFileToBytes(final File file) throws IOException {
56-
if (file == null)
57-
throw new NullPointerException("File cannot be null!");
58-
59-
final int size = (int) file.length();
60-
final byte[] bytes = new byte[size];
61-
final byte[] tmpBuff = new byte[size];
62-
try (final FileInputStream fis = new FileInputStream(file)) {
63-
int read = fis.read(bytes, 0, size);
64-
if (read < size) {
65-
int remain = size - read;
66-
while (remain > 0) {
67-
read = fis.read(tmpBuff, 0, remain);
68-
System.arraycopy(tmpBuff, 0, bytes, size - remain, read);
69-
remain -= read;
70-
}
71-
}
72-
}
73-
74-
return bytes;
37+
public List<HashData> getHashData() {
38+
return hashData;
7539
}
7640

7741
/**
78-
* Generate the List of HashData for the given input data
79-
* @return The List of HashData for the given input data
42+
* Get the compare String for the calculated hashes
43+
*
44+
* @return The compare String for the calculated hashes
8045
*/
81-
public List<HashData> generateHashes() {
82-
for (final HashAlgorithm algorithm : hashAlgorithms) {
83-
switch (algorithm) {
84-
case md5 -> {
85-
final String md5 = HashUtil.calculateHash(data, "MD5");
86-
hashData.add(new HashData("MD5", md5, compare));
87-
}
88-
case sha1 -> {
89-
final String sha1 = HashUtil.calculateHash(data, "SHA-1");
90-
hashData.add(new HashData("SHA-1", sha1, compare));
91-
}
92-
case sha224 -> {
93-
final String sha224 = HashUtil.calculateHash(data, "SHA-224");
94-
hashData.add(new HashData("SHA-224", sha224, compare));
95-
}
96-
case sha256 -> {
97-
final String sha256 = HashUtil.calculateHash(data, "SHA-256");
98-
hashData.add(new HashData("SHA-256", sha256, compare));
99-
}
100-
case sha384 -> {
101-
final String sha384 = HashUtil.calculateHash(data, "SHA-384");
102-
hashData.add(new HashData("SHA-384", sha384, compare));
103-
}
104-
case sha512 -> {
105-
final String sha512 = HashUtil.calculateHash(data, "SHA-512");
106-
hashData.add(new HashData("SHA-512", sha512, compare));
107-
}
108-
case crc32 -> {
109-
final String crc32 = HashUtil.calculateCRC32(data);
110-
hashData.add(new HashData("CRC32", crc32, compare));
111-
}
112-
}
113-
}
114-
115-
return hashData;
46+
public String getCompare() {
47+
return compare;
11648
}
11749
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.codedead.deadhash.domain.objects.hashgenerator;
2+
3+
import java.util.List;
4+
5+
public interface IHashGenerator {
6+
List<HashData> generateHashes();
7+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.codedead.deadhash.domain.objects.hashgenerator;
2+
3+
import com.codedead.deadhash.domain.utils.HashUtil;
4+
5+
import java.util.List;
6+
7+
public final class TextHashGenerator extends HashGenerator {
8+
9+
private final String data;
10+
11+
/**
12+
* Initialize a new TextHashGenerator
13+
*
14+
* @param data The String that should be hashed
15+
* @param hashAlgorithms The List of HashingAlgorithm enums that should be used to calculate hashes
16+
* @param compare The compare String for the calculated hashes
17+
*/
18+
public TextHashGenerator(final String data, List<HashAlgorithm> hashAlgorithms, String compare) {
19+
super(hashAlgorithms, compare);
20+
21+
if (data == null)
22+
throw new NullPointerException("Data cannot be null!");
23+
if (data.isEmpty())
24+
throw new IllegalArgumentException("Data cannot be empty!");
25+
26+
this.data = data;
27+
}
28+
29+
/**
30+
* Generate the List of HashData for the given input data
31+
*
32+
* @return The List of HashData for the given input data
33+
*/
34+
@Override
35+
public List<HashData> generateHashes() {
36+
for (final HashAlgorithm algorithm : super.getHashAlgorithms()) {
37+
switch (algorithm) {
38+
case md5 -> {
39+
final String md5 = HashUtil.calculateHash(data.getBytes(), "MD5");
40+
getHashData().add(new HashData("MD5", md5, getCompare()));
41+
}
42+
case sha1 -> {
43+
final String sha1 = HashUtil.calculateHash(data.getBytes(), "SHA-1");
44+
getHashData().add(new HashData("SHA-1", sha1, getCompare()));
45+
}
46+
case sha224 -> {
47+
final String sha224 = HashUtil.calculateHash(data.getBytes(), "SHA-224");
48+
getHashData().add(new HashData("SHA-224", sha224, getCompare()));
49+
}
50+
case sha256 -> {
51+
final String sha256 = HashUtil.calculateHash(data.getBytes(), "SHA-256");
52+
getHashData().add(new HashData("SHA-256", sha256, getCompare()));
53+
}
54+
case sha384 -> {
55+
final String sha384 = HashUtil.calculateHash(data.getBytes(), "SHA-384");
56+
getHashData().add(new HashData("SHA-384", sha384, getCompare()));
57+
}
58+
case sha512 -> {
59+
final String sha512 = HashUtil.calculateHash(data.getBytes(), "SHA-512");
60+
getHashData().add(new HashData("SHA-512", sha512, getCompare()));
61+
}
62+
case crc32 -> {
63+
final String crc32 = HashUtil.calculateCRC32(data.getBytes());
64+
getHashData().add(new HashData("CRC32", crc32, getCompare()));
65+
}
66+
}
67+
}
68+
69+
return getHashData();
70+
}
71+
}

app/src/main/java/com/codedead/deadhash/domain/utils/HashUtil.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package com.codedead.deadhash.domain.utils;
22

3+
import android.content.ContentResolver;
4+
import android.net.Uri;
5+
6+
import java.io.IOException;
7+
import java.io.InputStream;
38
import java.security.MessageDigest;
9+
import java.security.NoSuchAlgorithmException;
410
import java.util.zip.CRC32;
511

612
public final class HashUtil {
@@ -49,6 +55,36 @@ public static String calculateHash(final byte[] bytes, final String kind) {
4955
}
5056
}
5157

58+
/**
59+
* Calculate the hash of a specified file using the specified message digest
60+
*
61+
* @param uri The Uri of the file that should be hashed
62+
* @param contentResolver The ContentResolver that should be used to open the file
63+
* @param kind The message digest
64+
* @return The String object that contains the hash of the file using the specified message digest
65+
*/
66+
public static String calculateHash(final Uri uri, final ContentResolver contentResolver, final String kind) {
67+
try {
68+
final MessageDigest md = MessageDigest.getInstance(kind);
69+
try (final InputStream fis = contentResolver.openInputStream(uri)) {
70+
if (fis == null)
71+
return null;
72+
73+
final byte[] dataBytes = new byte[1024];
74+
75+
int nread;
76+
while ((nread = fis.read(dataBytes)) != -1) {
77+
md.update(dataBytes, 0, nread);
78+
}
79+
80+
final byte[] mdBytes = md.digest();
81+
return convertToHex(mdBytes);
82+
}
83+
} catch (final NoSuchAlgorithmException | IOException ex) {
84+
return null;
85+
}
86+
}
87+
5288
/**
5389
* Calculate the CRC32 value of a specified byte array
5490
*
@@ -64,4 +100,32 @@ public static String calculateCRC32(final byte[] bytes) {
64100
return null;
65101
}
66102
}
103+
104+
/**
105+
* Calculate the CRC32 value of a specified file
106+
*
107+
* @param uri The Uri of the file that should be hashed
108+
* @param contentResolver The ContentResolver that should be used to open the file
109+
* @return The String object that represents the CRC32 value of the given file
110+
*/
111+
public static String calculateCRC32(final Uri uri, final ContentResolver contentResolver) {
112+
try {
113+
final CRC32 crc = new CRC32();
114+
try (final InputStream fis = contentResolver.openInputStream(uri)) {
115+
if (fis == null)
116+
return null;
117+
118+
final byte[] dataBytes = new byte[1024];
119+
120+
int nread;
121+
while ((nread = fis.read(dataBytes)) != -1) {
122+
crc.update(dataBytes, 0, nread);
123+
}
124+
125+
return Long.toHexString(crc.getValue());
126+
}
127+
} catch (final IOException ex) {
128+
return null;
129+
}
130+
}
67131
}

0 commit comments

Comments
 (0)