项目场景:
某个项目中,需要对参数进行sign校验,其中用到了AES加密算法,于是使用了javax.crypto.Cipher进行加解密的运算。
问题描述
在压测中发现,偶尔会出现校验失败的情况。而生成sign的工具与被测试程序的算法是完全一致的,理论上不应该出现校验不通过,所以需要排查问题。
原因分析:
对被测试程序与测试工具分别进行了多线程测试,发现测试工具对同样的参数,每次生成的sign是一致的,而被测试程序偶尔会生成不同的sign,于是怀疑是并发导致的问题。
被测试程序与测试工具唯一的不同在于,被测试程序的Cipher是在初始时就实例化好的:
Cipher encryptCipher = Cipher.getInstance(ALGORITHM); cipher.init(mode, keySpec, new IvParameterSpec(iv));
之后每次加密都直接使用该对象:
encryptCipher.doFinal(srcData.getBytes(StandardCharsets.UTF_8));
而测试工具是每次获得一个新对象:
Cipher encryptCipher = Cipher.getInstance(ALGORITHM); cipher.init(mode, keySpec, new IvParameterSpec(iv)); encryptCipher.doFinal(srcData.getBytes(StandardCharsets.UTF_8));
通过查看源码与查阅资料可知, Cipher实例内部维护着自身的状态,在init或者doFinal时会改变自身状态,所以并非是线程安全的。
public final void init(int var1, Key var2, AlgorithmParameterSpec var3, SecureRandom var4) throws InvalidKeyException, InvalidAlgorithmParameterException {
this.initialized = false;
checkOpmode(var1);
if (this.spi != null) {
this.checkCryptoPerm(this.spi, var2, var3);
this.spi.engineInit(var1, var2, var3, var4);
} else {
this.chooseProvider(2, var1, var2, var3, (AlgorithmParameters)null, var4);