1 | /** |
2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
3 | * contributor license agreements. See the NOTICE file |
4 | * distributed with this work for additional information |
5 | * regarding copyright ownership. jclouds licenses this file |
6 | * to you under the Apache License, Version 2.0 (the |
7 | * "License"); you may not use this file except in compliance |
8 | * with the License. You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, |
13 | * software distributed under the License is distributed on an |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | * KIND, either express or implied. See the License for the |
16 | * specific language governing permissions and limitations |
17 | * under the License. |
18 | */ |
19 | package org.jclouds.crypto; |
20 | |
21 | import static com.google.common.base.Preconditions.checkNotNull; |
22 | |
23 | import java.io.ByteArrayOutputStream; |
24 | import java.io.IOException; |
25 | import java.io.InputStream; |
26 | import java.io.UnsupportedEncodingException; |
27 | import java.security.MessageDigest; |
28 | import java.security.NoSuchAlgorithmException; |
29 | import java.util.Arrays; |
30 | |
31 | import javax.crypto.Mac; |
32 | |
33 | import org.jclouds.encryption.internal.Base64; |
34 | import org.jclouds.io.InputSuppliers; |
35 | |
36 | import com.google.common.annotations.Beta; |
37 | import com.google.common.base.Charsets; |
38 | import com.google.common.base.Throwables; |
39 | import com.google.common.io.ByteProcessor; |
40 | import com.google.common.io.ByteStreams; |
41 | import com.google.common.io.InputSupplier; |
42 | |
43 | /** |
44 | * functions related to but not in {@link com.google.common.io.ByteStreams} |
45 | * |
46 | * @author Adrian Cole |
47 | */ |
48 | @Beta |
49 | public class CryptoStreams { |
50 | |
51 | public static String hex(byte[] in) { |
52 | byte[] hex = new byte[2 * in.length]; |
53 | int index = 0; |
54 | |
55 | for (byte b : in) { |
56 | int v = b & 0xFF; |
57 | hex[index++] = HEX_CHAR_TABLE[v >>> 4]; |
58 | hex[index++] = HEX_CHAR_TABLE[v & 0xF]; |
59 | } |
60 | try { |
61 | return new String(hex, "ASCII"); |
62 | } catch (UnsupportedEncodingException e) { |
63 | throw new RuntimeException(e); |
64 | } |
65 | } |
66 | |
67 | public static byte[] hex(String s) { |
68 | int len = s.length(); |
69 | byte[] data = new byte[len / 2]; |
70 | for (int i = 0; i < len; i += 2) { |
71 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); |
72 | } |
73 | return data; |
74 | } |
75 | |
76 | public static String base64(byte[] in) { |
77 | return Base64.encodeBytes(in, Base64.DONT_BREAK_LINES); |
78 | } |
79 | |
80 | public static byte[] base64(String in) { |
81 | return Base64.decode(in); |
82 | } |
83 | |
84 | /** |
85 | * @see #md5 |
86 | * @see #hex |
87 | */ |
88 | public static String md5Hex(InputSupplier<? extends InputStream> supplier) throws IOException { |
89 | return hex(md5(supplier)); |
90 | } |
91 | |
92 | /** |
93 | * @see #md5 |
94 | * @see #base64 |
95 | */ |
96 | public static String md5Base64(InputSupplier<? extends InputStream> supplier) throws IOException { |
97 | return base64(md5(supplier)); |
98 | } |
99 | |
100 | /** |
101 | * Computes and returns the MAC value for a supplied input stream. The mac |
102 | * object is reset when this method returns successfully. |
103 | * |
104 | * @param supplier |
105 | * the input stream factory |
106 | * @param mac |
107 | * the mac object |
108 | * @return the result of {@link Mac#doFinal()} after updating the mac object |
109 | * with all of the bytes in the stream and encoding in Base64 |
110 | * @throws IOException |
111 | * if an I/O error occurs |
112 | */ |
113 | public static String macBase64(InputSupplier<? extends InputStream> supplier, final Mac mac) throws IOException { |
114 | return base64(mac(supplier, mac)); |
115 | } |
116 | |
117 | /** |
118 | * Computes and returns the Digest value for a supplied input stream. The |
119 | * digest object is reset when this method returns successfully. |
120 | * |
121 | * @param supplier |
122 | * the input stream factory |
123 | * @param md |
124 | * the digest object |
125 | * @return the result of {@link MessageDigest#digest()} after updating the |
126 | * digest object with all of the bytes in the stream |
127 | * @throws IOException |
128 | * if an I/O error occurs |
129 | */ |
130 | public static byte[] digest(InputSupplier<? extends InputStream> supplier, final MessageDigest md) |
131 | throws IOException { |
132 | return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor<byte[]>() { |
133 | public boolean processBytes(byte[] buf, int off, int len) { |
134 | md.update(buf, off, len); |
135 | return true; |
136 | } |
137 | |
138 | public byte[] getResult() { |
139 | return md.digest(); |
140 | } |
141 | }); |
142 | } |
143 | |
144 | /** |
145 | * Computes and returns the SHA1 value for a supplied input stream. A digest |
146 | * object is created and disposed of at runtime, consider using |
147 | * {@link #digest} to be more efficient. |
148 | * |
149 | * @param supplier |
150 | * the input stream factory |
151 | * |
152 | * @return the result of {@link MessageDigest#digest()} after updating the |
153 | * sha1 object with all of the bytes in the stream |
154 | * @throws IOException |
155 | * if an I/O error occurs |
156 | */ |
157 | public static byte[] sha1(InputSupplier<? extends InputStream> supplier) throws IOException { |
158 | try { |
159 | return digest(supplier, MessageDigest.getInstance("SHA1")); |
160 | } catch (NoSuchAlgorithmException e) { |
161 | Throwables.propagate(e); |
162 | return null; |
163 | } |
164 | } |
165 | |
166 | public static byte[] sha1(byte[] in) { |
167 | try { |
168 | return sha1(ByteStreams.newInputStreamSupplier(in)); |
169 | } catch (IOException e) { |
170 | Throwables.propagate(e); |
171 | return null; |
172 | } |
173 | } |
174 | |
175 | /** |
176 | * Computes and returns the MD5 value for a supplied input stream. A digest |
177 | * object is created and disposed of at runtime, consider using |
178 | * {@link #digest} to be more efficient. |
179 | * |
180 | * @param supplier |
181 | * the input stream factory |
182 | * |
183 | * @return the result of {@link MessageDigest#digest()} after updating the |
184 | * md5 object with all of the bytes in the stream |
185 | * @throws IOException |
186 | * if an I/O error occurs |
187 | */ |
188 | public static byte[] md5(InputSupplier<? extends InputStream> supplier) throws IOException { |
189 | try { |
190 | return digest(supplier, MessageDigest.getInstance("MD5")); |
191 | } catch (NoSuchAlgorithmException e) { |
192 | Throwables.propagate(e); |
193 | return null; |
194 | } |
195 | } |
196 | |
197 | public static byte[] md5(byte[] in) { |
198 | try { |
199 | return md5(ByteStreams.newInputStreamSupplier(in)); |
200 | } catch (IOException e) { |
201 | Throwables.propagate(e); |
202 | return null; |
203 | } |
204 | } |
205 | |
206 | /** |
207 | * Computes and returns the MAC value for a supplied input stream. The mac |
208 | * object is reset when this method returns successfully. |
209 | * |
210 | * @param supplier |
211 | * the input stream factory |
212 | * @param mac |
213 | * the mac object |
214 | * @return the result of {@link Mac#doFinal()} after updating the mac object |
215 | * with all of the bytes in the stream |
216 | * @throws IOException |
217 | * if an I/O error occurs |
218 | */ |
219 | public static byte[] mac(InputSupplier<? extends InputStream> supplier, final Mac mac) throws IOException { |
220 | return com.google.common.io.ByteStreams.readBytes(checkNotNull(supplier, "supplier"), |
221 | new ByteProcessor<byte[]>() { |
222 | public boolean processBytes(byte[] buf, int off, int len) { |
223 | mac.update(buf, off, len); |
224 | return true; |
225 | } |
226 | |
227 | public byte[] getResult() { |
228 | return mac.doFinal(); |
229 | } |
230 | }); |
231 | } |
232 | |
233 | /** |
234 | * Computes and returns the base64 value for a supplied input stream. |
235 | * |
236 | * @param supplier |
237 | * the input stream factory |
238 | * |
239 | * @return the result of base 64 encoding all of the bytes in the stream |
240 | * @throws IOException |
241 | * if an I/O error occurs |
242 | */ |
243 | public static String base64Encode(InputSupplier<? extends InputStream> supplier) throws IOException { |
244 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
245 | return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Encoder(supplier), |
246 | new ByteProcessor<String>() { |
247 | public boolean processBytes(byte[] buf, int off, int len) { |
248 | out.write(buf, off, len); |
249 | return true; |
250 | } |
251 | |
252 | public String getResult() { |
253 | return new String(out.toByteArray(), Charsets.UTF_8); |
254 | } |
255 | }); |
256 | } |
257 | |
258 | /** |
259 | * Computes and returns the unencoded value for an input stream which is |
260 | * encoded in Base64. |
261 | * |
262 | * @param supplier |
263 | * the input stream factory |
264 | * |
265 | * @return the result of base 64 decoding all of the bytes in the stream |
266 | * @throws IOException |
267 | * if an I/O error occurs |
268 | */ |
269 | public static byte[] base64Decode(InputSupplier<? extends InputStream> supplier) throws IOException { |
270 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
271 | return com.google.common.io.ByteStreams.readBytes(InputSuppliers.base64Decoder(supplier), |
272 | new ByteProcessor<byte[]>() { |
273 | public boolean processBytes(byte[] buf, int off, int len) { |
274 | out.write(buf, off, len); |
275 | return true; |
276 | } |
277 | |
278 | public byte[] getResult() { |
279 | return out.toByteArray(); |
280 | } |
281 | }); |
282 | } |
283 | |
284 | final static byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', |
285 | (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', |
286 | (byte) 'f' }; |
287 | |
288 | /** |
289 | * Computes and returns the hex value for a supplied input stream. |
290 | * |
291 | * @param supplier |
292 | * the input stream factory |
293 | * |
294 | * @return the result of hex encoding all of the bytes in the stream |
295 | * @throws IOException |
296 | * if an I/O error occurs |
297 | */ |
298 | public static String hexEncode(InputSupplier<? extends InputStream> supplier) throws IOException { |
299 | final StringBuilder out = new StringBuilder(); |
300 | return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor<String>() { |
301 | public boolean processBytes(byte[] buf, int off, int len) { |
302 | char[] hex = new char[2 * len]; |
303 | int index = 0; |
304 | |
305 | for (int i = off; i < off + len; i++) { |
306 | byte b = buf[i]; |
307 | int v = b & 0xFF; |
308 | hex[index++] = (char) HEX_CHAR_TABLE[v >>> 4]; |
309 | hex[index++] = (char) HEX_CHAR_TABLE[v & 0xF]; |
310 | } |
311 | out.append(hex); |
312 | return true; |
313 | } |
314 | |
315 | public String getResult() { |
316 | return out.toString(); |
317 | } |
318 | }); |
319 | } |
320 | |
321 | /** |
322 | * Computes and returns the unencoded value for an input stream which is |
323 | * encoded in hex. |
324 | * |
325 | * @param supplier |
326 | * the input stream factory |
327 | * |
328 | * @return the result of hex decoding all of the bytes in the stream |
329 | * @throws IOException |
330 | * if an I/O error occurs |
331 | */ |
332 | public static byte[] hexDecode(InputSupplier<? extends InputStream> supplier) throws IOException { |
333 | final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
334 | return com.google.common.io.ByteStreams.readBytes(supplier, new ByteProcessor<byte[]>() { |
335 | int currentPos = 0; |
336 | |
337 | public boolean processBytes(byte[] buf, int off, int len) { |
338 | try { |
339 | if (currentPos == 0 && new String(Arrays.copyOfRange(buf, off, 2), "ASCII").equals("0x")) { |
340 | off += 2; |
341 | } |
342 | byte[] decoded = hex(new String(Arrays.copyOfRange(buf, off, len), "ASCII")); |
343 | out.write(decoded, 0, decoded.length); |
344 | currentPos += len; |
345 | } catch (UnsupportedEncodingException e) { |
346 | throw new IllegalStateException("ASCII must be supported"); |
347 | } |
348 | return true; |
349 | } |
350 | |
351 | public byte[] getResult() { |
352 | return out.toByteArray(); |
353 | } |
354 | }); |
355 | } |
356 | |
357 | } |