001: package org.bouncycastle.openpgp;
002:
003: import org.bouncycastle.bcpg.BCPGOutputStream;
004: import org.bouncycastle.bcpg.ContainedPacket;
005: import org.bouncycastle.bcpg.HashAlgorithmTags;
006: import org.bouncycastle.bcpg.PacketTags;
007: import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
008: import org.bouncycastle.bcpg.S2K;
009: import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
010: import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
011:
012: import javax.crypto.Cipher;
013: import javax.crypto.CipherOutputStream;
014: import javax.crypto.spec.IvParameterSpec;
015: import java.io.IOException;
016: import java.io.OutputStream;
017: import java.math.BigInteger;
018: import java.security.DigestOutputStream;
019: import java.security.Key;
020: import java.security.MessageDigest;
021: import java.security.NoSuchProviderException;
022: import java.security.SecureRandom;
023: import java.util.ArrayList;
024: import java.util.List;
025:
026: /**
027: * Generator for encrypted objects.
028: */
029: public class PGPEncryptedDataGenerator implements
030: SymmetricKeyAlgorithmTags, StreamGenerator {
031: private BCPGOutputStream pOut;
032: private CipherOutputStream cOut;
033: private Cipher c;
034: private boolean withIntegrityPacket = false;
035: private boolean oldFormat = false;
036: private DigestOutputStream digestOut;
037:
038: private abstract class EncMethod extends ContainedPacket {
039: protected byte[] sessionInfo;
040: protected int encAlgorithm;
041: protected Key key;
042:
043: public abstract void addSessionInfo(byte[] sessionInfo)
044: throws Exception;
045: }
046:
047: private class PBEMethod extends EncMethod {
048: S2K s2k;
049:
050: PBEMethod(int encAlgorithm, S2K s2k, Key key) {
051: this .encAlgorithm = encAlgorithm;
052: this .s2k = s2k;
053: this .key = key;
054: }
055:
056: public Key getKey() {
057: return key;
058: }
059:
060: public void addSessionInfo(byte[] sessionInfo) throws Exception {
061: String cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
062: Cipher c = Cipher.getInstance(cName + "/CFB/NoPadding",
063: defProvider);
064:
065: c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(
066: new byte[c.getBlockSize()]), rand);
067:
068: this .sessionInfo = c.doFinal(sessionInfo, 0,
069: sessionInfo.length - 2);
070: }
071:
072: public void encode(BCPGOutputStream pOut) throws IOException {
073: SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(
074: encAlgorithm, s2k, sessionInfo);
075:
076: pOut.writePacket(pk);
077: }
078: }
079:
080: private class PubMethod extends EncMethod {
081: PGPPublicKey pubKey;
082: BigInteger[] data;
083:
084: PubMethod(PGPPublicKey pubKey) {
085: this .pubKey = pubKey;
086: }
087:
088: public void addSessionInfo(byte[] sessionInfo) throws Exception {
089: Cipher c;
090:
091: switch (pubKey.getAlgorithm()) {
092: case PGPPublicKey.RSA_ENCRYPT:
093: case PGPPublicKey.RSA_GENERAL:
094: c = Cipher.getInstance("RSA/ECB/PKCS1Padding",
095: defProvider);
096: break;
097: case PGPPublicKey.ELGAMAL_ENCRYPT:
098: case PGPPublicKey.ELGAMAL_GENERAL:
099: c = Cipher.getInstance("ElGamal/ECB/PKCS1Padding",
100: defProvider);
101: break;
102: case PGPPublicKey.DSA:
103: throw new PGPException("Can't use DSA for encryption.");
104: case PGPPublicKey.ECDSA:
105: throw new PGPException(
106: "Can't use ECDSA for encryption.");
107: default:
108: throw new PGPException("unknown asymmetric algorithm: "
109: + pubKey.getAlgorithm());
110: }
111:
112: Key key = pubKey.getKey(defProvider);
113:
114: c.init(Cipher.ENCRYPT_MODE, key);
115:
116: byte[] encKey = c.doFinal(sessionInfo);
117:
118: switch (pubKey.getAlgorithm()) {
119: case PGPPublicKey.RSA_ENCRYPT:
120: case PGPPublicKey.RSA_GENERAL:
121: data = new BigInteger[1];
122:
123: data[0] = new BigInteger(1, encKey);
124: break;
125: case PGPPublicKey.ELGAMAL_ENCRYPT:
126: case PGPPublicKey.ELGAMAL_GENERAL:
127: byte[] b1 = new byte[encKey.length / 2];
128: byte[] b2 = new byte[encKey.length / 2];
129:
130: System.arraycopy(encKey, 0, b1, 0, b1.length);
131: System.arraycopy(encKey, b1.length, b2, 0, b2.length);
132:
133: data = new BigInteger[2];
134: data[0] = new BigInteger(1, b1);
135: data[1] = new BigInteger(1, b2);
136: break;
137: default:
138: throw new PGPException("unknown asymmetric algorithm: "
139: + encAlgorithm);
140: }
141: }
142:
143: public void encode(BCPGOutputStream pOut) throws IOException {
144: PublicKeyEncSessionPacket pk = new PublicKeyEncSessionPacket(
145: pubKey.getKeyID(), pubKey.getAlgorithm(), data);
146:
147: pOut.writePacket(pk);
148: }
149: }
150:
151: private List methods = new ArrayList();
152: private int defAlgorithm;
153: private SecureRandom rand;
154: private String defProvider;
155:
156: /**
157: * Base constructor.
158: *
159: * @param encAlgorithm the symmetric algorithm to use.
160: * @param rand source of randomness
161: * @param provider the provider to use for encryption algorithms.
162: */
163: public PGPEncryptedDataGenerator(int encAlgorithm,
164: SecureRandom rand, String provider) {
165: this .defAlgorithm = encAlgorithm;
166: this .rand = rand;
167: this .defProvider = provider;
168: }
169:
170: /**
171: * Creates a cipher stream which will have an integrity packet
172: * associated with it.
173: *
174: * @param encAlgorithm
175: * @param withIntegrityPacket
176: * @param rand
177: * @param provider
178: */
179: public PGPEncryptedDataGenerator(int encAlgorithm,
180: boolean withIntegrityPacket, SecureRandom rand,
181: String provider) {
182: this .defAlgorithm = encAlgorithm;
183: this .rand = rand;
184: this .defProvider = provider;
185: this .withIntegrityPacket = withIntegrityPacket;
186: }
187:
188: /**
189: * Base constructor.
190: *
191: * @param encAlgorithm the symmetric algorithm to use.
192: * @param rand source of randomness
193: * @param oldFormat PGP 2.6.x compatability required.
194: * @param provider the provider to use for encryption algorithms.
195: */
196: public PGPEncryptedDataGenerator(int encAlgorithm,
197: SecureRandom rand, boolean oldFormat, String provider) {
198: this .defAlgorithm = encAlgorithm;
199: this .rand = rand;
200: this .defProvider = provider;
201: this .oldFormat = oldFormat;
202: }
203:
204: /**
205: * Add a PBE encryption method to the encrypted object.
206: *
207: * @param passPhrase
208: * @throws NoSuchProviderException
209: * @throws PGPException
210: */
211: public void addMethod(char[] passPhrase)
212: throws NoSuchProviderException, PGPException {
213: byte[] iv = new byte[8];
214:
215: rand.nextBytes(iv);
216:
217: S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
218:
219: methods.add(new PBEMethod(defAlgorithm, s2k, PGPUtil
220: .makeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase,
221: defProvider)));
222: }
223:
224: /**
225: * Add a public key encrypted session key to the encrypted object.
226: *
227: * @param key
228: * @throws NoSuchProviderException
229: * @throws PGPException
230: */
231: public void addMethod(PGPPublicKey key)
232: throws NoSuchProviderException, PGPException {
233: if (!key.isEncryptionKey()) {
234: throw new IllegalArgumentException(
235: "passed in key not an encryption key!");
236: }
237:
238: methods.add(new PubMethod(key));
239: }
240:
241: private void addCheckSum(byte[] sessionInfo) {
242: int check = 0;
243:
244: for (int i = 1; i != sessionInfo.length - 2; i++) {
245: check += sessionInfo[i] & 0xff;
246: }
247:
248: sessionInfo[sessionInfo.length - 2] = (byte) (check >> 8);
249: sessionInfo[sessionInfo.length - 1] = (byte) (check);
250: }
251:
252: private byte[] createSessionInfo(int algorithm, Key key) {
253: byte[] keyBytes = key.getEncoded();
254: byte[] sessionInfo = new byte[keyBytes.length + 3];
255: sessionInfo[0] = (byte) algorithm;
256: System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
257: addCheckSum(sessionInfo);
258: return sessionInfo;
259: }
260:
261: /**
262: * If buffer is non null stream assumed to be partial, otherwise the length will be used
263: * to output a fixed length packet. The stream can be closed off by either calling close()
264: * on the stream or close() on the generator.
265: *
266: * @param out
267: * @param length
268: * @param buffer
269: * @return
270: * @throws IOException
271: * @throws PGPException
272: * @throws IllegalStateException
273: */
274: private OutputStream open(OutputStream out, long length,
275: byte[] buffer) throws IOException, PGPException,
276: IllegalStateException {
277: if (cOut != null) {
278: throw new IllegalStateException(
279: "generator already in open state");
280: }
281:
282: if (methods.size() == 0) {
283: throw new IllegalStateException(
284: "no encryption methods specified");
285: }
286:
287: Key key = null;
288:
289: pOut = new BCPGOutputStream(out);
290:
291: if (methods.size() == 1) {
292: if (methods.get(0) instanceof PBEMethod) {
293: PBEMethod m = (PBEMethod) methods.get(0);
294:
295: key = m.getKey();
296: } else {
297: key = PGPUtil.makeRandomKey(defAlgorithm, rand);
298: byte[] sessionInfo = createSessionInfo(defAlgorithm,
299: key);
300:
301: PubMethod m = (PubMethod) methods.get(0);
302:
303: try {
304: m.addSessionInfo(sessionInfo);
305: } catch (Exception e) {
306: throw new PGPException(
307: "exception encrypting session key", e);
308: }
309: }
310:
311: pOut.writePacket((ContainedPacket) methods.get(0));
312: } else // multiple methods
313: {
314: key = PGPUtil.makeRandomKey(defAlgorithm, rand);
315: byte[] sessionInfo = createSessionInfo(defAlgorithm, key);
316:
317: for (int i = 0; i != methods.size(); i++) {
318: EncMethod m = (EncMethod) methods.get(i);
319:
320: try {
321: m.addSessionInfo(sessionInfo);
322: } catch (Exception e) {
323: throw new PGPException(
324: "exception encrypting session key", e);
325: }
326:
327: pOut.writePacket(m);
328: }
329: }
330:
331: String cName = PGPUtil.getSymmetricCipherName(defAlgorithm);
332:
333: if (cName == null) {
334: throw new PGPException("null cipher specified");
335: }
336:
337: try {
338: if (withIntegrityPacket) {
339: c = Cipher.getInstance(cName + "/CFB/NoPadding",
340: defProvider);
341: } else {
342: c = Cipher.getInstance(cName + "/OpenPGPCFB/NoPadding",
343: defProvider);
344: }
345:
346: c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(
347: new byte[c.getBlockSize()]));
348:
349: if (buffer == null) {
350: //
351: // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
352: //
353: if (withIntegrityPacket) {
354: pOut = new BCPGOutputStream(out,
355: PacketTags.SYM_ENC_INTEGRITY_PRO, length
356: + c.getBlockSize() + 2 + 1 + 22);
357: pOut.write(1); // version number
358: } else {
359: pOut = new BCPGOutputStream(out,
360: PacketTags.SYMMETRIC_KEY_ENC, length
361: + c.getBlockSize() + 2, oldFormat);
362: }
363: } else {
364: if (withIntegrityPacket) {
365: pOut = new BCPGOutputStream(out,
366: PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
367: pOut.write(1); // version number
368: } else {
369: pOut = new BCPGOutputStream(out,
370: PacketTags.SYMMETRIC_KEY_ENC, buffer);
371: }
372: }
373:
374: OutputStream genOut = cOut = new CipherOutputStream(pOut, c);
375:
376: if (withIntegrityPacket) {
377: String digestName = PGPUtil
378: .getDigestName(HashAlgorithmTags.SHA1);
379: MessageDigest digest = MessageDigest.getInstance(
380: digestName, defProvider);
381: genOut = digestOut = new DigestOutputStream(cOut,
382: digest);
383: }
384:
385: byte[] inLineIv = new byte[c.getBlockSize() + 2];
386: rand.nextBytes(inLineIv);
387: inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
388: inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];
389:
390: genOut.write(inLineIv);
391:
392: return new WrappedGeneratorStream(genOut, this );
393: } catch (Exception e) {
394: throw new PGPException("Exception creating cipher", e);
395: }
396: }
397:
398: /**
399: * Return an outputstream which will encrypt the data as it is written
400: * to it. The stream can be closed off by either calling close()
401: * on the stream or close() on the generator.
402: *
403: * @param out
404: * @param length
405: * @return OutputStream
406: * @throws IOException
407: * @throws PGPException
408: */
409: public OutputStream open(OutputStream out, long length)
410: throws IOException, PGPException {
411: return this .open(out, length, null);
412: }
413:
414: /**
415: * Return an outputstream which will encrypt the data as it is written
416: * to it. The stream will be written out in chunks according to the size of the
417: * passed in buffer. The stream can be closed off by either calling close()
418: * on the stream or close() on the generator.
419: * <p>
420: * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
421: * bytes worth of the buffer will be used.
422: *
423: * @param out
424: * @param buffer the buffer to use.
425: * @return OutputStream
426: * @throws IOException
427: * @throws PGPException
428: */
429: public OutputStream open(OutputStream out, byte[] buffer)
430: throws IOException, PGPException {
431: return this .open(out, 0, buffer);
432: }
433:
434: /**
435: * Close off the encrypted object - this is equivalent to calling close on the stream
436: * returned by the open() method.
437: *
438: * @throws IOException
439: */
440: public void close() throws IOException {
441: if (cOut != null) {
442: if (digestOut != null) {
443: //
444: // hand code a mod detection packet
445: //
446: BCPGOutputStream bOut = new BCPGOutputStream(digestOut,
447: PacketTags.MOD_DETECTION_CODE, 20);
448:
449: bOut.flush();
450: digestOut.flush();
451:
452: byte[] dig = digestOut.getMessageDigest().digest();
453:
454: cOut.write(dig);
455: }
456:
457: cOut.flush();
458:
459: try {
460: pOut.write(c.doFinal());
461: pOut.finish();
462: } catch (Exception e) {
463: throw new IOException(e.toString());
464: }
465:
466: cOut = null;
467: pOut = null;
468: }
469: }
470: }
|