Encryption and decryption with Groovy

Author: Paul King
Published: 2022-09-19 02:34PM


Inspired by this recent blog entry, here is an example showing how to encrypt and decrypt with Groovy.

Using the JDK crypto classes

First, we need some text to encrypt. We’ll use an excerpt of the one from the aforementioned blog post:

var text = 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has \
roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.'

Next, we’ll create a factory for our cipher instance, generate a key, and set up an initialization vector.

First, the cipher factory:

var factory = { Cipher.getInstance('AES/CBC/PKCS5Padding') }

For our cipher algorithm, we are using the Advanced Encryption Standard (AES) algorithm, in Cipher Block Chaining (CBC) mode, with PKCS5 padding. We’ll look at other options later.

Next we generate our secret key. Our secret key is our password. Only someone who has the password will be able to decrypt the encrypted message. We could use any random bits for our key, but like passwords, we want to choose a strong key rather than a weak one. Cryptographic libraries provide classes to generate such keys. We just need to provide the key size. AES supports 128, 192 and 256-bit keys. We’ll choose 192 here:

var key = generateKey('AES', 192)

Our code uses this helper method:

def generateKey(String algorithm, Integer size) {
    var generator = KeyGenerator.getInstance(algorithm)
    generator.init(size)
    generator.generateKey()
}

Next, we generate an initialization vector:

var ivParameterSpec = randomParameterSpec(factory)

It uses this helper method (we’re using the algorithm block size for our initialization vector size):

def randomParameterSpec(Closure<Cipher> factory) {
    var block = new byte[factory().blockSize]
    SecureRandom.instanceStrong.nextBytes(block)
    new IvParameterSpec(block)
}

An initialization vector is used to introduce some additional randomness to avoid repeating patterns in the input leading to repeating patterns in the encrypted bytes.

With all these things in place, we are almost ready to encrypt or decrypt, but first, let’s define two more helper methods:

def encrypt(byte[] bytes, Key key, IvParameterSpec spec, Closure<Cipher> factory) {
    var cipher = factory()
    cipher.init(ENCRYPT_MODE, key, spec)
    cipher.doFinal(bytes)
}

def decrypt(byte[] bytes, Key key, IvParameterSpec spec, Closure<Cipher> factory) {
    var cipher = factory()
    cipher.init(DECRYPT_MODE, key, spec)
    cipher.doFinal(bytes)
}

And here is how we encrypt and decrypt:

var encrypted = encrypt(text.bytes, key, ivParameterSpec, factory)
println "Encrypted bytes : $encrypted"
println "Encrypted text : ${new String(encrypted)}"

var decrypted = decrypt(encrypted, key, ivParameterSpec, factory)
println "Decrypted bytes : $decrypted"
println "Decrypted text : ${new String(decrypted)}"

Which has this output:

Encrypted bytes : [-117, 36, 18, 69, -101, -8, 35, 93, -102, -49, -12, …, -19, -100]
Encrypted text : ‹$E›ø#]šÏôæ”Á˜çp^µ³=L(Ö^_ŒC>CIË„ö,1É8ÆŸ.Š?vßG,Èw‰å¼zÜf>?µ›D¹éÆk€ °˜2êÔ}í©àhl$>?¹¡Kå3ÔO?±&…êî¶Ê–¾°®q®à—0ú‘ÔhO<H¦ç®Ç”ÈhAëjó QPyƒy6Ĥ*´un¼ï¯m¨´ÙjeJtëº\ó6ƪKªœíœ
Decrypted bytes : [67, 111, 110, 116, 114, 97, 114, 121, 32, 116, 111, 32, …, 100, 46]
Decrypted text : Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.

We can see that everything worked as expected, since the final output matches our original input text.

We can alternatively, swap algorithms. There are numerous algorithms and modes supported by the JDK and others supported by third-party libraries. A nice summary can be found here. Let’s have a look at using a different algorithm.

Using the Bouncy Castle library

We’ll swap to use the CAST5 (CAST-128) algorithm which supports up to a 128-bit key. We’ll use HMAC-SHA1 to generate our key.

import org.bouncycastle.jce.provider.BouncyCastleProvider
var bc = new BouncyCastleProvider()
factory = { Cipher.getInstance('CAST5', bc) }
key = generateKey('HmacSHA1', 128)
ivParameterSpec = randomParameterSpec(factory)

CAST5 is the default algorithm used in some versions of GPG and PGP. It isn’t included by default in the JDK, so for this we’ll use the Bouncy Castle library.

NOTE

Just as an aside, if you are wanting to encrypt or decrypt GPG/PGP files, don’t use the above code. Libraries like Bouncy Castle have dedicated classes for such scenarios.

We now encrypt and decrypt as before:

encrypted = encrypt(text.bytes, key, ivParameterSpec, factory)
println "Encrypted text : ${new String(encrypted)}"
decrypted = decrypt(encrypted, key, ivParameterSpec, factory)
println "Decrypted text : ${new String(decrypted)}"

Which has this output:

Encrypted text : Mªá?r?v9£÷~4µT'›ÙÝÁl¿Þg¾0ñŽ¡?Ü=³9Q¬»3«ÖÁ¡µ ¾@4÷`FñÙŠfø7¥#›v¤Í–‰¼Ü¢ƒE6ôŽTÙlæÏz>o?àL›¡¢z1nÖo9]šOÔ¼SÔOÍ#Ý7LœÀî}ó5m%q•»l%/AWT´¢zH#t솱l¶£—Œ«©wˆÃ®>®Ü6ër-E
Decrypted text : Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.

Other useful functionality

Passing around binary data like our secret key or the encrypted data, has its own problems. Groovy provides extension methods to encode such data (and corresponding decode methods). For example, we can encode our secret key in various ways:

var keyBytes = key.encoded
println keyBytes.encodeHex()
println keyBytes.encodeBase64()
println keyBytes.encodeBase64Url()

Which has this output (the key is random, so the output will differ for each run):

85a0d3f0ce0cbe6402dc9579fbffcf1d
haDT8M4MvmQC3JV5+//PHQ==
haDT8M4MvmQC3JV5-__PHQ

Groovy also provides extension methods for various checksums (but you might want to look at stronger checksum algorithms in security sensitive scenarios):

println "SHA256 : ${text.sha256()}"
println "MD5 : ${text.md5()}"

Which has this output:

SHA256 : ccb184e35e4c32bafc730d84ec924ea2980035ea5fadb012e3b2b31abf4323c9
MD5 : 46c61a174c2dc99204521ca89f09f63c

If you are encrypting and decrypting entire files, the JDK has special classes for that too which are also easy to use from Groovy. That’s all for now.

Conclusion

We have taken a brief look at encrypting and decrypting with Apache Groovy.