diff --git a/src/main/java/org/c02e/jpgpj/Decryptor.java b/src/main/java/org/c02e/jpgpj/Decryptor.java
index 7f28fba..801355d 100644
--- a/src/main/java/org/c02e/jpgpj/Decryptor.java
+++ b/src/main/java/org/c02e/jpgpj/Decryptor.java
@@ -77,6 +77,7 @@ public class Decryptor {
public static final int DEFAULT_MAX_FILE_BUFFER_SIZE = 0x100000; // 1MB
public static final boolean DEFAULT_VERIFICATION_REQUIRED = true;
public static final int DEFAULT_COPY_FILE_BUFFER_SIZE = 0x4000;
+ public static final boolean DEFAULT_LOGGING_ENABLED = false;
protected boolean verificationRequired = DEFAULT_VERIFICATION_REQUIRED;
protected char[] symmetricPassphraseChars;
@@ -85,6 +86,7 @@ public class Decryptor {
protected String symmetricPassphrase;
protected int maxFileBufferSize = DEFAULT_MAX_FILE_BUFFER_SIZE; //1MB
protected int copyFileBufferSize = DEFAULT_COPY_FILE_BUFFER_SIZE;
+ protected boolean loggingEnabled = DEFAULT_LOGGING_ENABLED;
protected Ring ring;
protected final Logger log = LoggerFactory.getLogger(Decryptor.class.getName());
@@ -254,6 +256,32 @@ public Decryptor withRing(Ring x) {
return this;
}
+ /**
+ * @return {@code true} if logging a brief summary of the execution
+ * every time decryption is executed (e.g. file name/path, size, compression
+ * type, etc.). Note: errors/warnings logging are not affected by
+ * this setting
+ */
+ public boolean isLoggingEnabled() {
+ return loggingEnabled;
+ }
+
+ /**
+ * @param enabled {@code true} if should log a brief summary of the execution
+ * every time decryption is executed (e.g. file name/path, size, compression
+ * type, etc.). Note: errors/warnings logging are not affected by
+ * this setting
+ */
+ public void setLoggingEnabled(boolean enabled) {
+ loggingEnabled = enabled;
+ }
+
+ /** @see #setLoggingEnabled(boolean) */
+ public Decryptor withLoggingEnabled(boolean enabled) {
+ setLoggingEnabled(enabled);
+ return this;
+ }
+
/**
* Zeroes-out the cached passphrase for all keys,
* and releases the extracted private key material for garbage collection.
@@ -328,7 +356,9 @@ public FileMetadata decrypt(Path ciphertext, Path plaintext)
// delete old output file
if (Files.deleteIfExists(plaintext)) {
- log.debug("decrypt({}) deleted {}", ciphertext, plaintext);
+ if (isLoggingEnabled()) {
+ log.debug("decrypt({}) deleted {}", ciphertext, plaintext);
+ }
}
long inputSize = Files.size(ciphertext);
@@ -341,7 +371,9 @@ public FileMetadata decrypt(Path ciphertext, Path plaintext)
// delete output file if anything went wrong
try {
if (Files.deleteIfExists(plaintext)) {
- log.debug("decrypt({}) cleaned up {}", ciphertext, plaintext);
+ if (isLoggingEnabled()) {
+ log.debug("decrypt({}) cleaned up {}", ciphertext, plaintext);
+ }
}
} catch (IOException ioe) {
log.warn("decrypt({}) cannot clean up {}", ciphertext, plaintext, ioe);
@@ -458,10 +490,13 @@ protected List unpack(Iterator> packets, OutputStream plaintext)
List meta = new ArrayList();
List verifiers = new ArrayList();
+ boolean logUnpacking = isLoggingEnabled();
while (packets.hasNext()) {
Object packet = packets.next();
- log.trace("unpack {}", packet.getClass());
+ if (logUnpacking) {
+ log.trace("unpack {}", packet.getClass());
+ }
if (packet instanceof PGPMarker) {
// no-op
@@ -500,7 +535,10 @@ protected List unpack(Iterator> packets, OutputStream plaintext)
throw new PGPException("unexpected packet: " + packet.getClass());
}
}
- log.trace("unpacked all");
+
+ if (logUnpacking) {
+ log.trace("unpacked all");
+ }
// fail if verification required and any signature is bad
verify(verifiers, meta);
@@ -550,6 +588,7 @@ protected InputStream decrypt(Iterator> data)
PGPPBEEncryptedData pbe = null;
Ring decryptRing = getRing();
+ boolean logDecryption = isLoggingEnabled();
while (data.hasNext()) {
Object o = data.next();
@@ -565,11 +604,15 @@ protected InputStream decrypt(Iterator> data)
if (isUsableForDecryption(subkey))
return decrypt(pke, subkey);
- log.info("not using decryption key {}", subkey);
+ if (logDecryption) {
+ log.info("not using decryption key {}", subkey);
+ }
}
if (Util.isEmpty(keys))
- log.info("not found decryption key {}", Util.formatKeyId(id));
+ if (logDecryption) {
+ log.info("not found decryption key {}", Util.formatKeyId(id));
+ }
} else if (o instanceof PGPPBEEncryptedData) {
// try first symmetric-key option at the end
@@ -588,8 +631,9 @@ protected InputStream decrypt(PGPPublicKeyEncryptedData data, Subkey subkey)
throws IOException, PGPException {
if (data == null || subkey == null)
throw new DecryptionException("no suitable decryption key found");
-
- log.info("using decryption key {}", subkey);
+ if (isLoggingEnabled()) {
+ log.info("using decryption key {}", subkey);
+ }
return data.getDataStream(buildPublicKeyDecryptor(subkey));
}
@@ -658,8 +702,9 @@ protected void verify(List verifiers, List meta)
if (!verifier.verify())
throw new VerificationException(
"bad signature for key " + verifier.key);
- else
+ if (isLoggingEnabled()) {
log.debug("good signature for key {}", verifier.key);
+ }
Key key = verifier.getSignedBy();
for (FileMetadata file : meta) {
@@ -818,24 +863,31 @@ public Key getSignedBy() throws PGPException {
* Finds verification subkey by ID in this Decryptor's ring, or null.
* If found, also sets "key" field to subkey's key.
*/
- private Subkey findVerificationSubkey(Long id) {
+ protected Subkey findVerificationSubkey(Long id) {
Ring decryptorRing = getRing();
List keys = decryptorRing.findAll(id);
-
+ boolean logVerification = isLoggingEnabled();
for (Key key: keys) {
Subkey subkey = key.findById(id);
if (subkey != null && subkey.isForVerification()) {
- log.info("using verification key {}", subkey);
+ if (logVerification) {
+ log.info("using verification key {} for ID={}", subkey, Util.formatKeyId(id));
+ }
this.key = key;
return subkey;
}
- log.info("not using verification key {}", subkey);
+ if (logVerification) {
+ log.info("not using verification key {} for ID={}", subkey, Util.formatKeyId(id));
+ }
}
- if (Util.isEmpty(keys))
- log.info("not found verification key {}", Util.formatKeyId(id));
+ if (Util.isEmpty(keys)) {
+ if (logVerification) {
+ log.info("not found verification key {}", Util.formatKeyId(id));
+ }
+ }
return null;
}
diff --git a/src/main/java/org/c02e/jpgpj/Encryptor.java b/src/main/java/org/c02e/jpgpj/Encryptor.java
index caa7ff3..741b330 100644
--- a/src/main/java/org/c02e/jpgpj/Encryptor.java
+++ b/src/main/java/org/c02e/jpgpj/Encryptor.java
@@ -2,8 +2,8 @@
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -90,6 +90,7 @@ public class Encryptor {
public static final int DEFAULT_KEY_DERIVATION_ALGORITHM_WORK_FACTOR = 255;
public static final int DEFAULT_MAX_FILE_BUFFER_SIZE = 0x100000; // 1MB
+ public static final boolean DEFAULT_LOGGING_ENABLED = false;
protected boolean asciiArmored = DEFAULT_ASCII_ARMORED;
protected boolean removeDefaultArmoredVersionHeader = DEFAULT_REMOVE_DEFAULT_ARMORED_VERSION_HEADER;
@@ -109,6 +110,7 @@ public class Encryptor {
protected int keyDerivationWorkFactor = DEFAULT_KEY_DERIVATION_ALGORITHM_WORK_FACTOR;
protected int maxFileBufferSize = DEFAULT_MAX_FILE_BUFFER_SIZE;
+ protected boolean loggingEnabled = DEFAULT_LOGGING_ENABLED;
protected Ring ring;
protected final Logger log = LoggerFactory.getLogger(Encryptor.class.getName());
@@ -568,6 +570,32 @@ public Encryptor withRing(Ring x) {
return this;
}
+ /**
+ * @return {@code true} if logging a brief summary of the execution
+ * every time encryption is executed (e.g. file name/path, size, compression
+ * type, etc.). Note: errors/warnings logging are not affected by
+ * this setting
+ */
+ public boolean isLoggingEnabled() {
+ return loggingEnabled;
+ }
+
+ /**
+ * @param enabled {@code true} if should log a brief summary of the execution
+ * every time encryption is executed (e.g. file name/path, size, compression
+ * type, etc.). Note: errors/warnings logging are not affected by
+ * this setting
+ */
+ public void setLoggingEnabled(boolean enabled) {
+ loggingEnabled = enabled;
+ }
+
+ /** @see #setLoggingEnabled(boolean) */
+ public Encryptor withLoggingEnabled(boolean enabled) {
+ setLoggingEnabled(enabled);
+ return this;
+ }
+
/**
* Zeroes-out the cached passphrase for all keys,
* and releases the extracted private key material for garbage collection.
@@ -636,7 +664,9 @@ public FileMetadata encrypt(Path plaintext, Path ciphertext)
// delete old output file
if (Files.deleteIfExists(ciphertext)) {
- log.debug("encrypt({}) deleted {}", plaintext, ciphertext);
+ if (isLoggingEnabled()) {
+ log.debug("encrypt({}) deleted {}", plaintext, ciphertext);
+ }
}
FileMetadata meta = new FileMetadata(plaintext);
@@ -650,7 +680,9 @@ public FileMetadata encrypt(Path plaintext, Path ciphertext)
// delete output file if anything went wrong
try {
if (Files.deleteIfExists(ciphertext)) {
- log.debug("encrypt({}) cleaned up {}", plaintext, ciphertext);
+ if (isLoggingEnabled()) {
+ log.debug("encrypt({}) cleaned up {}", plaintext, ciphertext);
+ }
}
} catch(IOException ioe) {
log.warn("encrypt({}) cannot clean up {}", plaintext, ciphertext, ioe);
@@ -676,7 +708,7 @@ public InputStream wrapSourceInputStream(InputStream sourceStream, long inputSiz
* @param targetStream Original target (ciphertext) {@link OutputStream}
* @param inputSize Expected input (plaintext) size
* @return A wrapper buffered stream optimized for the input size according to
- * the current encryptor settings
+ * the current encryptor settings.
* @throws IOException If failed to generate the wrapper
* @see #estimateOutFileBufferSize(long)
*/
@@ -685,6 +717,90 @@ public OutputStream wrapTargetOutputStream(OutputStream targetStream, long input
return new BufferedOutputStream(targetStream, bestFileBufferSize);
}
+ /**
+ * @param data Data buffer to be used as plaintext input
+ * @param name The "file" name to report as being encrypted - can be {@code null}
+ * @param ciphertext Target ciphertext {@link File}
+ * @return The {@link FileMetadata} of the encrypted plaintext
+ * @throws IOException if an IO error occurs reading from or writing to
+ * the underlying input or output streams.
+ * @throws PGPException if no encryption keys and no passphrase for
+ * symmetric encryption were supplied (and the message is not unencrypted),
+ * or if no signing keys were supplied (and the message is not unsigned).
+ * @throws PassphraseException if an incorrect passphrase was supplied
+ * for one of the signing keys.
+ */
+ public FileMetadata encryptBytes(byte[] data, String name, File ciphertext)
+ throws IOException, PGPException {
+ return encryptBytes(data, name, ciphertext.toPath());
+ }
+
+ /**
+ * @param data Data buffer to be used as plaintext input
+ * @param name The "file" name to report as being encrypted - can be {@code null}
+ * @param ciphertext Target ciphertext {@link Path}
+ * @return The {@link FileMetadata} of the encrypted plaintext
+ * @throws IOException if an IO error occurs reading from or writing to
+ * the underlying input or output streams.
+ * @throws PGPException if no encryption keys and no passphrase for
+ * symmetric encryption were supplied (and the message is not unencrypted),
+ * or if no signing keys were supplied (and the message is not unsigned).
+ * @throws PassphraseException if an incorrect passphrase was supplied
+ * for one of the signing keys.
+ */
+ public FileMetadata encryptBytes(byte[] data, String name, Path ciphertext)
+ throws IOException, PGPException {
+ // delete old output file
+ if (Files.deleteIfExists(ciphertext)) {
+ if (isLoggingEnabled()) {
+ log.debug("encryptBytes({}) deleted {}", name, ciphertext);
+ }
+ }
+
+ try (OutputStream targetStream = Files.newOutputStream(ciphertext);
+ OutputStream output = wrapTargetOutputStream(targetStream, data.length)) {
+ return encryptBytes(data, name, output);
+ } catch (Exception e) {
+ // delete output file if anything went wrong
+ try {
+ if (Files.deleteIfExists(ciphertext)) {
+ if (isLoggingEnabled()) {
+ log.debug("encryptBytes({}) cleaned up {}", name, ciphertext);
+ }
+ }
+ } catch(IOException ioe) {
+ log.warn("encryptBytes({}) cannot clean up {}", name, ciphertext, ioe);
+ }
+
+ throw e;
+ }
+ }
+
+ /**
+ * @param data Data buffer to be used as plaintext input
+ * @param name The "file" name to report as being encrypted - can be {@code null}
+ * @param ciphertext Target ciphertext {@link OutputStream}
+ * @return The {@link FileMetadata} of the encrypted plaintext
+ * @throws IOException if an IO error occurs reading from or writing to
+ * the underlying input or output streams.
+ * @throws PGPException if no encryption keys and no passphrase for
+ * symmetric encryption were supplied (and the message is not unencrypted),
+ * or if no signing keys were supplied (and the message is not unsigned).
+ * @throws PassphraseException if an incorrect passphrase was supplied
+ * for one of the signing keys.
+ */
+ public FileMetadata encryptBytes(byte[] data, String name, OutputStream ciphertext)
+ throws IOException, PGPException {
+ FileMetadata meta = new FileMetadata();
+ meta.setName(name);
+ meta.setLength(data.length);
+ meta.setLastModified(System.currentTimeMillis());
+
+ try (InputStream input = new ByteArrayInputStream(data)) {
+ return encrypt(input, ciphertext, meta);
+ }
+ }
+
/**
* Signs, compresses, and encrypts the specified content as a PGP message
* into the specified output stream (with no optional metadata).
@@ -766,21 +882,52 @@ public FileMetadata encrypt(
*/
public OutputStream prepareCiphertextOutputStream(FileMetadata plainMeta, File ciphertext)
throws IOException, PGPException {
+ return prepareCiphertextOutputStream(plainMeta, ciphertext.toPath());
+ }
+
+ /**
+ * Builds a wrapper {@link OutputStream} where everything written to the it is
+ * encrypted+compressed+signed according to the encryptor's configuration,
+ * and then written to the specified target file. Closing the wrapper stream finalizes
+ * the encryption and signature, and finishes writing all the wrapper stream's
+ * content to the original stream as well as closing the file stream.
+ *
+ * @param plainMeta The {@link FileMetadata} describing the plaintext file - if
+ * {@code null} an empty ad-hoc instance will be created
+ * @param ciphertext The target {@link Path} for the encrypted data
+ * @return The wrapper stream
+ * @throws IOException If failed to wrap the stream
+ * @throws PGPException If failed to apply a PGP wrapper
+ */
+ public OutputStream prepareCiphertextOutputStream(FileMetadata plainMeta, Path ciphertext)
+ throws IOException, PGPException {
// delete old output file
- if (ciphertext.delete()) {
- log.debug("prepareCiphertextOutputStream - deleted {}", ciphertext);
+ if (Files.deleteIfExists(ciphertext)) {
+ if (isLoggingEnabled()) {
+ log.debug("prepareCiphertextOutputStream({}) - deleted {}",
+ (plainMeta == null) ? null : plainMeta.getName(), ciphertext);
+ }
}
OutputStream fileStream = null;
try {
- fileStream = new FileOutputStream(ciphertext);
+ fileStream = Files.newOutputStream(ciphertext);
OutputStream wrapper = prepareCiphertextOutputStream(fileStream, plainMeta, true);
fileStream = null; // avoid auto-close at finally clause
return wrapper;
} catch(Exception e) {
// delete output file if anything went wrong
if (fileStream != null) {
- ciphertext.delete();
+ String fileName = (plainMeta == null) ? null : plainMeta.getName();
+ try {
+ if (!Files.deleteIfExists(ciphertext)) {
+ if (isLoggingEnabled()) {
+ log.debug("prepareCiphertextOutputStream({}) - cleaned up output file {}", fileName, ciphertext);
+ }
+ }
+ } catch (IOException ioe) {
+ log.warn(fileName + ": Failed to clean up output file " + ciphertext, ioe);
+ }
}
throw e;
} finally {
@@ -959,7 +1106,10 @@ protected OutputStream armor(OutputStream out, FileMetadata meta) {
protected OutputStream encrypt(OutputStream out, FileMetadata meta)
throws IOException, PGPException {
EncryptionAlgorithm encAlgo = getEncryptionAlgorithm();
- log.trace("using encryption algorithm {}", encAlgo);
+ if (isLoggingEnabled()) {
+ log.trace("{}: using encryption algorithm {}",
+ (meta == null) ? null : meta.getName(), encAlgo);
+ }
if (encAlgo == EncryptionAlgorithm.Unencrypted)
return null;
@@ -972,10 +1122,10 @@ protected OutputStream encrypt(OutputStream out, FileMetadata meta)
PGPEncryptedDataGenerator generator = buildEncryptor();
for (Key key : keys)
- generator.addMethod(buildPublicKeyEncryptor(key));
+ generator.addMethod(buildPublicKeyEncryptor(key, meta));
if (!Util.isEmpty(passChars))
- generator.addMethod(buildSymmetricKeyEncryptor());
+ generator.addMethod(buildSymmetricKeyEncryptor(meta));
return generator.open(out, getEncryptionBuffer(meta));
}
@@ -987,7 +1137,10 @@ protected OutputStream compress(OutputStream out, FileMetadata meta)
throws IOException, PGPException {
CompressionAlgorithm compAlgo = getCompressionAlgorithm();
int compLevel = getCompressionLevel();
- log.trace("using compression algorithm {} - {}", compAlgo, compLevel);
+ if (isLoggingEnabled()) {
+ log.trace("{}: using compression algorithm {} - {}",
+ (meta == null) ? null : meta.getName(), compAlgo, compLevel);
+ }
if (compAlgo == CompressionAlgorithm.Uncompressed ||
compLevel < 1 || compLevel > 9)
@@ -1016,8 +1169,11 @@ protected OutputStream packet(OutputStream out, FileMetadata meta)
*/
protected SigningOutputStream sign(OutputStream out, FileMetadata meta)
throws IOException, PGPException {
+ String fileName = (meta == null) ? null : meta.getName();
HashingAlgorithm sigAlg = getSigningAlgorithm();
- log.trace("using signing algorithm {}", sigAlg);
+ if (isLoggingEnabled()) {
+ log.trace("{}: using signing algorithm {}", fileName, sigAlg);
+ }
if (sigAlg == HashingAlgorithm.Unsigned)
return null;
@@ -1029,7 +1185,9 @@ protected SigningOutputStream sign(OutputStream out, FileMetadata meta)
Key key = signers.get(i);
Subkey subkey = key.getSigning();
if (!isUsableForSigning(subkey)) {
- log.info("not using signing key {}", subkey);
+ if (isLoggingEnabled()) {
+ log.debug("{}: not using signing key {}", fileName, subkey);
+ }
signers.remove(i);
}
}
@@ -1083,8 +1241,11 @@ protected PGPEncryptedDataGenerator buildEncryptor() {
* Builds a PublicKeyKeyEncryptionMethodGenerator
* for the specified key.
*/
- protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key) {
- log.info("using encryption key {}", key.getEncryption());
+ protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key, FileMetadata meta) {
+ if (isLoggingEnabled()) {
+ log.info("{}: using encryption key {}",
+ (meta == null) ? null : meta.getName(), key.getEncryption());
+ }
PGPPublicKey publicKey = key.getEncryption().getPublicKey();
return new BcPublicKeyKeyEncryptionMethodGenerator(publicKey);
@@ -1092,14 +1253,16 @@ protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key)
/**
* Builds a PublicKeyKeyEncryptionMethodGenerator
- * for the specified key.
+ * for the specified key to encrypt the file.
*/
- protected PBEKeyEncryptionMethodGenerator buildSymmetricKeyEncryptor()
+ protected PBEKeyEncryptionMethodGenerator buildSymmetricKeyEncryptor(FileMetadata meta)
throws PGPException {
HashingAlgorithm kdAlgorithm = getKeyDeriviationAlgorithm();
int workFactor = getKeyDeriviationWorkFactor();
- log.info("using symmetric encryption with {} hash, work factor {}",
- kdAlgorithm, workFactor);
+ if (isLoggingEnabled()) {
+ log.info("{}: using symmetric encryption with {} hash, work factor {}",
+ (meta == null) ? null : meta.getName(), kdAlgorithm, workFactor);
+ }
return new BcPBEKeyEncryptionMethodGenerator(
getSymmetricPassphraseChars(),
@@ -1117,9 +1280,11 @@ protected boolean isUsableForSigning(Subkey subkey) {
*/
protected PGPSignatureGenerator buildSigner(Key key, FileMetadata meta)
throws PGPException {
+ String fileName = (meta == null) ? null : meta.getName();
Subkey subkey = key.getSigning();
-
- log.info("using signing key {}", subkey);
+ if (isLoggingEnabled()) {
+ log.info("{}: using signing key {}", fileName, key);
+ }
PGPContentSignerBuilder builder = buildSignerBuilder(
subkey.getPublicKey().getAlgorithm(),
@@ -1131,7 +1296,9 @@ protected PGPSignatureGenerator buildSigner(Key key, FileMetadata meta)
String uid = key.getSigningUid();
if (!Util.isEmpty(uid)) {
- log.debug("using signing uid {}", uid);
+ if (isLoggingEnabled()) {
+ log.debug("{}: using signing uid {}", fileName, uid);
+ }
PGPSignatureSubpacketGenerator signer =
new PGPSignatureSubpacketGenerator();
diff --git a/src/main/java/org/c02e/jpgpj/FileMetadata.java b/src/main/java/org/c02e/jpgpj/FileMetadata.java
index 62152ec..991d9f3 100644
--- a/src/main/java/org/c02e/jpgpj/FileMetadata.java
+++ b/src/main/java/org/c02e/jpgpj/FileMetadata.java
@@ -8,6 +8,7 @@
import java.nio.file.attribute.FileTime;
import java.util.Date;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPSignature;
@@ -27,7 +28,7 @@ public enum Format {
/** UTF-8 encoded text with CRLF line-endings. */
UTF8('u');
- protected char code;
+ private final char code;
Format(char code) {
this.code = code;
@@ -50,11 +51,11 @@ public static Format byCode(char code) {
public static final String DEFAULT_NAME = "";
public static final Format DEFAULT_FORMAT = Format.BINARY;
- protected String name;
- protected Format format;
- protected long length;
- protected long lastModified;
- protected Ring verified = new Ring();
+ private String name;
+ private Format format;
+ private long length;
+ private long lastModified;
+ private final Ring verified = new Ring();
/** Constructs a metadata object with default values. */
public FileMetadata() {
@@ -244,6 +245,34 @@ public int getSignatureType() {
PGPSignature.CANONICAL_TEXT_DOCUMENT : PGPSignature.BINARY_DOCUMENT;
}
+ @Override
+ public int hashCode() {
+ return Objects.hash(getName(), getFormat())
+ + 31 * Long.hashCode(getLength())
+ + 37 * Long.hashCode(TimeUnit.MILLISECONDS.toSeconds(getLastModified()))
+ ;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o == this) {
+ return true;
+ }
+ if (getClass() != o.getClass()) {
+ return false;
+ }
+
+ FileMetadata that = (FileMetadata) o;
+ return Objects.equals(getName(), that.getName())
+ && Objects.equals(getFormat(), that.getFormat())
+ && (getLength() == that.getLength())
+ && (TimeUnit.MILLISECONDS.toSeconds(getLastModified()) == TimeUnit.MILLISECONDS.toSeconds(that.getLastModified()))
+ ;
+ }
+
@Override
public String toString() {
return getClass().getSimpleName()
diff --git a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy
index 79b9c8d..9f370b6 100644
--- a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy
+++ b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy
@@ -236,6 +236,31 @@ class EncryptorSpec extends Specification {
meta.verified.keys.signingUid == ['Test Key 1 ']
}
+ def "encrypt bytes"() {
+ when:
+ def expected = "This is a test of bytes encoding"
+ def encryptor = new Encryptor(new Ring(stream('test-key-1.asc')))
+ encryptor.ring.keys*.passphrase = 'c02e'
+ def encMeta = encryptor.encryptBytes expected.getBytes(), "bytesTest", cipherOut
+
+ def decryptor = new Decryptor(new Ring(stream('test-key-1.asc')))
+ decryptor.ring.keys*.passphrase = 'c02e'
+ def decMeta = decryptor.decrypt(cipherIn, plainOut)
+
+ then:
+ plainOut.toString() == expected
+
+ decMeta.name == "bytesTest"
+ decMeta.length == expected.length()
+ decMeta.format == FileMetadata.Format.BINARY
+
+ decMeta == encMeta
+
+ decMeta.verified
+ decMeta.verified.keys.uids == [['Test Key 1 ']]
+ decMeta.verified.keys.signingUid == ['Test Key 1 ']
+ }
+
def "encrypt and sign with ascii armor"() {
when:
def encryptor = new Encryptor(new Ring(stream('test-key-1.asc')))
@@ -381,6 +406,7 @@ hQEMAyne546XDHBhAQ...
meta.verified
}
+
def "encrypt and sign zero-byte file"() {
when:
def encryptor = new Encryptor(new Key(file('test-key-1.asc'), 'c02e'))
@@ -913,8 +939,12 @@ hQEMAyne546XDHBhAQ...
'test\n'
}
+ protected getPlainBytes() {
+ plainText.bytes
+ }
+
protected getPlainIn() {
- new ByteArrayInputStream(plainText.bytes)
+ new ByteArrayInputStream(plainBytes)
}
protected getCipherIn() {