11 | * The {@link #digest()} method returns a raw binary representation of the hash. 12 | * The "content_hash" field in the Dropbox API is a hexadecimal-encoded version 13 | * of the digest. 14 | *
15 | * 16 | *17 | * Example: 18 | *
19 | * 20 | *
21 | * MessageDigest hasher = new DropboxContentHasher();
22 | * byte[] buf = new byte[1024];
23 | * InputStream in = new FileInputStream("some-file");
24 | * try {
25 | * while (true) {
26 | * int n = in.read(buf);
27 | * if (n < 0) break; // EOF
28 | * hasher.update(buf, 0, n);
29 | * }
30 | * }
31 | * finally {
32 | * in.close();
33 | * }
34 | *
35 | * byte[] rawHash = hasher.digest();
36 | * System.out.println(hex(rawHash));
37 | * // Assuming 'hex' is a method that converts a byte[] to
38 | * // a hexadecimal-encoded String
39 | *
40 | *
41 | * 42 | * If you need to hash something as it passes through a stream, you can use the 43 | * {@link java.security.DigestInputStream} or {@code java.security.DigestOutputStream} helpers. 44 | *
45 | * 46 | *
47 | * MessageDigest hasher = new DropboxContentHasher();
48 | * InputStream in = new FileInputStream("some-file");
49 | * UploadResponse r;
50 | * try {
51 | * r = someApiClient.upload(new DigestInputStream(in, hasher)));
52 | * }
53 | * finally {
54 | * in.close();
55 | * }
56 | *
57 | * String locallyComputed = hex(hasher.digest());
58 | * assert r.contentHash.equals(locallyComputed);
59 | *
60 | */
61 | public final class DropboxContentHasher extends MessageDigest implements Cloneable
62 | {
63 | private MessageDigest overallHasher;
64 | private MessageDigest blockHasher;
65 | private int blockPos = 0;
66 |
67 | public static final int BLOCK_SIZE = 4 * 1024 * 1024;
68 |
69 | public DropboxContentHasher()
70 | {
71 | this(newSha256Hasher(), newSha256Hasher(), 0);
72 | }
73 |
74 | private DropboxContentHasher(MessageDigest overallHasher, MessageDigest blockHasher, int blockPos)
75 | {
76 | super("Dropbox-Content-Hash");
77 | this.overallHasher = overallHasher;
78 | this.blockHasher = blockHasher;
79 | this.blockPos = blockPos;
80 | }
81 |
82 | @Override
83 | protected void engineUpdate(byte input)
84 | {
85 | finishBlockIfFull();
86 |
87 | blockHasher.update(input);
88 | blockPos += 1;
89 | }
90 |
91 | @Override
92 | protected int engineGetDigestLength()
93 | {
94 | return overallHasher.getDigestLength();
95 | }
96 |
97 | @Override
98 | protected void engineUpdate(byte[] input, int offset, int len)
99 | {
100 | int inputEnd = offset + len;
101 | while (offset < inputEnd) {
102 | finishBlockIfFull();
103 |
104 | int spaceInBlock = BLOCK_SIZE - this.blockPos;
105 | int inputPartEnd = Math.min(inputEnd, offset+spaceInBlock);
106 | int inputPartLength = inputPartEnd - offset;
107 | blockHasher.update(input, offset, inputPartLength);
108 |
109 | blockPos += inputPartLength;
110 | offset += inputPartLength;
111 | }
112 | }
113 |
114 | @Override
115 | protected void engineUpdate(ByteBuffer input)
116 | {
117 | int inputEnd = input.limit();
118 | while (input.position() < inputEnd) {
119 | finishBlockIfFull();
120 |
121 | int spaceInBlock = BLOCK_SIZE - this.blockPos;
122 | int inputPartEnd = Math.min(inputEnd, input.position()+spaceInBlock);
123 | int inputPartLength = inputPartEnd - input.position();
124 | input.limit(inputPartEnd);
125 | blockHasher.update(input);
126 |
127 | blockPos += inputPartLength;
128 | input.position(inputPartEnd);
129 | }
130 | }
131 |
132 | @Override
133 | protected byte[] engineDigest()
134 | {
135 | finishBlockIfNonEmpty();
136 | return overallHasher.digest();
137 | }
138 |
139 | @Override
140 | protected int engineDigest(byte[] buf, int offset, int len)
141 | throws DigestException
142 | {
143 | finishBlockIfNonEmpty();
144 | return overallHasher.digest(buf, offset, len);
145 | }
146 |
147 | @Override
148 | protected void engineReset()
149 | {
150 | this.overallHasher.reset();
151 | this.blockHasher.reset();
152 | this.blockPos = 0;
153 | }
154 |
155 | @Override
156 | public DropboxContentHasher clone()
157 | throws CloneNotSupportedException
158 | {
159 | DropboxContentHasher clone = (DropboxContentHasher) super.clone();
160 | clone.overallHasher = (MessageDigest) clone.overallHasher.clone();
161 | clone.blockHasher = (MessageDigest) clone.blockHasher.clone();
162 | return clone;
163 | }
164 |
165 | private void finishBlock()
166 | {
167 | overallHasher.update(blockHasher.digest());
168 | blockPos = 0;
169 | }
170 |
171 | private void finishBlockIfFull()
172 | {
173 | if (blockPos == BLOCK_SIZE) {
174 | finishBlock();
175 | }
176 | }
177 |
178 | private void finishBlockIfNonEmpty()
179 | {
180 | if (blockPos > 0) {
181 | finishBlock();
182 | }
183 | }
184 |
185 | static MessageDigest newSha256Hasher()
186 | {
187 | try {
188 | return MessageDigest.getInstance("SHA-256");
189 | }
190 | catch (NoSuchAlgorithmException ex) {
191 | throw new AssertionError("Couldn't create SHA-256 hasher");
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------