http://fastpicket.com/blog/2012/05/14/easy-pgp-in-java-bouncy-castle/
Posted by nate on May 14, 2012
I’m going to try my hand at a programmer’s blog. If nothing else I’d like to keep better track of all the little snippets of code and config that pile up and never seem to get organized.
I recently needed to do some PGP encryption in a webapp. Basic stuff, let users store a PGP public key on their profile and encrypt data with their key when we send them email, that kind of thing. So assuming we have a PGP public key block, we need to decode something that looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
mQENBE+XFq4BCADDQA9UFwxI2LUqC562PfYZaMETfm2hrfOpLqsHZ6XJ3vHL2c4K
7mbtDqxx9WYvZUb+i47j0ktYwvRJ/L1MDFDrCKrwRNmuDZjtZt3WHc503E6D9dpz
olJQa/ecXJ2p7Tmwcqq4I4WTrXv08mCkDDJ8DhmiRCg1Ekyeqeg71o4BHYZkiFQ8
WEweipqSwY2R3yxba6ADSLH0GYwgo3ZlOu95noO6IR9gcbuJelSsIbLg95ZXjaL1
0u2PpJcgj/bzl44uwPa2B+Nbup0pB33ljiDVBBEKNfpkXT6gHpWwnLfY+JKxM0bG
rZy/UU8GCtLBn/zC4oC6RZflS5uqqMHa54IhABEBAAG0L05hdGUgU2FtbW9ucyAo
U1NSIFRlc3QgS2V5KSA8bnNhbW1vbnNAZnRlbi5jb20+iQE4BBMBAgAiBQJPlxau
AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRC59WkIJpdvq+HnB/4iayXM
IvskvWV+jUIdphEYI6lsRvPMpwFxtcZmmHVdQCwK6pbDvJRgLVOjovUStmKIcM2O
YSeOVKt31XJuHAzcSBUflkNZXk5cmtYytEH+KWy3ZSwTaX153UwBSc/cOVbtszJI
cF8tGbQe5gaxEzxUVooXroWY+5G/yN/pJ6j+ZKkHCxRei4c/95+JmcO80+lLvGy1
x/9ZmtxbCQpqkWg0V7hETwjroH/vrZpL8l+jSd7XQz90nIEdTvt/xfcNb91jj0QM
Wy1P5PNRvRzUXoOSnLQvr67ZleIWLY93vbPdq2oNcecfMdl21LyfY3wBIo6C6cIi
zmaQsSenN+c4sdDsuQENBE+XFq4BCACp6gU7GEYWgjVZXWh/d6clLFoV+HZp8Adt
g65SERAKnU29POn2/BLGWZ6W4d20INxR+7Dt8y2WTDMRwkx6MnBT3+kZTC+K5qXQ
BiOyjyZYUtm5FNgVJq6/5xQz3UxNg76osQqtJP6zrDeXOeFgQZrHgYuSukMj1LM1
qO88HiHPBvgu/FR9ZeoqJG47FAVQRoTvNYIPsLMOmS2tlH77hodpZeJZ3WZzPZB8
4q7y6ySZXN9XHpqI5pubpQvaozOgWMMdbo25/7z+Xilu+I4MxFhrUb4eKVJI6L/i
cLUCGRFFAgd5aqcGfENgMa5NgCVigCEifeKNaoi9vI5TJV1kk5I1ABEBAAGJAR8E
GAECAAkFAk+XFq4CGwwACgkQufVpCCaXb6vVKgf/Z9hKeX4nBACBDpOjKzSgO+76
W1kgqHvNm+GTOHTbJ7cUokSudZ4PMkx5HQB+Q7u0nZnwHNts/Ih+DmTA9T/VXO1j
ljKHSJfRGZiAXqb1V0NvWahhE3As1IJIkqdBSNXZfjCgEA5apCGnT3xmMUTOIhb9
61ucsrNI0odoumkftpXQPDD7WhbYkN7aRIbA90uYAp5+nMSvYHdMRWVHjWQ0ZVo0
tpvlelBF4RMBm4HQQxMCKW0syhf0bzOSxCiu15Xssl6JBgz3yOU2b7xi1NXjutj2
TCALL5xgN5/lMxPl7TqNx4PtHLTxbFOkulKsJnpogLjiNhFRDDo3qK/CW8CXSA==
=GsQd
-----END PGP PUBLIC KEY BLOCK-----
|
Bouncy Castle is an open-source provider of encryption software. They have a cleanroom implementation of a Java SecurityProvider and a full PGP tools implementation, all entirely in Java, all free. We can pull in the security provider and PGP implementation to our project with this maven dependency declaration:
1
2
3
4
5
|
<
dependency
>
<
groupId
>org.bouncycastle
groupId
>
<
artifactId
>bcpg-jdk16
artifactId
>
<
version
>1.47
version
>
dependency
>
|
To get their security provider but not the PGP implementation, use this instead:
1
2
3
4
5
|
<
dependency
>
<
groupId
>org.bouncycastle
groupId
>
<
artifactId
>bcprov-jdk16
artifactId
>
<
version
>1.47
version
>
dependency
>
|
Now that we have some libraries to work with, we can decode that string and look through the keyring. To do this we need to decode the data stream in that text, read a keyring and finally look for an encryption key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import
org.bouncycastle.openpgp.PGPObjectFactory;
import
org.bouncycastle.openpgp.PGPPublicKey;
import
org.bouncycastle.openpgp.PGPPublicKeyRing;
import
org.bouncycastle.openpgp.PGPUtil;
/**
* Decode a PGP public key block and return the keyring it represents.
*/
public
PGPPublicKeyRing getKeyring(InputStream keyBlockStream)
throws
IOException {
// PGPUtil.getDecoderStream() will detect ASCII-armor automatically and decode it,
// the PGPObject factory then knows how to read all the data in the encoded stream
PGPObjectFactory factory =
new
PGPObjectFactory(PGPUtil.getDecoderStream(keyBlockStream));
// these files should really just have one object in them,
// and that object should be a PGPPublicKeyRing.
Object o = factory.nextObject();
if
(o
instanceof
PGPPublicKeyRing) {
return
(PGPPublicKeyRing)o;
}
throw
new
IllegalArgumentException(
"Input text does not contain a PGP Public Key"
);
}
/**
* Get the first encyption key off the given keyring.
*/
public
PGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
if
(keyRing ==
null
)
return
null
;
// iterate over the keys on the ring, look for one
// which is suitable for encryption.
Iterator keys = keyRing.getPublicKeys();
PGPPublicKey key =
null
;
while
(keys.hasNext()) {
key = (PGPPublicKey)keys.next();
if
(key.isEncryptionKey()) {
return
key;
}
}
return
null
;
}
|
Now that we have those two methods, we can easily read a key out of a string. Once you have the PGPPublicKey
, you can create an output stream connected to an encrypted payload in an ascii-armored PGP text file. Data written to that stream is passed into PGP for encryption and ASCII encoding. The Bouncy Castle PGP implementation provides a lot of building blocks, implementations of all the different types of data structures present in PGP keys, signatures, encrypted data streams, etc. PGP’s standardized data format is very packet and stream oriented — every file contains a stream, every stream is a series of packets. Packets can be keyrings or any number of things. What we want to do specifically is make a file containing what PGP calls a “literal” data packet. This type of packet is simply a named series of bytes, in our case a named series of plain text characters. Because of all the items involved in what sounds very simple, we create a utility class to make this easier. Bouncy Castle’s implementations also don’t chain calls to close()
, so you have to keep track of the various streams and what is connected to what so that you can close them in the right order. If you don’t close up shop in the right order you may end up with a file that’s truncated.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
import
org.bouncycastle.bcpg.ArmoredOutputStream;
import
org.bouncycastle.openpgp.*;
import
java.io.IOException;
import
java.io.OutputStream;
import
java.security.NoSuchProviderException;
import
java.security.SecureRandom;
import
java.util.Date;
public
class
PGPEncryptionUtil {
private
static
final
String BC_PROVIDER_NAME =
"BC"
;
// pick some sensible encryption buffer size
private
static
final
int
BUFFER_SIZE =
4096
;
// encrypt the payload data using AES-256,
// remember that PGP uses a symmetric key to encrypt
// data and uses the public key to encrypt the symmetric
// key used on the payload.
private
static
final
int
PAYLOAD_ENCRYPTION_ALG = PGPEncryptedData.AES_256;
// various streams we're taking care of
private
final
ArmoredOutputStream armoredOutputStream;
private
final
OutputStream encryptedOut;
private
final
OutputStream compressedOut;
private
final
OutputStream literalOut;
public
PGPEncryptionUtil(PGPPublicKey key, String payloadFilename, OutputStream out)
throws
PGPException, NoSuchProviderException, IOException {
// write data out using "ascii-armor" encoding. This is the
// normal PGP text output.
this
.armoredOutputStream =
new
ArmoredOutputStream(out);
// create an encrypted payload and set the public key on the data generator
PGPEncryptedDataGenerator encryptGen =
new
PGPEncryptedDataGenerator(PAYLOAD_ENCRYPTION_ALG,
new
SecureRandom(), BC_PROVIDER_NAME);
encryptGen.addMethod(key);
// open an output stream connected to the encrypted data generator
// and have the generator write its data out to the ascii-encoding stream
this
.encryptedOut = encryptGen.open(armoredOutputStream, buffer);
// compress data. we are building layers of output streams. we want to compress here
// because this is "before" encryption, and you get far better compression on
// unencrypted data.
PGPCompressedDataGenerator compressor =
new
PGPCompressedDataGenerator(PGPCompressedData.ZIP);
this
.compressedOut = compressor.open(encryptedOut);
// now we have a stream connected to a data compressor, which is connected to
// a data encryptor, which is connected to an ascii-encoder.
// into that we want to write a PGP "literal" object, which is just a named
// piece of data (as opposed to a specially-formatted key, signature, etc)
PGPLiteralDataGenerator literalGen =
new
PGPLiteralDataGenerator();
this
.literalOut = literalGen.open(compressedOut, PGPLiteralDataGenerator.UTF8,
payloadFilename,
new
Date(),
new
byte
[BUFFER_SIZE]);
}
/**
* Get an output stream connected to the encrypted file payload.
*/
public
OutputStream getPayloadOutputStream() {
return
this
.literalOut;
}
/**
* Close the encrypted output writers.
*/
public
void
close()
throws
IOException {
// close the literal output
literalOut.close();
// close the compressor
compressedOut.close();
// close the encrypted output
encryptedOut.close();
// close the armored output
armoredOutputStream.close();
}
}
|
So now that we have all these tools, we can put it together into a simple app that reads an ecrypted key from a file and writes out a message to and encrypted file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import
org.bouncycastle.jce.provider.BouncyCastleProvider;
import
org.bouncycastle.openpgp.*;
// add the bouncy castle security provider
// or have it installed in $JAVA_HOME/jre/lib/ext
Security.addProvider(
new
BouncyCastleProvider());
// read a public key from a file
PGPPublicKeyRing keyRing = getKeyring(
new
FileInputStream(
new
File(
"my_public_key.asc"
));
// read a public key from that keyring
PGPPublicKey publicKey = getEncryptionKey(keyRing);
System.out.println(
"Public Key: "
+ publicKey);
System.out.println(
" ID: "
+ publicKey.getKeyID());
// make an output stream connected to a file
// this also works with output streams in servlets
FileOutputStream fileOutputStream =
new
FileOutputStream(
new
File(
"big_secret.asc"
));
// make one of our encryption utilities
PGPEncryptionUtil util =
new
PGPEncryptionUtil(publicKey,
"secrets.txt"
, fileOutputStream);
// finally write something
PrintWriter pw =
new
PrintWriter(util.getPayloadOutputStream());
pw.println(
"Hello, world!"
);
// flush the stream and close up everything
pw.flush();
util.close();
|
Now you should have a file called “big_secret.asc
” which contains ascii-ified encrypted data. If you run that through PGP for decryption using the private key, it should create a file called “secrets.txt
” containing our top secret message.
Note the line where we install the security provider. This will register the provider with the JVM at runtime. You can also register the library implicitly by placing the bcprov-jdk16-1.47.jar
file in $JAVA_HOME/jre/lib/ext
— the jar must always remain intact because it has been digitally signed. If you extract the contents (say as part of a maven assembly) then the library will fail to load and the JVM will complain loudly.
Also if you’re going to use the above example, you will need the unlimited strength JCE jurisdiction policy files — because encryption software is still considered as dangerous as a nuclear weapon. You can download those policy files from Oracle’s Java SE downloads page, down at the bottom labeled “Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files”.
Sorry, the comment form is closed at this time.