校验方法
/**
* 校验数字签名
* @param data 加密数据
* @param publicKey 公钥
* @param sign 数字签名
* @return 校验成功返回true,失败返回false
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
// 解密由base64编码的公钥
byte[] keyBytes = decryptBASE64(publicKey);
// 构造X509EncodedKeySpec对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// KEY_ALGORITHM 指定的加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 取公钥匙对象
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pubKey);
signature.update(data);
// 验证签名是否正常
return signature.verify(decryptBASE64(sign));
}
调用逻辑例子
public static void main(String[] args) throws Exception {
String a = "test";
// 原始调用逻辑
verify(a.getBytes(), "publicKey", "signContent");
// 更改后的调用逻辑
verify(a.getBytes("UTF-8"), "publicKey", "signContent");
}
问题说明
在生产上发现签名验证总是失败, 获取正式环境中的数据, 本地写了个main方法进行调用 (逻辑一致, 写死了方法参数值为正式环境的值), 结果是校验成功
然后又写死了参数值 , 启动程序进行方法调用 , 结果是校验失败
结果
经排查确定公私钥没问题后, 怀疑是什么方法或逻辑会取当前环境的不同值 , 因为调用的参数都一致 , 但本地main测试和启动程序测试结果不一致
在仔细翻阅各个方法的源代码中后 , 发现 a.getBytes()
java.lang.String#getBytes()
这个获取字符数组时, 所使用的字符集是不确定的
java.lang.StringCoding#encode(char[], int, int)
java.nio.charset.Charset#defaultCharset
这里可以看出 , java.lang.String#getBytes()
最终所获取的字符集是受当前环境影响的
而在实际应用中 , 我本地的idea设置的是utf-8的字符集, 但使用的Tomcat因需要 , 设置了gbk的字符集
所有在本地使用main方法获取的是我idea设置的utf-8
默认字符集, 而使用本地Tomcat( 生产一致 )启动程序时获取到的是Tomcat中设置的gbk
默认字符集 , 导致验签结果不一致
调整逻辑a.getBytes("UTF-8")
使用 java.lang.String#getBytes(java.lang.String)
方法指定字符集为utf-8后 , 正式环境验签成功