From afce16e51eb8a08d587e5816eef89f188bb52b37 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 5 Jun 2018 01:30:58 +0200 Subject: [PATCH] Add encryption API --- .../vanitasvitae/crypto/pgpainless/Main.java | 25 --- .../crypto/pgpainless/PGPainless.java | 9 + .../encryption_signing/EncryptionBuilder.java | 38 ++-- .../EncryptionBuilderInterface.java | 16 +- .../encryption_signing/EncryptionStream.java | 195 +++++++++++++++--- .../SecretKeyRingDecryptor.java | 12 ++ 6 files changed, 222 insertions(+), 73 deletions(-) delete mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java create mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/SecretKeyRingDecryptor.java diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java deleted file mode 100644 index 0a4a8901..00000000 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.vanitasvitae.crypto.pgpainless; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; - -public class Main { - - public static void main(String[] args) - throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, IOException, - InvalidAlgorithmParameterException { - Security.addProvider(new BouncyCastleProvider()); - - PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() - .simpleEcKeyRing("elliptic@cur.ve"); - - //System.out.println(Base64.getEncoder().encodeToString(secretKeys.getEncoded())); - } -} diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/PGPainless.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/PGPainless.java index e2176f91..4870f1bb 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/PGPainless.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/PGPainless.java @@ -1,8 +1,14 @@ package de.vanitasvitae.crypto.pgpainless; +import java.io.ByteArrayInputStream; +import java.io.IOException; + import de.vanitasvitae.crypto.pgpainless.decryption_verification.DecryptionBuilder; import de.vanitasvitae.crypto.pgpainless.encryption_signing.EncryptionBuilder; import de.vanitasvitae.crypto.pgpainless.key.generation.KeyRingBuilder; +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; public class PGPainless { @@ -18,4 +24,7 @@ public class PGPainless { return new DecryptionBuilder(); } + public static PGPPublicKeyRing publicKeyRingFromBytes(byte[] bytes) throws IOException { + return new PGPPublicKeyRing(new ArmoredInputStream(new ByteArrayInputStream(bytes)), new BcKeyFingerprintCalculator()); + } } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java index d072de0f..fda45e05 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java @@ -1,5 +1,6 @@ package de.vanitasvitae.crypto.pgpainless.encryption_signing; +import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Iterator; @@ -10,6 +11,8 @@ import de.vanitasvitae.crypto.pgpainless.SecretKeyNotFoundException; import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -22,9 +25,10 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { private OutputStream outputStream; private final Set encryptionKeys = new HashSet<>(); private final Set signingKeys = new HashSet<>(); - private SymmetricKeyAlgorithm symmetricKeyAlgorithm; - private HashAlgorithm hashAlgorithm; - private CompressionAlgorithm compressionAlgorithm; + private SecretKeyRingDecryptor signingKeysDecryptor; + private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128; + private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256; + private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; private boolean asciiArmor = false; @Override @@ -115,19 +119,21 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class SignWithImpl implements SignWith { @Override - public Armor signWith(PGPSecretKey key) { + public Armor signWith(PGPSecretKey key, SecretKeyRingDecryptor decryptor) { EncryptionBuilder.this.signingKeys.add(key); + EncryptionBuilder.this.signingKeysDecryptor = decryptor; return new ArmorImpl(); } @Override - public Armor signWith(Set keys) { + public Armor signWith(Set keys, SecretKeyRingDecryptor decryptor) { EncryptionBuilder.this.signingKeys.addAll(keys); + EncryptionBuilder.this.signingKeysDecryptor = decryptor; return new ArmorImpl(); } @Override - public Armor signWith(Set keyIds, Set keyRings) + public Armor signWith(Set keyIds, Set keyRings, SecretKeyRingDecryptor decryptor) throws SecretKeyNotFoundException { Set keys = new HashSet<>(); @@ -148,11 +154,11 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { keys.add(key); } - return signWith(keys); + return signWith(keys, decryptor); } @Override - public Armor signWith(Set keyIds, PGPSecretKeyRingCollection keys) + public Armor signWith(Set keyIds, PGPSecretKeyRingCollection keys, SecretKeyRingDecryptor decryptor) throws SecretKeyNotFoundException { Set rings = new HashSet<>(); @@ -160,7 +166,7 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { for (Iterator i = keys.getKeyRings(); i.hasNext();) { rings.add(i.next()); } - return signWith(keyIds, rings); + return signWith(keyIds, rings, decryptor); } @Override @@ -172,22 +178,28 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class ArmorImpl implements Armor { @Override - public OutputStream asciiArmor() { + public OutputStream asciiArmor() throws IOException, PGPException { EncryptionBuilder.this.asciiArmor = true; return build(); } @Override - public OutputStream noArmor() { + public OutputStream noArmor() throws IOException, PGPException { EncryptionBuilder.this.asciiArmor = false; return build(); } - private OutputStream build() { + private OutputStream build() throws IOException, PGPException { + + Set privateKeys = new HashSet<>(); + for (PGPSecretKey secretKey : signingKeys) { + privateKeys.add(secretKey.extractPrivateKey(signingKeysDecryptor.getDecryptor(secretKey.getKeyID()))); + } + return EncryptionStream.create( EncryptionBuilder.this.outputStream, EncryptionBuilder.this.encryptionKeys, - EncryptionBuilder.this.signingKeys, + privateKeys, EncryptionBuilder.this.symmetricKeyAlgorithm, EncryptionBuilder.this.hashAlgorithm, EncryptionBuilder.this.compressionAlgorithm, diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java index 208c022c..e59d10a9 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java @@ -1,5 +1,6 @@ package de.vanitasvitae.crypto.pgpainless.encryption_signing; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Set; @@ -9,6 +10,7 @@ import de.vanitasvitae.crypto.pgpainless.SecretKeyNotFoundException; import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -48,13 +50,15 @@ public interface EncryptionBuilderInterface { interface SignWith { - Armor signWith(PGPSecretKey key); + Armor signWith(PGPSecretKey key, SecretKeyRingDecryptor decryptor); - Armor signWith(Set keys); + Armor signWith(Set keys, SecretKeyRingDecryptor decryptor); - Armor signWith(Set keyIds, Set keyRings) throws SecretKeyNotFoundException; + Armor signWith(Set keyIds, Set keyRings, SecretKeyRingDecryptor decryptor) + throws SecretKeyNotFoundException; - Armor signWith(Set keyIds, PGPSecretKeyRingCollection keys) throws SecretKeyNotFoundException; + Armor signWith(Set keyIds, PGPSecretKeyRingCollection keys, SecretKeyRingDecryptor decryptor) + throws SecretKeyNotFoundException; Armor doNotSign(); @@ -62,9 +66,9 @@ public interface EncryptionBuilderInterface { interface Armor { - OutputStream asciiArmor(); + OutputStream asciiArmor() throws IOException, PGPException; - OutputStream noArmor(); + OutputStream noArmor() throws IOException, PGPException; } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionStream.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionStream.java index 48fc43ca..5628aae8 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionStream.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionStream.java @@ -2,57 +2,134 @@ package de.vanitasvitae.crypto.pgpainless.encryption_signing; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.Set; import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator; +/** + * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. + * @see Source + */ public class EncryptionStream extends OutputStream { - private final OutputStream outputStream; - private final Set encryptionKeys; - private final Set signingKeys; - private final SymmetricKeyAlgorithm symmetricKeyAlgorithm; - private final HashAlgorithm hashAlgorithm; - private final CompressionAlgorithm compressionAlgorithm; - private final boolean asciiArmor; + private static final int BUFFER_SIZE = 1 << 8; - private EncryptionStream(OutputStream outputStream, + private List signatureGenerators = new ArrayList<>(); + private boolean closed = false; + + // ASCII Armor + private ArmoredOutputStream armorOutputStream = null; + + // Public Key Encryption of Symmetric Session Key + private OutputStream publicKeyEncryptedStream = null; + + // Data Compression + private PGPCompressedDataGenerator compressedDataGenerator; + private BCPGOutputStream basicCompressionStream; + + // Literal Data + private PGPLiteralDataGenerator literalDataGenerator; + private OutputStream literalDataStream; + + private EncryptionStream(OutputStream targetOutputStream, Set encryptionKeys, - Set signingKeys, + Set signingKeys, SymmetricKeyAlgorithm symmetricKeyAlgorithm, HashAlgorithm hashAlgorithm, CompressionAlgorithm compressionAlgorithm, - boolean asciiArmor) { - this.outputStream = outputStream; - this.encryptionKeys = encryptionKeys; - this.signingKeys = signingKeys; - this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; - this.hashAlgorithm = hashAlgorithm; - this.compressionAlgorithm = compressionAlgorithm; - this.asciiArmor = asciiArmor; + boolean asciiArmor) + throws IOException, PGPException { + + // Currently outermost Stream + OutputStream outerMostStream; + if (asciiArmor) { + armorOutputStream = new ArmoredOutputStream(targetOutputStream); + outerMostStream = armorOutputStream; + } else { + outerMostStream = targetOutputStream; + } + + // If we want to encrypt + if (!encryptionKeys.isEmpty()) { + BcPGPDataEncryptorBuilder dataEncryptorBuilder = + new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm.getAlgorithmId()); + + dataEncryptorBuilder.setWithIntegrityPacket(true); + + PGPEncryptedDataGenerator encryptedDataGenerator = + new PGPEncryptedDataGenerator(dataEncryptorBuilder); + + for (PGPPublicKey key : encryptionKeys) { + encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(key)); + } + + publicKeyEncryptedStream = encryptedDataGenerator.open(outerMostStream, new byte[BUFFER_SIZE]); + outerMostStream = publicKeyEncryptedStream; + } + + // If we want to sign, prepare for signing + if (!signingKeys.isEmpty()) { + for (PGPPrivateKey privateKey : signingKeys) { + + BcPGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder( + privateKey.getPublicKeyPacket().getAlgorithm(), hashAlgorithm.getAlgorithmId()); + + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); + signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + signatureGenerators.add(signatureGenerator); + } + } + + // Compression + compressedDataGenerator = new PGPCompressedDataGenerator( + compressionAlgorithm.getAlgorithmId()); + basicCompressionStream = new BCPGOutputStream(compressedDataGenerator.open(outerMostStream)); + + // If we want to sign, sign! + for (PGPSignatureGenerator signatureGenerator : signatureGenerators) { + signatureGenerator.generateOnePassVersion(false).encode(basicCompressionStream); + } + + literalDataGenerator = new PGPLiteralDataGenerator(); + literalDataStream = literalDataGenerator.open(basicCompressionStream, + PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, new Date(), new byte[BUFFER_SIZE]); } - public static EncryptionStream create(OutputStream outputStream, - Set encryptionKeys, - Set signingKeys, - SymmetricKeyAlgorithm symmetricKeyAlgorithm, - HashAlgorithm hashAlgorithm, - CompressionAlgorithm compressionAlgorithm, - boolean asciiArmor) { + static EncryptionStream create(OutputStream outputStream, + Set encryptionKeys, + Set signingKeys, + SymmetricKeyAlgorithm symmetricKeyAlgorithm, + HashAlgorithm hashAlgorithm, + CompressionAlgorithm compressionAlgorithm, + boolean asciiArmor) + throws IOException, PGPException { - requireNonNull(outputStream, "outputStream"); + requireNonNull(outputStream, "targetOutputStream"); requireNonNull(encryptionKeys, "encryptionKeys"); requireNonNull(signingKeys, "signingKeys"); requireNonNull(symmetricKeyAlgorithm, "symmetricKeyAlgorithm"); requireNonNull(hashAlgorithm, "hashAlgorithm"); requireNonNull(compressionAlgorithm, "compressionAlgorithm"); - - return new EncryptionStream(outputStream, encryptionKeys, signingKeys, @@ -63,8 +140,68 @@ public class EncryptionStream extends OutputStream { } @Override - public void write(int i) throws IOException { + public void write(int data) throws IOException { + literalDataStream.write(data); + for (PGPSignatureGenerator signatureGenerator : signatureGenerators) { + byte asByte = (byte) (data & 0xff); + signatureGenerator.update(asByte); + } + } + + @Override + public void write(byte[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + + @Override + public void write(byte[] buffer, int off, int len) throws IOException { + literalDataStream.write(buffer, 0, len); + for (PGPSignatureGenerator signatureGenerator : signatureGenerators) { + signatureGenerator.update(buffer, 0, len); + } + } + + @Override + public void flush() throws IOException { + literalDataStream.flush(); + } + + @Override + public void close() throws IOException { + if (!closed) { + + // Literal Data + literalDataStream.flush(); + literalDataStream.close(); + literalDataGenerator.close(); + + // Signing + for (PGPSignatureGenerator signatureGenerator : signatureGenerators) { + try { + signatureGenerator.generate().encode(basicCompressionStream); + } catch (PGPException e) { + throw new IOException(e); + } + } + + // Compressed Data + compressedDataGenerator.close(); + + // Public Key Encryption + if (publicKeyEncryptedStream != null) { + publicKeyEncryptedStream.flush(); + publicKeyEncryptedStream.close(); + } + + // Armor + if (armorOutputStream != null) { + armorOutputStream.flush(); + armorOutputStream.close(); + } + closed = true; + } } private static void requireNonNull(Object o, String name) { diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/SecretKeyRingDecryptor.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/SecretKeyRingDecryptor.java new file mode 100644 index 00000000..5f9a4204 --- /dev/null +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/SecretKeyRingDecryptor.java @@ -0,0 +1,12 @@ +package de.vanitasvitae.crypto.pgpainless.encryption_signing; + +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; + +public interface SecretKeyRingDecryptor { + + PBESecretKeyDecryptor getDecryptor(Long keyId); + + PBESecretKeyEncryptor getEncryptor(Long keyId); + +}