Easy PGP encryption in Java with Bouncy Castle

http://fastpicket.com/blog/2012/05/14/easy-pgp-in-java-bouncy-castle/

 

 

Easy PGP encryption in Java with 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值