Easy PGP encryption in Java with Bouncy Castle

来源:互联网 发布:挖矿软件下载 编辑:程序博客网 时间:2024/05/16 09:10

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 aPGP 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
importorg.bouncycastle.openpgp.PGPObjectFactory;
importorg.bouncycastle.openpgp.PGPPublicKey;
importorg.bouncycastle.openpgp.PGPPublicKeyRing;
importorg.bouncycastle.openpgp.PGPUtil;
  
/**
 * Decode a PGP public key block and return the keyring it represents.
 */
publicPGPPublicKeyRing getKeyring(InputStream keyBlockStream) throwsIOException {
    // 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 =newPGPObjectFactory(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 instanceofPGPPublicKeyRing) {
        return(PGPPublicKeyRing)o;
    }
    thrownewIllegalArgumentException("Input text does not contain a PGP Public Key");
}
  
/**
 * Get the first encyption key off the given keyring.
 */
publicPGPPublicKey getEncryptionKey(PGPPublicKeyRing keyRing) {
    if(keyRing == null)
        returnnull;
  
    // 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()) {
            returnkey;
        }
    }
    returnnull;
}

Now that we have those two methods, we can easily read a key out of a string.  Once you have thePGPPublicKey, 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 toclose(), 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
importorg.bouncycastle.bcpg.ArmoredOutputStream;
importorg.bouncycastle.openpgp.*;
  
importjava.io.IOException;
importjava.io.OutputStream;
importjava.security.NoSuchProviderException;
importjava.security.SecureRandom;
importjava.util.Date;
  
publicclassPGPEncryptionUtil {
  
    privatestaticfinalString BC_PROVIDER_NAME = "BC";
  
    // pick some sensible encryption buffer size
    privatestaticfinalintBUFFER_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.
    privatestaticfinalintPAYLOAD_ENCRYPTION_ALG = PGPEncryptedData.AES_256;
  
    // various streams we're taking care of
    privatefinalArmoredOutputStream armoredOutputStream;
    privatefinalOutputStream encryptedOut;
    privatefinalOutputStream compressedOut;
    privatefinalOutputStream literalOut;
  
    publicPGPEncryptionUtil(PGPPublicKey key, String payloadFilename, OutputStream out)throwsPGPException, NoSuchProviderException, IOException {
  
        // write data out using "ascii-armor" encoding.  This is the
        // normal PGP text output.
        this.armoredOutputStream =newArmoredOutputStream(out);
  
        // create an encrypted payload and set the public key on the data generator
        PGPEncryptedDataGenerator encryptGen =newPGPEncryptedDataGenerator(PAYLOAD_ENCRYPTION_ALG,
                                                              newSecureRandom(), 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 =newPGPCompressedDataGenerator(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 =newPGPLiteralDataGenerator();
        this.literalOut = literalGen.open(compressedOut, PGPLiteralDataGenerator.UTF8,
                                          payloadFilename,newDate(), newbyte[BUFFER_SIZE]);
    }
  
    /**
     * Get an output stream connected to the encrypted file payload.
     */
    publicOutputStream getPayloadOutputStream() {
        returnthis.literalOut;
    }
  
    /**
     * Close the encrypted output writers.
     */
    publicvoidclose() throwsIOException {
        // 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
importorg.bouncycastle.jce.provider.BouncyCastleProvider;
importorg.bouncycastle.openpgp.*;
  
// add the bouncy castle security provider
// or have it installed in $JAVA_HOME/jre/lib/ext
Security.addProvider(newBouncyCastleProvider());
  
// read a public key from a file
PGPPublicKeyRing keyRing = getKeyring(newFileInputStream(newFile("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 =newFileOutputStream(newFile("big_secret.asc"));
  
// make one of our encryption utilities
PGPEncryptionUtil util =newPGPEncryptionUtil(publicKey, "secrets.txt", fileOutputStream);
  
// finally write something
PrintWriter pw =newPrintWriter(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 fromOracle’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.

原创粉丝点击