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