001: package org.bouncycastle.openpgp;
002:
003: import java.io.BufferedInputStream;
004: import java.io.File;
005: import java.io.FileInputStream;
006: import java.io.IOException;
007: import java.io.InputStream;
008: import java.io.OutputStream;
009: import java.security.MessageDigest;
010: import java.security.NoSuchAlgorithmException;
011: import java.security.NoSuchProviderException;
012: import java.security.SecureRandom;
013: import java.util.Date;
014:
015: import javax.crypto.SecretKey;
016: import javax.crypto.spec.SecretKeySpec;
017:
018: import org.bouncycastle.asn1.ASN1InputStream;
019: import org.bouncycastle.asn1.ASN1Sequence;
020: import org.bouncycastle.asn1.DERInteger;
021: import org.bouncycastle.bcpg.ArmoredInputStream;
022: import org.bouncycastle.bcpg.HashAlgorithmTags;
023: import org.bouncycastle.bcpg.MPInteger;
024: import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
025: import org.bouncycastle.bcpg.S2K;
026: import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
027: import org.bouncycastle.util.encoders.Base64;
028:
029: /**
030: * Basic utility class
031: */
032: public class PGPUtil implements HashAlgorithmTags {
033: private static String defProvider = "BC";
034:
035: /**
036: * Return the provider that will be used by factory classes in situations
037: * where a provider must be determined on the fly.
038: *
039: * @return String
040: */
041: public static String getDefaultProvider() {
042: return defProvider;
043: }
044:
045: /**
046: * Set the provider to be used by the package when it is necessary to
047: * find one on the fly.
048: *
049: * @param provider
050: */
051: public static void setDefaultProvider(String provider) {
052: defProvider = provider;
053: }
054:
055: static MPInteger[] dsaSigToMpi(byte[] encoding) throws PGPException {
056: ASN1InputStream aIn = new ASN1InputStream(encoding);
057:
058: DERInteger i1;
059: DERInteger i2;
060:
061: try {
062: ASN1Sequence s = (ASN1Sequence) aIn.readObject();
063:
064: i1 = (DERInteger) s.getObjectAt(0);
065: i2 = (DERInteger) s.getObjectAt(1);
066: } catch (IOException e) {
067: throw new PGPException("exception encoding signature", e);
068: }
069:
070: MPInteger[] values = new MPInteger[2];
071:
072: values[0] = new MPInteger(i1.getValue());
073: values[1] = new MPInteger(i2.getValue());
074:
075: return values;
076: }
077:
078: static String getDigestName(int hashAlgorithm) throws PGPException {
079: switch (hashAlgorithm) {
080: case HashAlgorithmTags.SHA1:
081: return "SHA1";
082: case HashAlgorithmTags.MD2:
083: return "MD2";
084: case HashAlgorithmTags.MD5:
085: return "MD5";
086: case HashAlgorithmTags.RIPEMD160:
087: return "RIPEMD160";
088: case HashAlgorithmTags.SHA256:
089: return "SHA256";
090: case HashAlgorithmTags.SHA384:
091: return "SHA384";
092: case HashAlgorithmTags.SHA512:
093: return "SHA512";
094: case HashAlgorithmTags.SHA224:
095: return "SHA224";
096: default:
097: throw new PGPException(
098: "unknown hash algorithm tag in getDigestName: "
099: + hashAlgorithm);
100: }
101: }
102:
103: static String getSignatureName(int keyAlgorithm, int hashAlgorithm)
104: throws PGPException {
105: String encAlg;
106:
107: switch (keyAlgorithm) {
108: case PublicKeyAlgorithmTags.RSA_GENERAL:
109: case PublicKeyAlgorithmTags.RSA_SIGN:
110: encAlg = "RSA";
111: break;
112: case PublicKeyAlgorithmTags.DSA:
113: encAlg = "DSA";
114: break;
115: case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases.
116: case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
117: encAlg = "ElGamal";
118: break;
119: default:
120: throw new PGPException(
121: "unknown algorithm tag in signature:"
122: + keyAlgorithm);
123: }
124:
125: return getDigestName(hashAlgorithm) + "with" + encAlg;
126: }
127:
128: static String getSymmetricCipherName(int algorithm)
129: throws PGPException {
130: switch (algorithm) {
131: case SymmetricKeyAlgorithmTags.NULL:
132: return null;
133: case SymmetricKeyAlgorithmTags.TRIPLE_DES:
134: return "DESEDE";
135: case SymmetricKeyAlgorithmTags.IDEA:
136: return "IDEA";
137: case SymmetricKeyAlgorithmTags.CAST5:
138: return "CAST5";
139: case SymmetricKeyAlgorithmTags.BLOWFISH:
140: return "Blowfish";
141: case SymmetricKeyAlgorithmTags.SAFER:
142: return "SAFER";
143: case SymmetricKeyAlgorithmTags.DES:
144: return "DES";
145: case SymmetricKeyAlgorithmTags.AES_128:
146: return "AES";
147: case SymmetricKeyAlgorithmTags.AES_192:
148: return "AES";
149: case SymmetricKeyAlgorithmTags.AES_256:
150: return "AES";
151: case SymmetricKeyAlgorithmTags.TWOFISH:
152: return "Twofish";
153: default:
154: throw new PGPException("unknown symmetric algorithm: "
155: + algorithm);
156: }
157: }
158:
159: public static SecretKey makeRandomKey(int algorithm,
160: SecureRandom random) throws PGPException {
161: String algName = null;
162: int keySize = 0;
163:
164: switch (algorithm) {
165: case SymmetricKeyAlgorithmTags.TRIPLE_DES:
166: keySize = 192;
167: algName = "DES_EDE";
168: break;
169: case SymmetricKeyAlgorithmTags.IDEA:
170: keySize = 128;
171: algName = "IDEA";
172: break;
173: case SymmetricKeyAlgorithmTags.CAST5:
174: keySize = 128;
175: algName = "CAST5";
176: break;
177: case SymmetricKeyAlgorithmTags.BLOWFISH:
178: keySize = 128;
179: algName = "Blowfish";
180: break;
181: case SymmetricKeyAlgorithmTags.SAFER:
182: keySize = 128;
183: algName = "SAFER";
184: break;
185: case SymmetricKeyAlgorithmTags.DES:
186: keySize = 64;
187: algName = "DES";
188: break;
189: case SymmetricKeyAlgorithmTags.AES_128:
190: keySize = 128;
191: algName = "AES";
192: break;
193: case SymmetricKeyAlgorithmTags.AES_192:
194: keySize = 192;
195: algName = "AES";
196: break;
197: case SymmetricKeyAlgorithmTags.AES_256:
198: keySize = 256;
199: algName = "AES";
200: break;
201: case SymmetricKeyAlgorithmTags.TWOFISH:
202: keySize = 256;
203: algName = "Twofish";
204: break;
205: default:
206: throw new PGPException("unknown symmetric algorithm: "
207: + algorithm);
208: }
209:
210: byte[] keyBytes = new byte[(keySize + 7) / 8];
211:
212: random.nextBytes(keyBytes);
213:
214: return new SecretKeySpec(keyBytes, algName);
215: }
216:
217: public static SecretKey makeKeyFromPassPhrase(int algorithm,
218: char[] passPhrase, String provider)
219: throws NoSuchProviderException, PGPException {
220: return makeKeyFromPassPhrase(algorithm, null, passPhrase,
221: provider);
222: }
223:
224: public static SecretKey makeKeyFromPassPhrase(int algorithm,
225: S2K s2k, char[] passPhrase, String provider)
226: throws PGPException, NoSuchProviderException {
227: String algName = null;
228: int keySize = 0;
229:
230: switch (algorithm) {
231: case SymmetricKeyAlgorithmTags.TRIPLE_DES:
232: keySize = 192;
233: algName = "DES_EDE";
234: break;
235: case SymmetricKeyAlgorithmTags.IDEA:
236: keySize = 128;
237: algName = "IDEA";
238: break;
239: case SymmetricKeyAlgorithmTags.CAST5:
240: keySize = 128;
241: algName = "CAST5";
242: break;
243: case SymmetricKeyAlgorithmTags.BLOWFISH:
244: keySize = 128;
245: algName = "Blowfish";
246: break;
247: case SymmetricKeyAlgorithmTags.SAFER:
248: keySize = 128;
249: algName = "SAFER";
250: break;
251: case SymmetricKeyAlgorithmTags.DES:
252: keySize = 64;
253: algName = "DES";
254: break;
255: case SymmetricKeyAlgorithmTags.AES_128:
256: keySize = 128;
257: algName = "AES";
258: break;
259: case SymmetricKeyAlgorithmTags.AES_192:
260: keySize = 192;
261: algName = "AES";
262: break;
263: case SymmetricKeyAlgorithmTags.AES_256:
264: keySize = 256;
265: algName = "AES";
266: break;
267: case SymmetricKeyAlgorithmTags.TWOFISH:
268: keySize = 256;
269: algName = "Twofish";
270: break;
271: default:
272: throw new PGPException("unknown symmetric algorithm: "
273: + algorithm);
274: }
275:
276: byte[] pBytes = new byte[passPhrase.length];
277: MessageDigest digest;
278:
279: for (int i = 0; i != passPhrase.length; i++) {
280: pBytes[i] = (byte) passPhrase[i];
281: }
282:
283: byte[] keyBytes = new byte[(keySize + 7) / 8];
284:
285: int generatedBytes = 0;
286: int loopCount = 0;
287:
288: while (generatedBytes < keyBytes.length) {
289: if (s2k != null) {
290: String digestName = getS2kDigestName(s2k);
291:
292: try {
293: digest = getDigestInstance(digestName, provider);
294: } catch (NoSuchAlgorithmException e) {
295: throw new PGPException("can't find S2K digest", e);
296: }
297:
298: for (int i = 0; i != loopCount; i++) {
299: digest.update((byte) 0);
300: }
301:
302: byte[] iv = s2k.getIV();
303:
304: switch (s2k.getType()) {
305: case S2K.SIMPLE:
306: digest.update(pBytes);
307: break;
308: case S2K.SALTED:
309: digest.update(iv);
310: digest.update(pBytes);
311: break;
312: case S2K.SALTED_AND_ITERATED:
313: long count = s2k.getIterationCount();
314: digest.update(iv);
315: digest.update(pBytes);
316:
317: count -= iv.length + pBytes.length;
318:
319: while (count > 0) {
320: if (count < iv.length) {
321: digest.update(iv, 0, (int) count);
322: break;
323: } else {
324: digest.update(iv);
325: count -= iv.length;
326: }
327:
328: if (count < pBytes.length) {
329: digest.update(pBytes, 0, (int) count);
330: count = 0;
331: } else {
332: digest.update(pBytes);
333: count -= pBytes.length;
334: }
335: }
336: break;
337: default:
338: throw new PGPException("unknown S2K type: "
339: + s2k.getType());
340: }
341: } else {
342: try {
343: digest = getDigestInstance("MD5", provider);
344: } catch (NoSuchAlgorithmException e) {
345: throw new PGPException("can't find MD5 digest", e);
346: }
347:
348: for (int i = 0; i != loopCount; i++) {
349: digest.update((byte) 0);
350: }
351:
352: digest.update(pBytes);
353: }
354:
355: byte[] dig = digest.digest();
356:
357: if (dig.length > (keyBytes.length - generatedBytes)) {
358: System.arraycopy(dig, 0, keyBytes, generatedBytes,
359: keyBytes.length - generatedBytes);
360: } else {
361: System.arraycopy(dig, 0, keyBytes, generatedBytes,
362: dig.length);
363: }
364:
365: generatedBytes += dig.length;
366:
367: loopCount++;
368: }
369:
370: for (int i = 0; i != pBytes.length; i++) {
371: pBytes[i] = 0;
372: }
373:
374: return new SecretKeySpec(keyBytes, algName);
375: }
376:
377: static MessageDigest getDigestInstance(String digestName,
378: String provider) throws NoSuchProviderException,
379: NoSuchAlgorithmException {
380: try {
381: return MessageDigest.getInstance(digestName, provider);
382: } catch (NoSuchAlgorithmException e) {
383: // try falling back
384: return MessageDigest.getInstance(digestName);
385: }
386: }
387:
388: private static String getS2kDigestName(S2K s2k) throws PGPException {
389: switch (s2k.getHashAlgorithm()) {
390: case HashAlgorithmTags.MD5:
391: return "MD5";
392: case HashAlgorithmTags.SHA1:
393: return "SHA1";
394: default:
395: throw new PGPException("unknown hash algorithm: "
396: + s2k.getHashAlgorithm());
397: }
398: }
399:
400: /**
401: * write out the passed in file as a literal data packet.
402: *
403: * @param out
404: * @param fileType the LiteralData type for the file.
405: * @param file
406: *
407: * @throws IOException
408: */
409: public static void writeFileToLiteralData(OutputStream out,
410: char fileType, File file) throws IOException {
411: PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
412: OutputStream pOut = lData.open(out, fileType, file.getName(),
413: file.length(), new Date(file.lastModified()));
414: FileInputStream in = new FileInputStream(file);
415: byte[] buf = new byte[4096];
416: int len;
417:
418: while ((len = in.read(buf)) > 0) {
419: pOut.write(buf, 0, len);
420: }
421:
422: lData.close();
423: in.close();
424: }
425:
426: /**
427: * write out the passed in file as a literal data packet in partial packet format.
428: *
429: * @param out
430: * @param fileType the LiteralData type for the file.
431: * @param file
432: * @param buffer buffer to be used to chunk the file into partial packets.
433: *
434: * @throws IOException
435: */
436: public static void writeFileToLiteralData(OutputStream out,
437: char fileType, File file, byte[] buffer) throws IOException {
438: PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
439: OutputStream pOut = lData.open(out, fileType, file.getName(),
440: new Date(file.lastModified()), buffer);
441: FileInputStream in = new FileInputStream(file);
442: byte[] buf = new byte[buffer.length];
443: int len;
444:
445: while ((len = in.read(buf)) > 0) {
446: pOut.write(buf, 0, len);
447: }
448:
449: lData.close();
450: in.close();
451: }
452:
453: private static final int READ_AHEAD = 60;
454:
455: private static boolean isPossiblyBase64(int ch) {
456: return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
457: || (ch >= '0' && ch <= '9') || (ch == '+')
458: || (ch == '/') || (ch == '\r') || (ch == '\n');
459: }
460:
461: /**
462: * Return either an ArmoredInputStream or a BCPGInputStream based on
463: * whether the initial characters of the stream are binary PGP encodings or not.
464: *
465: * @param in the stream to be wrapped
466: * @return a BCPGInputStream
467: * @throws IOException
468: */
469: public static InputStream getDecoderStream(InputStream in)
470: throws IOException {
471: if (!in.markSupported()) {
472: in = new BufferedInputStream(in);
473: }
474:
475: in.mark(READ_AHEAD);
476:
477: int ch = in.read();
478:
479: if ((ch & 0x80) != 0) {
480: in.reset();
481:
482: return in;
483: } else {
484: if (!isPossiblyBase64(ch)) {
485: in.reset();
486:
487: return new ArmoredInputStream(in);
488: }
489:
490: byte[] buf = new byte[READ_AHEAD];
491: int count = 1;
492: int index = 1;
493:
494: buf[0] = (byte) ch;
495: while (count != READ_AHEAD && (ch = in.read()) >= 0) {
496: if (!isPossiblyBase64(ch)) {
497: in.reset();
498:
499: return new ArmoredInputStream(in);
500: }
501:
502: if (ch != '\n' && ch != '\r') {
503: buf[index++] = (byte) ch;
504: }
505:
506: count++;
507: }
508:
509: in.reset();
510:
511: //
512: // nothing but new lines, little else, assume regular armoring
513: //
514: if (count < 4) {
515: return new ArmoredInputStream(in);
516: }
517:
518: //
519: // test our non-blank data
520: //
521: byte[] firstBlock = new byte[8];
522:
523: System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length);
524:
525: byte[] decoded = Base64.decode(firstBlock);
526:
527: //
528: // it's a base64 PGP block.
529: //
530: if ((decoded[0] & 0x80) != 0) {
531: return new ArmoredInputStream(in, false);
532: }
533:
534: return new ArmoredInputStream(in);
535: }
536: }
537: }
|