使用 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 提供扩展方法来对这种数据进行 `encode`(以及相应的 `decode` 方法)。例如,我们可以用不同的方式对我们的密钥进行编码

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 进行加密和解密。