使用 Groovy™ 进行加密和解密

作者: Paul King

发布时间:2022-09-19 02:34PM


受这篇近期博客文章的启发,这里有一个示例,展示如何使用 Groovy 进行加密和解密。

使用 JDK 加密类

首先,我们需要一些要加密的文本。我们将使用上述博客文章中的一个摘录。

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.'

接下来,我们将为我们的密码实例创建一个工厂,生成一个密钥,并设置一个初始化向量。

首先,密码工厂

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

对于我们的密码算法,我们使用高级加密标准(AES)算法,采用密码块链接(CBC)模式,并使用PKCS5 填充。我们稍后会探讨其他选项。

接下来我们生成我们的密钥。我们的密钥就是我们的密码。只有拥有密码的人才能解密加密的消息。我们可以使用任何随机位作为密钥,但像密码一样,我们希望选择一个强密钥而不是弱密钥。加密库提供了生成此类密钥的类。我们只需要提供密钥大小。AES 支持 128、192 和 256 位密钥。我们这里选择 192 位。

var key = generateKey('AES', 192)

我们的代码使用这个辅助方法

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

接下来,我们生成一个初始化向量

var ivParameterSpec = randomParameterSpec(factory)

它使用这个辅助方法(我们使用算法块大小作为初始化向量大小)

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

使用初始化向量是为了引入一些额外的随机性,以避免输入中重复的模式导致加密字节中出现重复的模式。

有了所有这些,我们几乎可以进行加密或解密了,但首先,让我们再定义两个辅助方法

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)
}

以下是我们加密和解密的方式

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)}"

其输出如下

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.

我们可以看到一切都按预期工作,因为最终输出与我们的原始输入文本匹配。

我们也可以替换算法。JDK 支持多种算法和模式,其他第三方库也支持其他算法和模式。一份不错的总结可以在这里找到。让我们看看如何使用不同的算法。

使用 Bouncy Castle 库

我们将切换到使用 CAST5(CAST-128)算法,该算法支持高达 128 位的密钥。我们将使用HMAC-SHA1来生成我们的密钥。

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

CAST5 是某些 GPG 和 PGP 版本中使用的默认算法。它默认不包含在 JDK 中,因此为此我们将使用 Bouncy Castle 库

注意

顺便提一下,如果您想加密或解密 GPG/PGP 文件,请不要使用上述代码。像 Bouncy Castle 这样的库针对此类场景提供了专用类

我们现在像以前一样进行加密和解密

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)}"

其输出如下

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.

其他有用功能

传递二进制数据,如我们的密钥或加密数据,本身就有问题。Groovy 提供了扩展方法来<编码>这类数据(以及相应的<解码>方法)。例如,我们可以用各种方式编码我们的密钥

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

其输出如下(密钥是随机的,因此每次运行的输出都会不同)

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

Groovy 还提供了各种校验和的扩展方法(但在安全敏感的场景中,您可能需要考虑更强的校验和算法)

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

其输出如下

SHA256 : ccb184e35e4c32bafc730d84ec924ea2980035ea5fadb012e3b2b31abf4323c9
MD5 : 46c61a174c2dc99204521ca89f09f63c

如果您正在加密和解密整个文件,JDK 也有专门的类,从 Groovy 调用这些类也很容易。目前就这些。

参考文献

结论

我们简要了解了使用 Apache Groovy 进行加密和解密。