RSA签名和验签说明
最近遇到一个项目,需要进行RSA的验签,RSA的密钥为2048位即256个字节长。
上游是先对一个文件进行SHA256做hash,得到32字节的摘要,然后进行填充,填充规则为前面两个字节为0x00,0x01,然后填充全FF,接下来是32字节的摘要数据,这样填充之后的总数据长度为256字节,然后使用RSA的私钥对这256字节进行签名,下发下来,下游需要对这个签名进行处理,得到原始摘要,然后跟自己计算的摘要比对,验证通过则合法。
涉及的知识点如下:
一、RSA签名算法的填充方式,常用的有如下三种
1、RSA/ECB/PKCS1Padding 填充模式,最常用的模式
要求:
输入:必须 比 RSA 密钥长度 短至少11个字节, 否则会报错,数据不足时由接口自动进行随机填充
如果输入的明文过长,必须切割, 然后填充
输出:和RSA密钥长度一样长
根据这个要求,对于2048bit的密钥, block length = 2048/8 – 11 = 245字节
注意:加密的时候会在你的明文中随机填充一些数据,所以会导致对同样的明文每次加密后的结果都不一样。
对加密后的密文,服务器使用相同的填充方式都能解密。解密后的明文也就是之前加密的明文。
如果是私钥签名,那么填充的前面两个字节为0x00和0x01(表示私钥签名),如果是公钥签名,那么填充的签名的两个字节为0x00和0x02(表示公钥签名),然后填充全FF,在填充最后的有效数据之前,会填充一个00,然后后面就全部是有效数据,所以使用RSA签名的数据再解密之后得到的数据在真是数据之前一定有00,否则就是输入没有严格遵守RSA/ECB/PKCS1Padding填充方式
2.RSA_PKCS1_OAEP_PADDING
输入:必须 比 RSA 密钥长度 短至少41个字节, 否则会报错,数据不足时由接口自动进行随机填充
输出:和RSA密钥长度一样长
3.RSA/ECB/NoPadding 不填充
输入:可以和RSA钥模长一样长,如果输入的明文过长,必须切割,然后填充,这种方式需要自己手动进行填充好输入数据
输出:和RSA密钥长度一样长
跟DES,AES一样, RSA也是一个块加密算法( block cipher algorithm),总是在一个固定长度的块上进行操作。
但跟AES等不同的是, block length是跟key length有关的。
每次RSA加密的明文的长度是受RSA填充模式限制的,但是RSA每次加密的块长度就是密钥长度
具体代码片段如下:
/**
* RSA签名
*
* @param key RSA的密钥 公钥用X.509编码;私钥用PKCS#8编码
* @param data 输入数据
* @param mode 0-加密,1-解密
* @param type 0-私钥加密,公钥解密 1-公钥加密,私钥解密
* @return 签名后的数据 为null表示操作失败
*/
public static String generateRSA(String key, String data, int mode, int type) {
try {
// 判断加密还是解密
int opmode = (mode == 0) ? Cipher.ENCRYPT_MODE
: Cipher.DECRYPT_MODE;
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Key strkey = null;
if (mode != type) { // 生成公钥
KeySpec keySpec = new X509EncodedKeySpec(
Base64.decodeBase64(key)); // X.509编码
strkey = keyFactory.generatePublic(keySpec);
} else { // 生成私钥
KeySpec keySpec = new PKCS8EncodedKeySpec(
Base64.decodeBase64(key)); // PKCS#8编码
strkey = keyFactory.generatePrivate(keySpec);
}
// 获得一个RSA的Cipher类,使用私钥加密
// RSA/ECB/NoPadding表示无填充,待加密数据长度必须跟密钥长度一致,填充需要自己完成
// RSA/ECB/PKCS1Padding表示PKCS#1填充,前面填充0x00,0x01(私钥加密,公钥加密是02),然后填充0xFF,在数据之前再填充一个0x00,接下来是真正传入的数据,待加密数据需要小于密钥数据-11,这个是自动填充
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); // ,
// 初始化
cipher.init(opmode, strkey);
byte[] byteData = str2bytes(data);
System.out.println("generateRSA doFinal字节长度为: " + byteData.length);
// 返回加解密结果
return (bytesToHexString(cipher.doFinal(byteData)))
.toUpperCase(Locale.getDefault());// 开始计算
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return null;
}