Skip to content

Commit 143ba71

Browse files
committed
added ProgressListener
1 parent 52b0678 commit 143ba71

File tree

6 files changed

+61
-31
lines changed

6 files changed

+61
-31
lines changed

src/de/ntcomputer/crypto/eddsa/Ed25519PrivateKey.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import javax.security.auth.Destroyable;
3131

3232
import de.ntcomputer.crypto.hash.HashCondenser;
33+
import de.ntcomputer.crypto.hash.ProgressListener;
3334
import net.i2p.crypto.eddsa.EdDSAEngine;
3435
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
3536
import net.i2p.crypto.eddsa.EdDSAPublicKey;
@@ -252,7 +253,7 @@ private String signLow(byte[] data) {
252253
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
253254
*/
254255
public String sign(byte[] data) throws NoSuchAlgorithmException {
255-
return signLow(HashCondenser.getInstance().compute(data));
256+
return signLow(HashCondenser.getInstance().compute(data, null));
256257
}
257258

258259
/**
@@ -273,30 +274,32 @@ public String sign(String data) throws NoSuchAlgorithmException {
273274
*
274275
* @param source the data source
275276
* @param sourceSize the exact size of all data which will pass through the InputStream
277+
* @param listener a progress listener. Optional, may be null.
276278
* @return a hexadecimal representation of the signature
277279
* @throws IllegalArgumentException if sourceSize is not the correct size
278280
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
279281
* @throws IOException if an IO error occurs while reading the stream
280282
*/
281-
public String sign(InputStream source, long sourceSize) throws IllegalArgumentException, NoSuchAlgorithmException, IOException {
282-
return signLow(HashCondenser.getInstance().compute(source, sourceSize));
283+
public String sign(InputStream source, long sourceSize, ProgressListener listener) throws IllegalArgumentException, NoSuchAlgorithmException, IOException {
284+
return signLow(HashCondenser.getInstance().compute(source, sourceSize, listener));
283285
}
284286

285287
/**
286288
* Signs the content of the given file.
287289
*
288290
* @see #sign(InputStream, long)
289291
* @param source
292+
* @param listener a progress listener. Optional, may be null.
290293
* @return a hexadecimal representation of the signature
291294
* @throws IOException if an IO error occurs while reading the file
292295
* @throws IllegalArgumentException if the file's size changes during computation
293296
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
294297
*/
295-
public String sign(File source) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
298+
public String sign(File source, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
296299
if(!source.isFile()) throw new FileNotFoundException(source.getAbsolutePath());
297300
long fileSize = source.length();
298301
try(InputStream is = new FileInputStream(source)) {
299-
return this.sign(is, fileSize);
302+
return this.sign(is, fileSize, listener);
300303
}
301304
}
302305

@@ -306,12 +309,13 @@ public String sign(File source) throws IOException, IllegalArgumentException, No
306309
*
307310
* @see #signToFile(File, File)
308311
* @param source
312+
* @param listener a progress listener. Optional, may be null.
309313
* @throws IOException if an IO error occurs while reading or writing a file
310314
* @throws IllegalArgumentException if the file's size changes during computation
311315
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
312316
*/
313-
public void signToFile(File source) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
314-
this.signToFile(source, new File(source, ".sig"));
317+
public void signToFile(File source, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
318+
this.signToFile(source, new File(source, ".sig"), listener);
315319
}
316320

317321
/**
@@ -320,12 +324,13 @@ public void signToFile(File source) throws IOException, IllegalArgumentException
320324
* @see #sign(File)
321325
* @param source
322326
* @param signatureFile the file to write the signature to
327+
* @param listener a progress listener. Optional, may be null.
323328
* @throws IOException if an IO error occurs while reading or writing a file
324329
* @throws IllegalArgumentException if the file's size changes during computation
325330
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
326331
*/
327-
public void signToFile(File source, File signatureFile) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
328-
String signature = this.sign(source);
332+
public void signToFile(File source, File signatureFile, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException {
333+
String signature = this.sign(source, listener);
329334
Files.write(signatureFile.toPath(), signature.getBytes(StandardCharsets.UTF_8));
330335
}
331336

src/de/ntcomputer/crypto/eddsa/Ed25519PublicKey.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.security.SignatureException;
1313

1414
import de.ntcomputer.crypto.hash.HashCondenser;
15+
import de.ntcomputer.crypto.hash.ProgressListener;
1516
import net.i2p.crypto.eddsa.EdDSAEngine;
1617
import net.i2p.crypto.eddsa.EdDSAPublicKey;
1718
import net.i2p.crypto.eddsa.Utils;
@@ -123,7 +124,7 @@ private boolean verifyLow(byte[] data, String signature) throws SignatureExcepti
123124
* @throws SignatureException if the given signature has an invalid format
124125
*/
125126
public boolean verify(byte[] data, String signature) throws NoSuchAlgorithmException, SignatureException {
126-
return this.verifyLow(HashCondenser.getInstance().compute(data), signature);
127+
return this.verifyLow(HashCondenser.getInstance().compute(data, null), signature);
127128
}
128129

129130
/**
@@ -147,14 +148,15 @@ public boolean verify(String data, String signature) throws NoSuchAlgorithmExcep
147148
* @param source the data source
148149
* @param sourceSize the exact size of all data which will pass through the InputStream
149150
* @param signature a hexadecimal representation of the signature (generated by {@link Ed25519PrivateKey#sign(InputStream, long)}
151+
* @param listener a progress listener. Optional, may be null.
150152
* @return true if the signature is correct. False otherwise.
151153
* @throws IllegalArgumentException if sourceSize is not the correct size
152154
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
153155
* @throws IOException if an IO error occurs while reading the stream
154156
* @throws SignatureException if the given signature has an invalid format
155157
*/
156-
public boolean verify(InputStream source, long sourceSize, String signature) throws IllegalArgumentException, NoSuchAlgorithmException, IOException, SignatureException {
157-
return this.verifyLow(HashCondenser.getInstance().compute(source, sourceSize), signature);
158+
public boolean verify(InputStream source, long sourceSize, String signature, ProgressListener listener) throws IllegalArgumentException, NoSuchAlgorithmException, IOException, SignatureException {
159+
return this.verifyLow(HashCondenser.getInstance().compute(source, sourceSize, listener), signature);
158160
}
159161

160162
/**
@@ -163,17 +165,18 @@ public boolean verify(InputStream source, long sourceSize, String signature) thr
163165
* @see #verify(InputStream, long, String)
164166
* @param source
165167
* @param signature a hexadecimal representation of the signature (generated by {@link Ed25519PrivateKey#sign(File)}
168+
* @param listener a progress listener. Optional, may be null.
166169
* @return true if the signature is correct. False otherwise.
167170
* @throws IOException if an IO error occurs while reading the file
168171
* @throws IllegalArgumentException if the file's size changes during computation
169172
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
170173
* @throws SignatureException if the given signature has an invalid format
171174
*/
172-
public boolean verify(File source, String signature) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
175+
public boolean verify(File source, String signature, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
173176
if(!source.isFile()) throw new FileNotFoundException(source.getAbsolutePath());
174177
long fileSize = source.length();
175178
try(InputStream is = new FileInputStream(source)) {
176-
return this.verify(is, fileSize, signature);
179+
return this.verify(is, fileSize, signature, listener);
177180
}
178181
}
179182

@@ -183,14 +186,15 @@ public boolean verify(File source, String signature) throws IOException, Illegal
183186
*
184187
* @see #verifyFromFile(File, File)
185188
* @param source
189+
* @param listener a progress listener. Optional, may be null.
186190
* @return true if the signature is correct. False otherwise.
187191
* @throws IOException if an IO error occurs while reading a file
188192
* @throws IllegalArgumentException if the file's size changes during computation
189193
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
190194
* @throws SignatureException if the signature in the .sig file has an invalid format
191195
*/
192-
public boolean verifyFromFile(File source) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
193-
return this.verifyFromFile(source, new File(source, ".sig"));
196+
public boolean verifyFromFile(File source, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
197+
return this.verifyFromFile(source, new File(source, ".sig"), listener);
194198
}
195199

196200
/**
@@ -199,15 +203,16 @@ public boolean verifyFromFile(File source) throws IOException, IllegalArgumentEx
199203
* @see #verify(File, String)
200204
* @param source
201205
* @param signatureFile the file to write the signature to
206+
* @param listener a progress listener. Optional, may be null.
202207
* @return true if the signature is correct. False otherwise.
203208
* @throws IOException if an IO error occurs while reading a file
204209
* @throws IllegalArgumentException if the file's size changes during computation
205210
* @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
206211
* @throws SignatureException if the signature in the signature file has an invalid format
207212
*/
208-
public boolean verifyFromFile(File source, File signatureFile) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
213+
public boolean verifyFromFile(File source, File signatureFile, ProgressListener listener) throws IOException, IllegalArgumentException, NoSuchAlgorithmException, SignatureException {
209214
String signature = new String(Files.readAllBytes(signatureFile.toPath()), StandardCharsets.UTF_8);
210-
return this.verify(source, signature);
215+
return this.verify(source, signature, listener);
211216
}
212217

213218
}

src/de/ntcomputer/crypto/hash/HashCondenser.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import java.security.NoSuchAlgorithmException;
1010
import java.util.Arrays;
1111

12-
import net.i2p.crypto.eddsa.Utils;
13-
1412
/**
1513
* A class which packs any byte input stream or array into a fixed-size output byte array using a {@link MessageDigest}.
1614
* This is useful when there is a need to sign or verify large files with a signature scheme that requires caching the input data (such as Ed25519).
@@ -98,11 +96,12 @@ private HashCondenser(MessageDigest md, MessageDigest md2, int outputSize) {
9896
*
9997
* @see #compute(InputStream, long)
10098
* @param data
99+
* @param listener a progress listener. Optional, may be null.
101100
* @return the condensed version of the input
102101
*/
103-
public byte[] compute(byte[] data) {
102+
public byte[] compute(byte[] data, ProgressListener listener) {
104103
try {
105-
return this.compute(new ByteArrayInputStream(data), data.length);
104+
return this.compute(new ByteArrayInputStream(data), data.length, listener);
106105
} catch (IOException e) {
107106
// IOException should never happen for cached byte stream
108107
throw new RuntimeException(e);
@@ -116,12 +115,15 @@ public byte[] compute(byte[] data) {
116115
* Every result contains the original sourceSize and a operation mode indicator.
117116
*
118117
* @param source
119-
* @param sourceSize the exact number of bytes which can be read from source
118+
* @param sourceSize the exact number of bytes which can be read from source
119+
* @param listener a progress listener. Optional, may be null.
120120
* @return the condensed version of the input
121121
* @throws IOException when source throws an IOException
122122
* @throws IllegalArgumentException if sourceSize happens not to be the same as source's size
123123
*/
124-
public byte[] compute(InputStream source, long sourceSize) throws IOException, IllegalArgumentException {
124+
public byte[] compute(InputStream source, long sourceSize, ProgressListener listener) throws IOException, IllegalArgumentException {
125+
if(listener!=null) listener.onProgress(0, sourceSize);
126+
125127
// create result buffer
126128
byte[] result = new byte[this.outputSize];
127129
Arrays.fill(result, (byte) 0x00);
@@ -151,6 +153,7 @@ public byte[] compute(InputStream source, long sourceSize) throws IOException, I
151153
} else {
152154
// otherwise, hash segments
153155
this.segmentDigest.reset();
156+
long lastProgressUpdateTime = System.currentTimeMillis();
154157

155158
// add file size + positive sign as compressed mode indicator
156159
ByteBuffer.wrap(result).order(ByteOrder.BIG_ENDIAN).putLong(sourceSize);
@@ -203,6 +206,15 @@ public byte[] compute(InputStream source, long sourceSize) throws IOException, I
203206
this.segmentDigest.update(buf, readIndex, readLength);
204207
previouslyReadSegmentSize+= readLength;
205208
}
209+
210+
if(listener!=null) {
211+
long currentTime = System.currentTimeMillis();
212+
if(currentTime > lastProgressUpdateTime + 100) {
213+
lastProgressUpdateTime = currentTime;
214+
listener.onProgress(readSourceSize, sourceSize);
215+
}
216+
}
217+
206218
}
207219

208220
if(readSourceSize != sourceSize) throw new IllegalArgumentException("read not as many bytes as sourceSize originally provided. Maybe the resource changed?");
@@ -212,7 +224,7 @@ public byte[] compute(InputStream source, long sourceSize) throws IOException, I
212224
// put one digest over the whole stream into the result
213225
System.arraycopy(this.digest.digest(), 0, result, LONG_SIZE, digestLength);
214226

215-
System.out.println(Utils.bytesToHex(result));
227+
if(listener!=null) listener.onProgress(sourceSize, sourceSize);
216228

217229
return result;
218230
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package de.ntcomputer.crypto.hash;
2+
3+
@FunctionalInterface
4+
public interface ProgressListener {
5+
6+
public void onProgress(long progress, long limit);
7+
8+
}

test/de/ntcomputer/crypto/eddsa/Ed25519Test.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,27 @@ public void testKeyLifecycle() throws Exception {
3232

3333
URL testSignDataURL = HashCondenserTest.class.getResource("randomdata.bin");
3434
File testSignDataFile = new File(testSignDataURL.toURI());
35-
String signature = privateKey.sign(testSignDataFile);
35+
String signature = privateKey.sign(testSignDataFile, null);
3636

37-
boolean verifyResult = publicKey.verify(testSignDataFile, signature);
37+
boolean verifyResult = publicKey.verify(testSignDataFile, signature, null);
3838
assertThat("Ed25519 signature verification failure", verifyResult, is(true));
3939

4040
char[] password = new BigInteger(130, random).toString(32).toCharArray();
4141
privateKey.saveAsFile(privateKeyFile, password);
4242
privateKey = Ed25519PrivateKey.loadFromFile(privateKeyFile, password);
4343

44-
String signature2 = privateKey.sign(testSignDataFile);
44+
String signature2 = privateKey.sign(testSignDataFile, null);
4545
assertThat("Ed25519 signature regeneration failure", signature2, is(equalTo(signature)));
4646

4747
publicKey.saveAsFile(publicKeyFile);
4848
publicKey = Ed25519PublicKey.loadFromFile(publicKeyFile);
4949

50-
verifyResult = publicKey.verify(testSignDataFile, signature);
50+
verifyResult = publicKey.verify(testSignDataFile, signature, null);
5151
assertThat("Ed25519 signature reverification failure", verifyResult, is(true));
5252

5353
exception.expect(SignatureException.class);
5454
exception.expectMessage("signature length is wrong");
55-
publicKey.verify(testSignDataFile, "");
55+
publicKey.verify(testSignDataFile, "", null);
5656

5757
} finally {
5858
privateKeyFile.delete();

test/de/ntcomputer/crypto/hash/HashCondenserTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void testHash() throws Exception {
3636
// run hash condenser
3737
HashCondenser cond = HashCondenser.getInstance(131);
3838
try (InputStream is = new FileInputStream(file)) {
39-
byte[] result = cond.compute(is, fileSize);
39+
byte[] result = cond.compute(is, fileSize, null);
4040
assertThat("HashCondenser failed to produce correct result", result, is(equalTo(expectedBin)));
4141
}
4242
}

0 commit comments

Comments
 (0)