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}