-
Notifications
You must be signed in to change notification settings - Fork 20
DecryptingFiles
To decrypt a message, create a new Decryptor
object. Add to its ring the secret key of each expected message recipient, and the public key of each expected message signer. Then run the decrypt()
method of the Decryptor
-- this will attempt to decrypt the message using one of the secret keys on the ring flagged as forDecryption
(and that has its passphrase supplied), and verify the message using one of the keys on the ring flagged as forVerification
. See KeyRings for examples of how to load keys and manipulate their usage flags.
Note that you can reuse Decryptor
objects (including sharing the same Decryptor
among multiple threads) -- just be careful with MultiThreading.
The following examples will all use a Decryptor
configured with Alice's secret key and Bob's public key (so as to decrypt messages for Alice, and verify that they have been signed by either Alice or Bob):
Decryptor decryptor = new Decryptor(
new Key(new File("/path/to/alice-sec.gpg"), "password123"),
new Key(new File("/path/to/bob-pub.gpg"))
);
Note that you need to first supply the passphrase for a secret key before using it to sign or decrypt a message. In the above example, the passphrase password123
was supplied to the Key
constructor for Alice's key (which will be used as the passphrase for all of that key's subkeys).
If you attempt to decrypt a message that was not encrypted for a secret key on the Decryptor
object's ring (or is on the ring, but without a passphrase supplied or not flagged as forDecryption
), a DecryptionException
will be raised with an exception message "no suitable decryption key found".
You can decrypt a file by supplying the existing "ciphertext" (encrypted) version of the file as one java.io.File
object, and the desired location of the "plaintext" (decrypted) version of the file as a second java.io.File
object. If the second file already exists, it will be overwritten (and you'll get an IOException
if you try to use the same location for both the plaintext and ciphertext files). For example, this decrypts "path/to/ciphertext.txt.gpg" to "path/to/plaintext.txt":
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
This is equivalent to the following GnuPG command:
gpg --decrypt --output path/to/plaintext.txt path/to/ciphertext.txt.gpg
The file extensions aren't important -- you could simply name the plaintext file "path/to/foo" and ciphertext "path/to/bar". Typically an extension of .gpg
(like foo.txt.gpg
) is used to indicate a file decrypted as a PGP message, and an extension of .asc
(like foo.txt.asc
) to indicate a file decrypted as a PGP message and encoded with ASCII Armor (although sometimes these extensions are used interchangeably, or other file-naming conventions are used).
ASCII Armor Base64-encodes a PGP message so that it's safe to copy and paste as text into email messages or other files or applications. Despite its name, it doesn't provide any additional security properties (its "armor" protects PGP messages from inadvertent corruption when embedded in other files, rather than "armoring" against decryption or deliberate modification by third parties). ASCII Armor also conveniently declares what kind of data is encoded within its armor via a header and footer like this (making it easier to figure out what you've been given when someone gives you a .gpg
or .asc
file):
-----BEGIN PGP MESSAGE-----
Version: GnuPG v1
jA0EAwMCRPdXu3qZeLBgySHwRvh2vWI8YHXCNDwHDzkMr6ZoR9iZFDM8gaWyIz1T
x/o=
=AqCM
-----END PGP MESSAGE-----
JPGPJ reads armored key files and encrypted messages transparently, so you don't need to do anything special to decrypt a PGP message encoded with ASCII Armor.
You can decrypt directly from a java.io.InputStream
to a java.io.OutputStream
. The Decryptor
does not call close()
on the InputStream
nor flush()
or close()
on the OutputStream
(so you should, if appropriate).
InputStream input = new URL("http://example.com/ciphertext.txt.gpg").openStream();
URLConnection post = new URL(
"http://example.com/plaintext.txt").openConnection();
post.setDoOutput(true);
InputStream output = post.getOutputStream();
decryptor.decrypt(input, output);
input.close();
output.close();
This is equivalent to the following GnuPG command:
curl http://example.com/ciphertext.txt.gpg |
gpg --decrypt |
curl --data-binary @- http://example.com/plaintext.txt
PGP allows some optional metadata to be supplied with a message, such as the original file's name, last-modified date, and line-ending normalization. You can safely ignore this metadata, but it may be useful for certain use cases -- if you trust the message signer to have supplied legitimate values for the metadata (and even if you do trust the signer, it's still a good idea to validate the metadata contains reasonable values, like the message's modified date isn't from the future, or that the filename doesn't include any slashes, etc).
The following example uses the metadata extracted from the decrypted message to set the decrypted file's last-modified date and name:
File plaintext = new File("path/to/plaintext.txt");
FileMetadata metadata = decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
plaintext
);
if (metadata.getLastModified() > 0 &&
metadata.getLastModified() < System.currentTimeMilis())
plaintext.setLastModified(metadata.getLastModified());
if (metadata.getName().length() > 0 &&
Pattern.matches("[\\w\\.\\-]+", metadata.getName())) {
File renamed = new File("path/to", metdata.getName());
if (!renamed.exists())
plaintext.renameTo(renamed);
}
When decrypting a message, a Decryptor
will also attempt to verify that the message has been signed by at least one of the keys on its ring flagged as forVerification
. By default, all keys on a Decryptor
object's ring will be flagged as forVerification
.
To avoid verifying a message entirely, set the Decryptor
object's verificationRequired
property to false
:
decryptor.setVerificationRequired(false);
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
This is equivalent to the following GnuPG command:
gpg --decrypt --skip-verify \
--output path/to/plaintext.txt path/to/ciphertext.txt.gpg
If you do not explicitly set the Decryptor
object's verificationRequired
property to false
and the message was not signed, or was not signed with a key on the Decryptor
object's ring flagged as forVerification
, a VerificationException
will be raised with an exception message "content not signed with a required key".
To limit only a subset of the keys on a ring to be used for verification, you must explicitly turn off the forVerification
flags of the keys for which you don't want to use for verification. A convenient way to do this is to load keys that you only want to decrypt with via the KeyForDecryption
class -- this will ensure that the loaded key is used only for decryption, and not for verification:
Decryptor decryptor = new Decryptor(
new KeyForDecryption(new File("/path/to/alice-sec.gpg"), "password123"),
new KeyForVerification(new File("/path/to/bob-pub.gpg"))
).decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
Alternately, you can loop through all the subkeys on a ring and manipulate their forVerification
flag individually:
Decryptor decryptor = new Decryptor(
new Key(new File("/path/to/alice-sec.gpg"), "password123"),
new Key(new File("/path/to/bob-pub.gpg"))
);
for (Key key: decryptor.getRing().findAll("alice"))
for (Subkey subkey: key.getSubkeys())
subkey.setForVerification(false);
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
In the above example, we load Alice's secret key and Bob's public key, and then turn off the forVerification
flag on Alice's subkeys -- so the Decryptor
will attempt to decrypt files with Alice's key, and verify that they were signed by Bob. In GnuPG, you might accomplish something similar by limiting the keys in your keystore to only Alice's and Bob's (or specifying a custom keyring with --no-default-keyring --keyring path/to/keyring.gpg
), and marking Alice's key as never trusted.
See KeyRings#setting-usage-flags for further details on subkey usage flags.
To decrypt messages with a key that does not have usage flags set (or has usage flags set differently than are actually used), you can use the KeyForDecryption
subclass of the Key
class to load the key and automatically flag all of its subkeys for (and only for) decryption. You can also use the KeyForVerification
subclass of the Key
class to load a key and automatically flag all of its subkeys for (and only for) verification. The following example will always only use Alice's secret key for decryption, and Bob's key for verification, regardless of the keys' embedded usage flags:
Decryptor decryptor = new Decryptor(
new KeyForDecryption(new File("/path/to/alice-sec.gpg"), "password123"),
new KeyForVerification(new File("/path/to/bob-sec.gpg"))
).decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
JPGPJ can handle messages compressed with any of the compression options specified in RFC 4880:
- Uncompressed
- ZIP
- ZLIB
- BZip2
If a message has been encrypted with a passphrase (in place of, or in addition to, a key), you can decrypt it using the same passphrase by setting the symmetricPassphrase
property of a Decryptor
object. For example, if you were to set up a Decryptor
without any keys (and turned verification off, since you didn't supply any keys for verification), but provide it with a symmetricPassphrase
, the Decryptor
will attempt to use the passphrase to decrypt the message:
Decryptor decryptor = new Decryptor();
decryptor.setVerificationRequired(false);
decryptor.setSymmetricPassphrase("the eagle flies at midnight");
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
If you were to set up the Decryptor
with Alice's secret key and Bob's public key (like most of the examples on this page), and also supply the Decryptor
with a symmetricPassphrase
, it will attempt to decrypt the message with Alice's key if the message was encrypted for Alice's key; but if the message was not encrypted with Alice's key, it will attempt to decrypt the message with the provided passphrase (and then verify the message's signature with either Alice's key or Bob's key):
Decryptor decryptor = new Decryptor(
new Key(new File("/path/to/alice-sec.gpg"), "password123"),
new Key(new File("/path/to/bob-pub.gpg"))
);
decryptor.setSymmetricPassphrase("the eagle flies at midnight");
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
Also, if you're able to load the passphrase as a char[]
instead of as a String
, you can zero-out the char[]
after decryption by calling the clearSecrets()
method of the decryptor:
Decryptor decryptor = new Decryptor();
try {
decryptor.setVerificationRequired(false);
char[] passphrase = ... // load this as a char[] from some source
decryptor.setSymmetricPassphraseChars(passphrase);
decryptor.decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
} finally {
decryptor.clearSecrets();
}
JPGPJ can handle any of the encryption algorithms specified in RFC 4880:
- Unencrypted
- IDEA
- TripleDES
- CAST5
- Blowfish
- AES128
- AES192
- AES256
- Twofish
Plus the Camellia family (RFC 5581):
- Camellia128
- Camellia192
- Camellia256
JPGPJ may fail to decrypt a PGP message for a variety of reasons. Here are some common errors you may see:
JPGPJ will raise a DecryptionException
with an error message of "no suitable decryption key found" if it fails to decrypt a message for one of the following reasons:
One reason a DecryptionException
may be raised is that the message was encrypted with a key or keys not on the Decryptor
ring. When attempting to decrypt a message, JPGPJ will log an INFO
level message of "not found decryption key [subkey long ID]" for every subkey that could be used to decrypt the message but is not on the Decryptor
ring. If you see this log message, you need to add one of the missing keys to the Decryptor
ring in order to decrypt the encrypted message.
Another reason a DecryptionException
may be raised is that the message was encrypted with a key or keys for which the passphrase has not been set. When attempting to decrypt a message, JPGPJ will log an INFO
level message of "not using decryption key [subkey flags and short ID]" for every subkey that could be used to decrypt the message but either doesn't have its passphrase set, or isn't flagged for decryption. If you see this log message, make sure you have set a passphrase for the subkey.
See KeyRings#setting-passphrases for instructions on how to set the passphrase for a subkey.
Another reason a DecryptionException
may be raised is that the message was encrypted with a subkey not flagged for encryption/decryption usage. When attempting to decrypt a message, JPGPJ will log an INFO
level message of "not using decryption key [subkey flags and short ID]" for every subkey that could be used to decrypt the message but either doesn't have its passphrase set, or isn't flagged for decryption. If you see this log message, and you have set a passphrase for the subkey, try explicitly setting the forDecryption
flag on the subkey; or, alternately, using the KeyForDecryption
class to load the key, which will automatically set the forDecryption
flag on all subkeys that possibly could be used for decryption.
The following example shows how to use the KeyForDecryption
class to load Alice's secret key (and use it exclusively for decryption), and the KeyForVerification
class to load Bob's public key (and use it exclusively for verification):
Decryptor decryptor = new Decryptor(
new KeyForDecryption(new File("/path/to/alice-sec.gpg"), "password123"),
new KeyForVerification(new File("/path/to/bob-pub.gpg"))
).decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
Another reason a DecryptionException
may be raised is that the message was encrypted with a passphrase instead of a public key, and no passphrase was set on the Decryptor
. If this is the case, you won't see any "not found decryption key" or "not using decryption key" log messages from the Decryptor
. If the message was encrypted with a passphrase instead of a public key, you need to set that passphrase on the Decryptor
itself in order to decrypt the message.
See Symmetric Decryption for instructions on how to set a symmetric passphrase.
JPGPJ will raise a PassphraseException
if it fails to decrypt a message for one of the following reasons:
JPGPJ will raise a PassphraseException
with an error message of "incorrect passphrase for subkey [subkey flags and short ID]" if it tries to decrypt a message with a secret key that has its passphrase set to an incorrect passphrase. If you see this error message, you're using the wrong passphrase for the key.
JPGPJ will raise a PassphraseException
with an error message of "incorrect passphrase for symmetric key" if it tries to decrypt a message encrypted with a passphrase, and is using an incorrect passphrase to do so. If you see this error message, you're using the wrong symmetric passphrase.
JPGPJ will raise a VerificationException
with an error message of "content not signed with a required key" if it fails to verify a decrypted message for one of the following reasons:
One reason a VerificationException
may be raised is that the message was not signed at all. If the message was not signed at all, you won't see any "not found verification key" or "not using verification key" log messages (see below) from the Decryptor
.
If message signatures aren't important for your use-case, turn off signature verification as described in the No verification section.
Another reason a VerificationException
may be raised is that the message was signed with a key or keys not on the Decryptor
ring. When attempting to verify a message, JPGPJ will log an INFO
level message of "not found verification key [subkey long ID]" for every subkey that could be used to verify the message but is not on the Decryptor
ring. If you see this log message, you need to add one of the missing keys to the Decryptor
ring in order to verify the encrypted message.
Another reason a VerificationException
may be raised is that the message was signed with a subkey not flagged for signing/verification usage. When attempting to verify a message, JPGPJ will log an INFO
level message of "not using verification key [subkey flags and short ID]" for every subkey that could be used to verify the message but isn't flagged for verification. If you see this log message, try explicitly setting the forDecryption
flag on the subkey; or, alternately, using the KeyForVerification
class to load the key, which will automatically set the forVerification
flag on all subkeys that possibly could be used for verification.
The following example shows how to use the KeyForDecryption
class to load Alice's secret key (and use it exclusively for decryption), and the KeyForVerification
class to load Bob's public key (and use it exclusively for verification):
Decryptor decryptor = new Decryptor(
new KeyForDecryption(new File("/path/to/alice-sec.gpg"), "password123"),
new KeyForVerification(new File("/path/to/bob-pub.gpg"))
).decrypt(
new File("path/to/ciphertext.txt.gpg"),
new File("path/to/plaintext.txt")
);
JPGPJ will raise a VerificationException
with an error message of "bad signature for key [key uid and subkeys]" if it fails to verify a decrypted message because the signature is invalid. This would be the case if the message was tampered with (or it also could be the case that the message was signed incorrectly or in a way incompatible with the current Bouncy Castle PGP implementation).
The above exceptions extend from the Bouncy Castle PGPException
class. If an error occurs in the low-level mechanics of decrypting a message (like the message has been formatted incorrectly, or is not actually a PGP message), a generic PGPException
will be raised.
JPGPJ will raise an IOException
if a general I/O error occurs not specific to the decryption process (like the input file does not exist, or the input stream was cut off unexpectedly).
One reason in particular an IOException
might be raised is if you try to overwrite the file you're decrypting with the decrypted file. If your use-case calls for decrypting a file "in place", first decrypt the file to a temporary location, and then use the java File.renameTo()
or Files.move()
methods to replace the encrypted file with the decrypted version.