Logo Search packages:      
Sourcecode: bouncycastle version File versions

PGPEncryptedDataGenerator.java

package org.bouncycastle.openpgp;

import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.ContainedPacket;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PacketTags;
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.DigestOutputStream;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

/**
 *  Generator for encrypted objects.
 */
00031 public class PGPEncryptedDataGenerator
    implements SymmetricKeyAlgorithmTags, StreamGenerator
{
    private BCPGOutputStream     pOut;
    private CipherOutputStream   cOut;
    private Cipher               c;
    private boolean              withIntegrityPacket = false;
    private boolean              oldFormat = false;
    private DigestOutputStream   digestOut;
        
    private abstract class EncMethod
        extends ContainedPacket
    {
        protected byte[]     sessionInfo;
        protected int        encAlgorithm;
        protected Key        key;
        
        public abstract void addSessionInfo(
            byte[]    sessionInfo) 
            throws Exception;
    }
    
    private class PBEMethod
        extends EncMethod
    {
        S2K             s2k;

        PBEMethod(
            int        encAlgorithm,
            S2K        s2k,
            Key        key)
        {
            this.encAlgorithm = encAlgorithm;
            this.s2k = s2k;
            this.key = key;
        }

        public Key getKey()
        {
            return key;
        }

        public void addSessionInfo(
            byte[]    sessionInfo) 
            throws Exception
        {
            String        cName = PGPUtil.getSymmetricCipherName(encAlgorithm);
            Cipher        c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);

            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[c.getBlockSize()]), rand);
        
            this.sessionInfo = c.doFinal(sessionInfo, 0, sessionInfo.length - 2);
        }

        public void encode(BCPGOutputStream pOut) 
            throws IOException
        {
            SymmetricKeyEncSessionPacket pk = new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, sessionInfo);

            pOut.writePacket(pk);
        }
    }

    private class PubMethod
        extends EncMethod
    {
        PGPPublicKey    pubKey;
        BigInteger[]    data;
        
        PubMethod(
            PGPPublicKey        pubKey)
        {
            this.pubKey = pubKey;
        }
    
        public void addSessionInfo(
            byte[]    sessionInfo) 
            throws Exception
        {
            Cipher            c;

            switch (pubKey.getAlgorithm())
            {
            case PGPPublicKey.RSA_ENCRYPT:
            case PGPPublicKey.RSA_GENERAL:
                c = Cipher.getInstance("RSA/ECB/PKCS1Padding", defProvider);
                break;
            case PGPPublicKey.ELGAMAL_ENCRYPT:
            case PGPPublicKey.ELGAMAL_GENERAL:
                c = Cipher.getInstance("ElGamal/ECB/PKCS1Padding", defProvider);
                break;
            case PGPPublicKey.DSA:
                throw new PGPException("Can't use DSA for encryption.");
            case PGPPublicKey.ECDSA:
                throw new PGPException("Can't use ECDSA for encryption.");
            default:
                throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm());
            }

            Key key = pubKey.getKey(defProvider);
            
            c.init(Cipher.ENCRYPT_MODE, key, rand);
        
            byte[]    encKey = c.doFinal(sessionInfo);
            
            switch (pubKey.getAlgorithm())
            {
            case PGPPublicKey.RSA_ENCRYPT:
            case PGPPublicKey.RSA_GENERAL:
                data = new BigInteger[1];
                
                data[0] = new BigInteger(1, encKey);
                break;
            case PGPPublicKey.ELGAMAL_ENCRYPT:
            case PGPPublicKey.ELGAMAL_GENERAL:
                byte[]        b1 = new byte[encKey.length / 2];
                byte[]        b2 = new byte[encKey.length / 2];
                
                System.arraycopy(encKey, 0, b1, 0, b1.length);
                System.arraycopy(encKey, b1.length, b2, 0, b2.length);
                
                data = new BigInteger[2];
                data[0] = new BigInteger(1, b1);
                data[1] = new BigInteger(1, b2);
                break;
            default:
                throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm);
            }
        }
        
        public void encode(BCPGOutputStream pOut) 
            throws IOException
        {
            PublicKeyEncSessionPacket    pk = new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), data);
            
            pOut.writePacket(pk);
        }
    }
    
    private List            methods = new ArrayList();
    private int             defAlgorithm;
    private SecureRandom    rand;
    private Provider        defProvider;
    
    /**
     * Base constructor.
     *
     * @param encAlgorithm the symmetric algorithm to use.
     * @param rand source of randomness
     * @param provider the provider to use for encryption algorithms.
     */
00182     public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        SecureRandom        rand,
        String              provider)
    {
        this(encAlgorithm, rand, Security.getProvider(provider));
    }

    public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        SecureRandom        rand,
        Provider            provider)
    {
        this.defAlgorithm = encAlgorithm;
        this.rand = rand;
        this.defProvider = provider;
    }

    /**
     * Creates a cipher stream which will have an integrity packet
     * associated with it.
     * 
     * @param encAlgorithm
     * @param withIntegrityPacket
     * @param rand
     * @param provider
     */
00209     public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        boolean             withIntegrityPacket,
        SecureRandom        rand,
        String              provider)
    {
        this(encAlgorithm, withIntegrityPacket, rand, Security.getProvider(provider));
    }

    public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        boolean             withIntegrityPacket,
        SecureRandom        rand,
        Provider            provider)
    {
        this.defAlgorithm = encAlgorithm;
        this.rand = rand;
        this.defProvider = provider;
        this.withIntegrityPacket = withIntegrityPacket;
    }

    /**
     * Base constructor.
     *
     * @param encAlgorithm the symmetric algorithm to use.
     * @param rand source of randomness
     * @param oldFormat PGP 2.6.x compatability required.
     * @param provider the provider to use for encryption algorithms.
     */
00238     public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        SecureRandom        rand,
        boolean             oldFormat,
        String              provider)
    {
        this.defAlgorithm = encAlgorithm;
        this.rand = rand;
        this.defProvider = Security.getProvider(provider);
        this.oldFormat = oldFormat;
    }

    public PGPEncryptedDataGenerator(
        int                 encAlgorithm,
        SecureRandom        rand,
        boolean             oldFormat,
        Provider            provider)
    {
        this.defAlgorithm = encAlgorithm;
        this.rand = rand;
        this.defProvider = provider;
        this.oldFormat = oldFormat;
    }

    /**
     * Add a PBE encryption method to the encrypted object.
     * 
     * @param passPhrase
     * @throws NoSuchProviderException
     * @throws PGPException
     */
00269     public void addMethod(
        char[]    passPhrase) 
        throws NoSuchProviderException, PGPException
    {
        if (defProvider == null)
        {
            throw new NoSuchProviderException("unable to find provider.");
        }

        byte[]        iv = new byte[8];
        
        rand.nextBytes(iv);
        
        S2K            s2k = new S2K(HashAlgorithmTags.SHA1, iv, 0x60);
        
        methods.add(new PBEMethod(defAlgorithm, s2k, PGPUtil.makeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase, defProvider)));
    }
    
    /**
     * Add a public key encrypted session key to the encrypted object.
     * 
     * @param key
     * @throws NoSuchProviderException
     * @throws PGPException
     */
00294     public void addMethod(
        PGPPublicKey    key) 
        throws NoSuchProviderException, PGPException
    {   
        if (!key.isEncryptionKey())
        {
            throw new IllegalArgumentException("passed in key not an encryption key!");
        }

        if (defProvider == null)
        {
            throw new NoSuchProviderException("unable to find provider.");
        }

        methods.add(new PubMethod(key));
    }
    
    private void addCheckSum(
        byte[]    sessionInfo)
    {
        int    check = 0;
        
        for (int i = 1; i != sessionInfo.length - 2; i++)
        {
            check += sessionInfo[i] & 0xff;
        }
        
        sessionInfo[sessionInfo.length - 2] = (byte)(check >> 8);
        sessionInfo[sessionInfo.length - 1] = (byte)(check);
    }

    private byte[] createSessionInfo(
        int algorithm,
        Key key)
    {
        byte[] keyBytes = key.getEncoded();
        byte[] sessionInfo = new byte[keyBytes.length + 3];
        sessionInfo[0] = (byte) algorithm;
        System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length);
        addCheckSum(sessionInfo);
        return sessionInfo;
    }

    /**
     * If buffer is non null stream assumed to be partial, otherwise the
     * length will be used to output a fixed length packet.
     * <p>
     * The stream created can be closed off by either calling close()
     * on the stream or close() on the generator. Closing the returned
     * stream does not close off the OutputStream parameter out.
     * 
     * @param out
     * @param length
     * @param buffer
     * @return
     * @throws IOException
     * @throws PGPException
     * @throws IllegalStateException
     */
00353     private OutputStream open(
        OutputStream    out,
        long            length,
        byte[]          buffer)
        throws IOException, PGPException, IllegalStateException
    {
        if (cOut != null)
        {
            throw new IllegalStateException("generator already in open state");
        }

        if (methods.size() == 0)
        {
            throw new IllegalStateException("no encryption methods specified");
        }

        if (defProvider == null)
        {
            throw new IllegalStateException("provider resolves to null");
        }

        Key key = null;

        pOut = new BCPGOutputStream(out);

        if (methods.size() == 1)
        {    
            if (methods.get(0) instanceof PBEMethod)
            {
                PBEMethod m = (PBEMethod)methods.get(0);
                
                key = m.getKey();
            }
            else
            {
                key = PGPUtil.makeRandomKey(defAlgorithm, rand);
                byte[] sessionInfo = createSessionInfo(defAlgorithm, key);

                PubMethod m = (PubMethod)methods.get(0);

                try
                {
                    m.addSessionInfo(sessionInfo);
                }
                catch (Exception e)
                {
                    throw new PGPException("exception encrypting session key", e);
                }
            }
            
            pOut.writePacket((ContainedPacket)methods.get(0));
        }
        else // multiple methods
        {
            key = PGPUtil.makeRandomKey(defAlgorithm, rand);
            byte[] sessionInfo = createSessionInfo(defAlgorithm, key);

            for (int i = 0; i != methods.size(); i++)
            {
                EncMethod m = (EncMethod)methods.get(i);

                try
                {
                    m.addSessionInfo(sessionInfo);
                }
                catch (Exception e)
                {
                    throw new PGPException("exception encrypting session key", e);
                }

                pOut.writePacket(m);
            }
        }

        String cName = PGPUtil.getSymmetricCipherName(defAlgorithm);

        if (cName == null)
        {
            throw new PGPException("null cipher specified");
        }

        try
        {
            if (withIntegrityPacket)
            {
                c = Cipher.getInstance(cName + "/CFB/NoPadding", defProvider);
            }
            else
            {
                c = Cipher.getInstance(cName + "/OpenPGPCFB/NoPadding", defProvider);
            }

            byte[] iv = new byte[c.getBlockSize()];
            c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv), rand);
            
            if (buffer == null)
            {
                //
                // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected
                //
                if (withIntegrityPacket)
                {
                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + c.getBlockSize() + 2 + 1 + 22);
                    pOut.write(1);        // version number
                }
                else
                {
                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + c.getBlockSize() + 2, oldFormat);
                }
            }
            else
            {
                if (withIntegrityPacket)
                {
                    pOut = new BCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer);
                    pOut.write(1);        // version number
                }
                else
                {
                    pOut = new BCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer);
                }
            }


            OutputStream genOut = cOut = new CipherOutputStream(pOut, c);

            if (withIntegrityPacket)
            {
                String digestName = PGPUtil.getDigestName(HashAlgorithmTags.SHA1);
                MessageDigest digest = MessageDigest.getInstance(digestName, defProvider.getName());
                genOut = digestOut = new DigestOutputStream(cOut, digest);
            }

            byte[] inLineIv = new byte[c.getBlockSize() + 2];
            rand.nextBytes(inLineIv);
            inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3];
            inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4];

            genOut.write(inLineIv);

            return new WrappedGeneratorStream(genOut, this);
        }
        catch (Exception e)
        {
            throw new PGPException("Exception creating cipher", e);
        }
    }

    /**
     * Return an outputstream which will encrypt the data as it is written
     * to it.
     * <p>
     * The stream created can be closed off by either calling close()
     * on the stream or close() on the generator. Closing the returned
     * stream does not close off the OutputStream parameter out.
     * 
     * @param out
     * @param length
     * @return OutputStream
     * @throws IOException
     * @throws PGPException
     */
00515     public OutputStream open(
        OutputStream    out,
        long            length)
        throws IOException, PGPException
    {
        return this.open(out, length, null);
    }
    
    /**
     * Return an outputstream which will encrypt the data as it is written
     * to it. The stream will be written out in chunks according to the size of the
     * passed in buffer.
     * <p>
     * The stream created can be closed off by either calling close()
     * on the stream or close() on the generator. Closing the returned
     * stream does not close off the OutputStream parameter out.
     * <p>
     * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2
     * bytes worth of the buffer will be used.
     * 
     * @param out
     * @param buffer the buffer to use.
     * @return OutputStream
     * @throws IOException
     * @throws PGPException
     */
00541     public OutputStream open(
        OutputStream    out,
        byte[]          buffer)
        throws IOException, PGPException
    {
        return this.open(out, 0, buffer);
    }
    
    /**
     * Close off the encrypted object - this is equivalent to calling close on the stream
     * returned by the open() method.
     * <p>
     * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it created by the open() method.
     * @throws IOException
     */
00556     public void close()
        throws IOException
    {
        if (cOut != null)
        {    
            if (digestOut != null)
            {
                //
                // hand code a mod detection packet
                //
                BCPGOutputStream bOut = new BCPGOutputStream(digestOut, PacketTags.MOD_DETECTION_CODE, 20);

                bOut.flush();
                digestOut.flush();

                byte[] dig = digestOut.getMessageDigest().digest();

                cOut.write(dig);
            }

            cOut.flush();

            try
            {
                pOut.write(c.doFinal());
                pOut.finish();
            }
            catch (Exception e)
            {
                throw new IOException(e.toString());
            }

            cOut = null;
            pOut = null;
        }
    }
}

Generated by  Doxygen 1.6.0   Back to index