A Grails plugin with some Groovy wrappers for both Blowfish and PGP, as well as a tool class for generating random passwords, salting passwords when hashing them, and verifying salted passwords. Uses the
Bouncy Castle encryption libraries.
Password Management
Mr. Smart Developer Says:
"Storing plaintext passwords is EEEEEEVIL. Simply don't do it, because you're
exposing your users to all kinds of potential threat, and may be tempted for evil
yourself. If you aren't going to use some kind of externalized authentication service,
then store your passwords salted and hashed."
While Mr. Smart Developer is totally right, that's traditionally been difficult to do in Grails, because there's no easy way to salt and hash passwords. This plugin solves that.
Password management utilities are found in cr.co.arquetipos.password.PasswordTools. In there are two sets of static methods for working with salted and hashed passwords:
saltPasswordBase64(String) / checkDigestBase64(String,String)
saltPasswordHex(String) / checkDigestHex(String,String)
The first method in each of these sets generates a String consisted of the salted and hashed password, and the second checks a submitted password (the first argument) against a salted and hashed password (the second argument).
In addition, there are two utility static methods to return a random login and password:
generateRandomPassword(int size=10)
generateRandomLogin(int size=10)
The set of allowed password characters are the alphanumerics along with "!@,;.-=+" (not including quotes).
The set of allowed login characters are the alphanumerics along with just "." (not including quotes).
Blowfish
The following encrypts a message with blowfish, and attempts to decrypt it with both the correct password and a bad one.
String message = 'Hush hush'
String password = 'DeVitto'
String badPassword = 'Danny'// Encrypts the message as a byte array
byte[] encrypted = Blowfish.encrypt(message.getBytes(), password)
assert encrypted// Decrypts with the correct password, and compares
def decrypted = Blowfish.decrypt(encrypted, password)
assert decrypted
String decryptedString = new String(decrypted)// Decrypted strings may contain trailing char(0)s
decryptedString = decryptedString.trim()println "$decryptedString ${decryptedString.getBytes()}"
println "$message ${message.getBytes()}"assertEquals decryptedString, message// Decrypting with a bad password returns null
decrypted = Blowfish.decrypt(encrypted, badPassword)
assert decrypted == null
Encrypted values can also be returned base64-encoded, where there's an extra option to trim the result string.
String message = 'Hush hush'
String password ='DeVitto'
String badPassword = 'Danny'// Encrypts a message and returns it base64-encoded
String encrypted = Blowfish.encryptBase64(message, password)
assert encrypted
String decrypted = Blowfish.decryptBase64(encrypted, password)
assert decryptedassertEquals decrypted, message// There's an option to not trim the return value, even though the
// default is to do so. In that case, the decrypted value may not
// exactly match the original message
decrypted = Blowfish.decryptBase64(encrypted, password, false)
assert decrypted != message
assertEquals decrypted.trim(), messageprintln "The following two rows should be different:"
println "$decrypted ${decrypted.getBytes()}"// Decrypting with an invalid password returns null
decrypted = Blowfish.decryptBase64(encrypted, badPassword)
assertEquals decrypted, nullPGP
Key generation and encoding:
// Generates a PGP key pair
def pgp = PGP.generateKeyPair()// Verifies that all values are present
assert pgp
assert pgp.keyPair
assert pgp.publicKey
assert pgp.privateKeyassertEquals pgp.keyPair.publicKey, pgp.publicKey
assertEquals pgp.keyPair.privateKey, pgp.privateKey// Obtains the public and private keys, encoded armored-encoded
String encodedPublic = pgp.encodedPublicKey
String encodedPrivate = pgp.encodedPrivateKey// Creates PGP objects which contain only the public or private keys
PGP publicOnly = new PGP(encodedPublic, '')
PGP privateOnly = new PGP('', encodedPrivate)// Verifies that the previously encoded public key matches the
// one from the object we just created with it
assertEquals encodedPublic, publicOnly.encodedPublicKeyassert publicOnly
assert privateOnly
// Key created from the private key must have the public key as well
assert privateOnly.encodedPrivateKey
assert privateOnly.encodedPublicKeyprintln "From privateOnly:"
println publicOnly.encodedPublicKey
println privateOnly.encodedPublicKey
println privateOnly.encodedPrivateKey
For encryption, the following code encrypts a message in three different ways, which should all be equivalent. While the example returns the encrypted text as base64-encoded, a function that returns it as a byte array is also available (the equivalent call would be pgp.decrypt)
def pgp = PGP.generateKeyPair()String encodedPublic = pgp.encodedPublicKey
String encodedPrivate = pgp.encodedPrivateKeyPGP publicOnly = new PGP(encodedPublic, '')
PGP privateOnly = new PGP('', encodedPrivate)assert publicOnly
assert privateOnlyassertEquals encodedPublic, publicOnly.encodedPublicKeyString message = 'Hush hush'
/* Encrypts the message in three different ways: with the original
key pair, with the one that only has the public key, and with
the one that was generated from the private key. They will later
be decrypted in several ways to ensure that all are equivalent. */
String encrypted = pgp.encryptBase64(message)
String encrypted2 = publicOnly.encryptBase64(message)
String encrypted3 = privateOnly.encryptBase64(message)assert encrypted
assert encrypted2
assert encrypted3println "Encrypted number one:"
println encrypted
println "Encrypted number two:"
println encrypted2
String decryptedPrivate = privateOnly.decryptBase64(encrypted)
assert decryptedPrivateString decryptedBoth = pgp.decryptBase64(encrypted2)
assert decryptedBothassertEquals decryptedPrivate, decryptedBoth
assertEquals decryptedPrivate, messagedecryptedPrivate = privateOnly.decryptBase64(encrypted2)
assert decryptedPrivatedecryptedBoth = pgp.decryptBase64(encrypted)
assert decryptedBothString decryptedPrivate3 = privateOnly.decryptBase64(encrypted3)
assert decryptedPrivate3String decryptedBoth3 = pgp.decryptBase64(encrypted3)
assert decryptedBoth3assertEquals decryptedPrivate, decryptedBoth
assertEquals decryptedPrivate, message
assertEquals decryptedBoth3, message
assertEquals decryptedPrivate3, message
And this code encrypts a message and instead of returning the byte array base64-encoded, returns it encoded as armored text.
def pgp = PGP.generateKeyPair()String encodedPublic = pgp.encodedPublicKey
String encodedPrivate = pgp.encodedPrivateKeyPGP publicOnly = new PGP(encodedPublic, '')
PGP privateOnly = new PGP('', encodedPrivate)assert publicOnly
assert privateOnlyassertEquals encodedPublic, publicOnly.encodedPublicKeyString message = 'Hush hush'
String encrypted = pgp.encryptArmored(message)
String encrypted2 = pgp.encryptArmored(message)println "Armored 1:n$encryptedn"
println "Armored 2:n$encrypted2n"
In the case of a decryption failure, the decrypt function will return null:
def pgp = PGP.generateKeyPair()
def pgp2 = PGP.generateKeyPair()String message = 'Hush hush'
String encrypted = pgp.encryptBase64(message)
assert encryptedString decrypted = pgp2.decryptBase64(encrypted)
assertEquals decrypted, null
While the default encodedPrivateKey function returns the key encrypted with a blank passphrase, one can easily be added:
def pgp = PGP.generateKeyPair()String passphrase = PasswordTools.generateRandomPassword(10)
assert passphrase
assertEquals passphrase.size(), 10String encodedPublic = pgp.encodedPublicKey
String encodedPrivate = pgp.getEncodedPrivateKey(passphrase)// .....
// The encoded key is later instantiated using the passphrasePGP privateOnly = new PGP('', encodedPrivate, passphrase)
assert privateOnlyThe above code would fail in JDK6 because of a String.getBytes() problem.
The solution would be to:
import java.nio.charset.Charset
....
byte
(+) xencrypted = Blowfish.encrypt(message.getBytes(Charset.forName("ISO-8859-1")), password)
Similar for decrypt:
String decryptedString = new String(decrypted,Charset.forName("ISO-8859-1"))
BTW, Some how "UTF-8" does not work here