diff --git a/src/main/java/org/qortal/crypto/Crypto.java b/src/main/java/org/qortal/crypto/Crypto.java index 49cdd2fb..5d91781c 100644 --- a/src/main/java/org/qortal/crypto/Crypto.java +++ b/src/main/java/org/qortal/crypto/Crypto.java @@ -1,5 +1,8 @@ package org.qortal.crypto; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -75,12 +78,74 @@ public abstract class Crypto { return digest(digest(input)); } + /** + * Returns 32-byte SHA-256 digest of file passed in input. + * + * @param file + * file in which to perform digest + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static byte[] digest(File file) throws IOException { + return Crypto.digest(file, 8192); + } + + /** + * Returns 32-byte SHA-256 digest of file passed in input, in hex format + * + * @param file + * file in which to perform digest + * @return String digest as a hexadecimal string, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static String digestHexString(File file, int bufferSize) throws IOException { + byte[] digest = Crypto.digest(file, bufferSize); + + // Convert to hex + StringBuilder stringBuilder = new StringBuilder(); + for (byte b : digest) { + stringBuilder.append(String.format("%02x", b)); + } + return stringBuilder.toString(); + } + + /** + * Returns 32-byte SHA-256 digest of file passed in input. + * + * @param file + * file in which to perform digest + * @param bufferSize + * the number of bytes to load into memory + * @return byte[32] digest, or null if SHA-256 algorithm can't be accessed + * + * @throws IOException if the file cannot be read + */ + public static byte[] digest(File file, int bufferSize) throws IOException { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + FileInputStream fileInputStream = new FileInputStream(file); + byte[] bytes = new byte[bufferSize]; + int count; + + while ((count = fileInputStream.read(bytes)) != -1) { + sha256.update(bytes, 0, count); + } + fileInputStream.close(); + + return sha256.digest(); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 message digest not available"); + } + } + /** * Returns 64-byte duplicated digest of message passed in input. *

* Effectively Bytes.concat(digest(input), digest(input)). - * - * @param addressVersion + * * @param input */ public static byte[] dupDigest(byte[] input) { diff --git a/src/test/java/org/qortal/test/CryptoTests.java b/src/test/java/org/qortal/test/CryptoTests.java index 46edc698..3a76b9f3 100644 --- a/src/test/java/org/qortal/test/CryptoTests.java +++ b/src/test/java/org/qortal/test/CryptoTests.java @@ -10,7 +10,12 @@ import org.qortal.utils.Base58; import static org.junit.Assert.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.security.SecureRandom; +import java.util.Random; import org.bouncycastle.crypto.agreement.X25519Agreement; import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; @@ -40,6 +45,37 @@ public class CryptoTests extends Common { assertArrayEquals(expected, digest); } + @Test + public void testFileDigest() throws IOException { + byte[] input = HashCode.fromString("00").asBytes(); + + Path tempPath = Files.createTempFile("", ".tmp"); + Files.write(tempPath, input, StandardOpenOption.CREATE); + + byte[] digest = Crypto.digest(tempPath.toFile()); + byte[] expected = HashCode.fromString("6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d").asBytes(); + + assertArrayEquals(expected, digest); + + Files.delete(tempPath); + } + + @Test + public void testFileDigestWithRandomData() throws IOException { + byte[] input = new byte[128]; + new Random().nextBytes(input); + + Path tempPath = Files.createTempFile("", ".tmp"); + Files.write(tempPath, input, StandardOpenOption.CREATE); + + byte[] fileDigest = Crypto.digest(tempPath.toFile()); + byte[] memoryDigest = Crypto.digest(input); + + assertArrayEquals(fileDigest, memoryDigest); + + Files.delete(tempPath); + } + @Test public void testPublicKeyToAddress() { byte[] publicKey = HashCode.fromString("775ada64a48a30b3bfc4f1db16bca512d4088704975a62bde78781ce0cba90d6").asBytes();