Groovy AES Encryption Driver

Just posting this for documentation purposes. I am working on porting the pygogogo2 driver from python and hass.io to hubitat / groovy. I am almost there and wanted to post some of the harder bits here.

They were just hard for me as I didn't know any Groovy before this. I borrowed heavily from the GoGo2 driver created by @mgroeninger He helped get me down the road a bunch.

So here is the hot steamy mess of AES encryption and decryption:

import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.Cipher

/ do the magic
def encrypt (def plainText, def secret) {
  log ("Encrypting - ${plainText}","trace")


    // this particular magic sauce pads the payload to 128 bits per chunk
    // even though that shouldn't work with S5Padding....
    def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE")
    SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES")
    // generate an Initial Vector using Random Number and no additional libraries....
    // because they don't exist here....these are not the SecureRandom numbers you are looking for.....
    def IVKey = Long.toUnsignedString(new Random().nextLong(), 16).toUpperCase()
    IvParameterSpec iv = new IvParameterSpec(IVKey.getBytes("UTF-8"))
    // initialize the encryption and get ready for that dirty dirty magic
    cipher.init(Cipher.ENCRYPT_MODE, key, iv)
    // boom goes the dynamite
    def result = cipher.doFinal(plainText.getBytes("UTF-8")).encodeBase64().toString()
    // extract the Initial Vector and make it a string so we can pre-pend to the GoGoGate2
    // encryption string.  Someone way smarter than me did it so I am doing it too...
    // looking at you broadfoot..
    def ivString = cipher.getIV()
    ivString = new String(ivString, "UTF-8")
    // prepend the IV to the Result and send it back....still needs to be URLEncoded etc......
    // but we now have a valid payload.  Serioulsy...this was hard.....Python is WAY easier
    // when it comes to some of this manipulation....but at least I didn't have to use a lambda.
    return ivString+result
    }

// undo the magic
def decrypt (def cypherText, def secret) {
log ("Decrypting - ${cypherText}","trace")
  // this was so much easier to get done than the encryption.  It works.  Don't touch it
  // cereal.  you will regret it.

  // rip off the iv bandaid.....
  def iv = cypherText.take(16)
    log ("IV:  ${iv}","trace")
  // drop those beats..or bytes.
  byte[] decodedBytes = cypherText.drop(16).decodeBase64()
    log("decodedBytes:   ${decodedBytes}", "trace")
  // no whammy no whammy no whammy.......
  def cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE")
  SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "AES")
  cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv.getBytes("UTF-8")))
  //whammy!
  return new String(cipher.doFinal(decodedBytes), "UTF-8")
}
2 Likes

Glad you found my code useful!

1 Like

@mike.maxwell. I am thinking of a driver that would use these methods; however, before I leap, do these methods use excessive Hubitat resources (processor, memory, other resources) given an average command length of 100 characters???

Dave