Skip to content

Commit 50e2d77

Browse files
committed
Add MessageDigestInputStream and deprecate
MessageDigestCalculatingInputStream
1 parent 7dd7e4b commit 50e2d77

9 files changed

Lines changed: 322 additions & 30 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ file comparators, endian transformation classes, and much more.
288288
<version>3.13.0</version>
289289
<scope>test</scope>
290290
</dependency>
291+
<dependency>
292+
<groupId>commons-codec</groupId>
293+
<artifactId>commons-codec</artifactId>
294+
<version>1.16.0</version>
295+
<scope>test</scope>
296+
</dependency>
291297
<dependency>
292298
<groupId>org.openjdk.jmh</groupId>
293299
<artifactId>jmh-core</artifactId>

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ The <action> type attribute can be add,update,fix,remove.
121121
<action dev="ggregory" type="add" due-to="Gary Gregory">
122122
Add org.apache.commons.io.StreamIterator.
123123
</action>
124+
<action dev="ggregory" type="add" due-to="Gary Gregory">
125+
Add MessageDigestInputStream and deprecate MessageDigestCalculatingInputStream.
126+
</action>
124127
<!-- UPDATE -->
125128
<action dev="ggregory" type="update" due-to="Gary Gregory">
126129
Bump org.apache.commons:commons-parent from 62 to 64.

src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
/**
2929
* This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer},
30-
* which calculates a checksum using a MessageDigest, for example an MD5 sum.
30+
* which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum.
3131
* <p>
3232
* To build an instance, see {@link Builder}.
3333
* </p>
@@ -36,12 +36,11 @@
3636
* Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
3737
* </p>
3838
* <p>
39-
* <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe. So is {@link MessageDigestCalculatingInputStream}.
40-
* </p>
41-
* <p>
42-
* TODO Rename to MessageDigestInputStream in 3.0.
39+
* <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestCalculatingInputStream}.
4340
* </p>
41+
* @deprecated Use {@link MessageDigestInputStream}.
4442
*/
43+
@Deprecated
4544
public class MessageDigestCalculatingInputStream extends ObservableInputStream {
4645

4746
/**
@@ -96,6 +95,9 @@ public MessageDigestCalculatingInputStream get() throws IOException {
9695

9796
/**
9897
* Sets the message digest.
98+
* <p>
99+
* The MD5 cryptographic algorithm is weak and should not be used.
100+
* </p>
99101
*
100102
* @param messageDigest the message digest.
101103
*/
@@ -105,6 +107,9 @@ public void setMessageDigest(final MessageDigest messageDigest) {
105107

106108
/**
107109
* Sets the name of the name of the message digest algorithm.
110+
* <p>
111+
* The MD5 cryptographic algorithm is weak and should not be used.
112+
* </p>
108113
*
109114
* @param algorithm the name of the algorithm. See the MessageDigest section in the
110115
* <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
@@ -192,6 +197,9 @@ public MessageDigestCalculatingInputStream(final InputStream inputStream) throws
192197

193198
/**
194199
* Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
200+
* <p>
201+
* The MD5 cryptographic algorithm is weak and should not be used.
202+
* </p>
195203
*
196204
* @param inputStream the stream to calculate the message digest for
197205
* @param messageDigest the message digest to use
@@ -206,6 +214,9 @@ public MessageDigestCalculatingInputStream(final InputStream inputStream, final
206214

207215
/**
208216
* Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the given algorithm.
217+
* <p>
218+
* The MD5 cryptographic algorithm is weak and should not be used.
219+
* </p>
209220
*
210221
* @param inputStream the stream to calculate the message digest for
211222
* @param algorithm the name of the algorithm requested. See the MessageDigest section in the
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.security.MessageDigest;
22+
import java.security.NoSuchAlgorithmException;
23+
import java.util.Objects;
24+
25+
import org.apache.commons.io.build.AbstractStreamBuilder;
26+
27+
/**
28+
* This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer},
29+
* which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum.
30+
* <p>
31+
* To build an instance, see {@link Builder}.
32+
* </p>
33+
* <p>
34+
* See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java
35+
* Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
36+
* </p>
37+
* <p>
38+
* You must specify a message digest algorithm name or instance.
39+
* </p>
40+
* <p>
41+
* <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}.
42+
* </p>
43+
*
44+
* @since 2.15.0
45+
*/
46+
public final class MessageDigestInputStream extends ObservableInputStream {
47+
48+
/**
49+
* Builds new {@link MessageDigestInputStream} instances.
50+
* <p>
51+
* For example:
52+
* </p>
53+
* <pre>{@code
54+
* MessageDigestInputStream s = MessageDigestInputStream.builder()
55+
* .setPath(path)
56+
* .setMessageDigest("SHA-512")
57+
* .get();}
58+
* </pre>
59+
* <p>
60+
* You must specify a message digest algorithm name or instance.
61+
* </p>
62+
*/
63+
public static class Builder extends AbstractStreamBuilder<MessageDigestInputStream, Builder> {
64+
65+
private MessageDigest messageDigest;
66+
67+
/**
68+
* Constructs a new Builder.
69+
*/
70+
public Builder() {
71+
// empty
72+
}
73+
74+
/**
75+
* Constructs a new instance.
76+
* <p>
77+
* This builder use the aspects InputStream, OpenOption[], and MessageDigest.
78+
* </p>
79+
* <p>
80+
* You must provide an origin that can be converted to an InputStream by this builder, otherwise, this call will throw an
81+
* {@link UnsupportedOperationException}.
82+
* </p>
83+
*
84+
* @return a new instance.
85+
* @throws UnsupportedOperationException if the origin cannot provide an InputStream.
86+
* @see #getInputStream()
87+
*/
88+
@SuppressWarnings("resource")
89+
@Override
90+
public MessageDigestInputStream get() throws IOException {
91+
return new MessageDigestInputStream(getInputStream(), messageDigest);
92+
}
93+
94+
/**
95+
* Sets the message digest.
96+
* <p>
97+
* The MD5 cryptographic algorithm is weak and should not be used.
98+
* </p>
99+
*
100+
* @param messageDigest the message digest.
101+
* @return this
102+
*/
103+
public Builder setMessageDigest(final MessageDigest messageDigest) {
104+
this.messageDigest = messageDigest;
105+
return this;
106+
}
107+
108+
/**
109+
* Sets the name of the name of the message digest algorithm.
110+
* <p>
111+
* The MD5 cryptographic algorithm is weak and should not be used.
112+
* </p>
113+
*
114+
* @param algorithm the name of the algorithm. See the MessageDigest section in the
115+
* <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
116+
* Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
117+
* @return this
118+
* @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
119+
*/
120+
public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
121+
this.messageDigest = MessageDigest.getInstance(algorithm);
122+
return this;
123+
}
124+
125+
}
126+
127+
/**
128+
* Maintains the message digest.
129+
*/
130+
public static class MessageDigestMaintainingObserver extends Observer {
131+
132+
private final MessageDigest messageDigest;
133+
134+
/**
135+
* Constructs an MessageDigestMaintainingObserver for the given MessageDigest.
136+
*
137+
* @param messageDigest the message digest to use
138+
* @throws NullPointerException if messageDigest is null.
139+
*/
140+
public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
141+
this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest");
142+
}
143+
144+
@Override
145+
public void data(final byte[] input, final int offset, final int length) throws IOException {
146+
messageDigest.update(input, offset, length);
147+
}
148+
149+
@Override
150+
public void data(final int input) throws IOException {
151+
messageDigest.update((byte) input);
152+
}
153+
}
154+
155+
/**
156+
* Constructs a new {@link Builder}.
157+
*
158+
* @return a new {@link Builder}.
159+
*/
160+
public static Builder builder() {
161+
return new Builder();
162+
}
163+
164+
private final MessageDigest messageDigest;
165+
166+
/**
167+
* Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
168+
* <p>
169+
* The MD5 cryptographic algorithm is weak and should not be used.
170+
* </p>
171+
*
172+
* @param inputStream the stream to calculate the message digest for
173+
* @param messageDigest the message digest to use
174+
* @throws NullPointerException if messageDigest is null.
175+
*/
176+
private MessageDigestInputStream(final InputStream inputStream, final MessageDigest messageDigest) {
177+
super(inputStream, new MessageDigestMaintainingObserver(messageDigest));
178+
this.messageDigest = messageDigest;
179+
}
180+
181+
/**
182+
* Gets the {@link MessageDigest}, which is being used for generating the checksum.
183+
* <p>
184+
* <em>Note</em>: The checksum will only reflect the data, which has been read so far. This is probably not, what you expect. Make sure, that the complete
185+
* data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
186+
* </p>
187+
*
188+
* @return the message digest used
189+
*/
190+
public MessageDigest getMessageDigest() {
191+
return messageDigest;
192+
}
193+
}

src/main/java/org/apache/commons/io/input/ObservableInputStream.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
* be used.
4040
* </p>
4141
*
42-
* @see MessageDigestCalculatingInputStream
42+
* @see MessageDigestInputStream
4343
*/
4444
public class ObservableInputStream extends ProxyInputStream {
4545

src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,40 +20,57 @@
2020

2121
import java.io.ByteArrayInputStream;
2222
import java.security.MessageDigest;
23-
import java.util.Random;
2423

24+
import org.apache.commons.codec.digest.DigestUtils;
25+
import org.apache.commons.codec.digest.MessageDigestAlgorithms;
26+
import org.apache.commons.io.input.MessageDigestCalculatingInputStream.Builder;
2527
import org.junit.jupiter.api.Test;
2628

2729
/**
2830
* Tests {@link MessageDigestCalculatingInputStream}.
2931
*/
32+
@SuppressWarnings("deprecation")
3033
public class MessageDigestCalculatingInputStreamTest {
3134

32-
public static byte[] generateRandomByteStream(final int pSize) {
33-
final byte[] buffer = new byte[pSize];
34-
final Random rnd = new Random();
35-
rnd.nextBytes(buffer);
36-
return buffer;
37-
}
38-
3935
@Test
40-
public void test() throws Exception {
36+
public void testNormalUse() throws Exception {
4137
for (int i = 256; i < 8192; i = i * 2) {
42-
final byte[] buffer = generateRandomByteStream(i);
43-
final MessageDigest messageDigest = MessageDigestCalculatingInputStream.getDefaultMessageDigest();
44-
final byte[] expect = messageDigest.digest(buffer);
38+
final byte[] buffer = MessageDigestInputStreamTest.generateRandomByteStream(i);
39+
final MessageDigest defaultMessageDigest = MessageDigestCalculatingInputStream.getDefaultMessageDigest();
40+
final byte[] defaultExpect = defaultMessageDigest.digest(buffer);
41+
// Defaults
4542
try (MessageDigestCalculatingInputStream messageDigestInputStream = new MessageDigestCalculatingInputStream(new ByteArrayInputStream(buffer))) {
4643
messageDigestInputStream.consume();
47-
assertArrayEquals(expect, messageDigestInputStream.getMessageDigest().digest());
44+
assertArrayEquals(defaultExpect, messageDigestInputStream.getMessageDigest().digest());
4845
}
4946
try (MessageDigestCalculatingInputStream messageDigestInputStream = MessageDigestCalculatingInputStream.builder()
5047
.setInputStream(new ByteArrayInputStream(buffer)).get()) {
5148
messageDigestInputStream.consume();
52-
assertArrayEquals(expect, messageDigestInputStream.getMessageDigest().digest());
49+
assertArrayEquals(defaultExpect, messageDigestInputStream.getMessageDigest().digest());
5350
}
5451
try (MessageDigestCalculatingInputStream messageDigestInputStream = MessageDigestCalculatingInputStream.builder().setByteArray(buffer).get()) {
5552
messageDigestInputStream.consume();
56-
assertArrayEquals(expect, messageDigestInputStream.getMessageDigest().digest());
53+
assertArrayEquals(defaultExpect, messageDigestInputStream.getMessageDigest().digest());
54+
}
55+
// SHA-512
56+
final byte[] sha512Expect = DigestUtils.sha512(buffer);
57+
{
58+
final Builder builder = MessageDigestCalculatingInputStream.builder();
59+
builder.setMessageDigest(MessageDigestAlgorithms.SHA_512);
60+
builder.setInputStream(new ByteArrayInputStream(buffer));
61+
try (MessageDigestCalculatingInputStream messageDigestInputStream = builder.get()) {
62+
messageDigestInputStream.consume();
63+
assertArrayEquals(sha512Expect, messageDigestInputStream.getMessageDigest().digest());
64+
}
65+
}
66+
{
67+
final Builder builder = MessageDigestCalculatingInputStream.builder();
68+
builder.setMessageDigest(MessageDigestAlgorithms.SHA_512);
69+
builder.setInputStream(new ByteArrayInputStream(buffer));
70+
try (MessageDigestCalculatingInputStream messageDigestInputStream = builder.get()) {
71+
messageDigestInputStream.consume();
72+
assertArrayEquals(sha512Expect, messageDigestInputStream.getMessageDigest().digest());
73+
}
5774
}
5875
}
5976
}

0 commit comments

Comments
 (0)