001/*
002 * Copyright 2018 Paul Schaub.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.pgpainless.symmetric_encryption;
017
018import javax.annotation.Nonnull;
019import java.io.BufferedInputStream;
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.security.SecureRandom;
026import java.util.Date;
027
028import org.bouncycastle.openpgp.PGPCompressedData;
029import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
030import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
031import org.bouncycastle.openpgp.PGPEncryptedDataList;
032import org.bouncycastle.openpgp.PGPException;
033import org.bouncycastle.openpgp.PGPLiteralData;
034import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
035import org.bouncycastle.openpgp.PGPPBEEncryptedData;
036import org.bouncycastle.openpgp.PGPUtil;
037import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
038import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
039import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
040import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
041import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
042import org.bouncycastle.util.io.Streams;
043import org.pgpainless.algorithm.CompressionAlgorithm;
044import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
045import org.pgpainless.util.Passphrase;
046
047/**
048 * Stolen from <a href="https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java">
049 *     Bouncycastle examples</a>.
050 */
051public class SymmetricEncryptorDecryptor {
052
053    /**
054     * Encrypt some {@code data} symmetrically using an {@code encryptionAlgorithm} and a given {@code password}.
055     * The input data will be compressed using the given {@code compressionAlgorithm} and packed in a modification
056     * detection package, which is then encrypted.
057     *
058     * @param data bytes that will be encrypted
059     * @param password password that will be used to encrypt the data
060     * @param encryptionAlgorithm symmetric algorithm that will be used to encrypt the data
061     * @param compressionAlgorithm compression algorithm that will be used to compress the data
062     * @return encrypted data
063     * @throws IOException IO is dangerous
064     * @throws PGPException OpenPGP is brittle
065     */
066    public static byte[] symmetricallyEncrypt(@Nonnull byte[] data,
067                                              @Nonnull Passphrase password,
068                                              @Nonnull SymmetricKeyAlgorithm encryptionAlgorithm,
069                                              @Nonnull CompressionAlgorithm compressionAlgorithm)
070            throws IOException, PGPException {
071
072        byte[] compressedData = compress(data, compressionAlgorithm.getAlgorithmId());
073
074        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
075
076        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
077                new JcePGPDataEncryptorBuilder(encryptionAlgorithm.getAlgorithmId())
078                        .setWithIntegrityPacket(true)
079                        .setSecureRandom(new SecureRandom())
080                        .setProvider("BC"));
081
082        encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(password.getChars()).setProvider("BC"));
083
084        OutputStream encOut = encGen.open(bOut, compressedData.length);
085
086        encOut.write(compressedData);
087        encOut.close();
088
089        return bOut.toByteArray();
090    }
091
092    /**
093     * Decrypt and decompress some symmetrically encrypted data using a password.
094     * Note, that decryption will fail if the given data is not integrity protected with a modification detection
095     * package.
096     *
097     * @param data encrypted data
098     * @param password password to decrypt the data
099     * @return decrypted data
100     * @throws IOException IO is dangerous
101     * @throws PGPException OpenPGP is brittle
102     */
103    public static byte[] symmetricallyDecrypt(@Nonnull byte[] data, @Nonnull Passphrase password)
104            throws IOException, PGPException {
105        InputStream in = new BufferedInputStream(new ByteArrayInputStream(data));
106        in = PGPUtil.getDecoderStream(in);
107
108        BcPGPObjectFactory pgpF = new BcPGPObjectFactory(in);
109        PGPEncryptedDataList enc;
110        Object o = pgpF.nextObject();
111
112        if (o instanceof PGPEncryptedDataList) {
113            enc = (PGPEncryptedDataList) o;
114        } else {
115            enc = (PGPEncryptedDataList) pgpF.nextObject();
116        }
117
118        PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) enc.get(0);
119
120        InputStream clear = pbe.getDataStream(new BcPBEDataDecryptorFactory(
121                password.getChars(), new BcPGPDigestCalculatorProvider()));
122
123
124        BcPGPObjectFactory pgpFact = new BcPGPObjectFactory(clear);
125
126        o = pgpFact.nextObject();
127        if (o instanceof PGPCompressedData) {
128            PGPCompressedData   cData = (PGPCompressedData) o;
129            pgpFact = new BcPGPObjectFactory(cData.getDataStream());
130            o = pgpFact.nextObject();
131        }
132
133        PGPLiteralData ld = (PGPLiteralData) o;
134        InputStream unc = ld.getInputStream();
135
136        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
137
138        Streams.pipeAll(unc, outputStream);
139
140        outputStream.close();
141
142        if (pbe.isIntegrityProtected()) {
143            if (!pbe.verify()) {
144                throw new PGPException("Integrity check failed.");
145            }
146        } else {
147            throw new PGPException("Symmetrically encrypted data is not integrity protected.");
148        }
149
150        return outputStream.toByteArray();
151    }
152
153    /**
154     * Wrap some data in an OpenPGP compressed data package.
155     *
156     * @param clearData uncompressed data
157     * @param algorithm compression algorithm
158     * @return compressed data
159     * @throws IOException IO is dangerous
160     */
161    private static byte[] compress(@Nonnull byte[] clearData, int algorithm) throws IOException {
162        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
163        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm);
164        OutputStream cos = comData.open(bOut);
165
166        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
167
168        OutputStream  pOut = lData.open(cos,
169                PGPLiteralData.BINARY,
170                PGPLiteralDataGenerator.CONSOLE,
171                clearData.length,
172                new Date()
173        );
174
175        pOut.write(clearData);
176        pOut.close();
177
178        comData.close();
179
180        return bOut.toByteArray();
181    }
182
183}