RSA加密解密

32 篇文章 0 订阅

需求描述

小件员app端拨打消费者手机号,基于小件员手机号,消费者手机号,绑定一个中间隐私号码,通过拨打隐私小号来建立二者的通话,保护客户隐私。
隐私小号绑定由另外一个公司提供,我们称为公司B。我们公司称为公司A。
公司A将消费者手机号通过私钥加密。公司B将消费者手机号通过公钥加密。

加解密

加密

公司A将加密后的数据转为字符串透传给公司B

new String(EncodeDecodeUtil.encryptByPrivateKey(bindVirtualNumberParam.getCalledNum().getBytes(), privateKey),
                StandardCharsets.UTF_8)

解密

公司B将解密后的数据转为字符串,然后绑定隐私小号响应给公司A

new String(EncodeDecodeUtil.decryptByPublicKey(param.getCalledNum().getBytes(), publicKey),
                StandardCharsets.UTF_8)

工具类

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import javax.crypto.Cipher;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

/**
 * @author 会灰翔的灰机
 * @date 2020/11/20
 */
@Slf4j
public class EncodeDecodeUtil {

    public static final String RSA = "RSA";
    private final static String MOBILE_STR = "0123456789ABCDEF";

    /**
     * 私钥加密
     *
     * @param data 待加密数据
     * @param key 密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPrivateKey(byte[] data, String key) {
        try {
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key));
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            log.error("encryptByPrivateKey failed", e);
        }
        return data;
    }

    /**
     * 公钥解密
     *
     * @param data 待解密数据
     * @param key 密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPublicKey(byte[] data, String key) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(key));
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, pubKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            log.error("decryptByPublicKey failed", e);
        }
        return data;
    }

    /**
     * 将字节数组转换为十六进制字符串
     * @param bytearray :
     * @return :
     */
    public static String byteArrayToHexString(byte[] bytearray) {
        StringBuilder strDigest = new StringBuilder();
        for (byte b : bytearray) {
            strDigest.append(byteToHexString(b));
        }
        return strDigest.toString();
    }

    /**
     * 将字节转换为十六进制字符串
     * @param ib :
     * @return :
     */
    private static String byteToHexString(byte ib) {
        char[] digit = MOBILE_STR.toCharArray();
        char[] ob = new char[2];
        ob[0] = digit[(ib >>> 4) & 0X0F];
        ob[1] = digit[ib & 0X0F];
        return new String(ob);
    }

    /**
     * 16进制字符串转为字节数组
     * @param hex :
     * @return :
     */
    public static byte[] hexStringToByte(String hex) {
        int len = (hex.length() / 2);
        byte[] result = new byte[len];
        char[] character = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(character[pos]) << 4 | toByte(character[pos + 1]));
        }

        return result;
    }

    public static int toByte(char c) {
        return (byte) MOBILE_STR.indexOf(c);
    }
    
    public static void initKey() throws Exception {
        //通过SPI接口获取密钥生成器实例
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        //初始化密钥生成器
        keyPairGenerator.initialize(512);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 公钥、私钥转字符串,便于动态配置
        System.out.printf("publicKey=%s, privateKey=%s%n", Base64.encodeBase64String(publicKey.getEncoded()), Base64.encodeBase64String(privateKey.getEncoded()));
    }

    public static void main(String[] args) throws Exception {
        // 离线执行初始化公钥秘钥,并将公钥提供给公司B
        initKey();
    }
}

问题

IllegalBlockSizeException

该异常很直白,数据超出长度限制64字节

2020-11-23 14:53:19.304 [23333333] [HSFBizProcessor-DEFAULT-6-thread-2] ERROR ?:? - decryptByPublicKey failed
javax.crypto.IllegalBlockSizeException: Data must not be longer than 64 bytes
        at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:344)
        at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
        at javax.crypto.Cipher.doFinal(Cipher.java:2165)
        at com.dianwoda.virtual.number.util.EncodeDecodeUtil.decryptByPublicKey(EncodeDecodeUtil.java:56)
        at com.dianwoda.virtual.number.provider.VirtualNumberProviderImpl.bindVirtualNumber(VirtualNumberProviderImpl.java:62)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)

咋办?看源码吧,堆栈中的具体类:sun.security.mscapi.RSACipher

private byte[] doFinal() throws BadPaddingException, IllegalBlockSizeException {
    if (this.bufOfs > this.buffer.length) {
        throw new IllegalBlockSizeException("Data must not be longer than " + (this.buffer.length - this.paddingLength) + " bytes");
    } ...
}

可以看到我们实际可以使用的数据长度不一定是buffer的长度,还需要考虑paddingLength长度。这两个长度是否可以调整?两个字段的默认配置是多少?

buffer长度

buffer长度=密钥长度/8
除以8的原因是密钥的长度为bit单位,数据单位为字节byte

// sun.security.mscapi.RSACipher
// 1. 参数 var1:模式,例如:解密模式=Cipher.DECRYPT_MODE
// 2. 参数 var2:密钥,例如:非对称加密的公钥、密钥
private void init(int var1, java.security.Key var2) throws InvalidKeyException {
    boolean var3;
...
    if (var2 instanceof PublicKey) {
        ...
        this.outputSize = this.publicKey.length() / 8;
    } else {
        ...
        this.outputSize = this.privateKey.length() / 8;
    }
    this.bufOfs = 0;
    this.buffer = new byte[this.outputSize];
}

paddingLength长度

paddingLength长度逻辑

  1. 如果是javax.crypto.Cipher#ENCRYPT_MODE、javax.crypto.Cipher#WRAP_MODE模式,则为11
  2. 如果是javax.crypto.Cipher#DECRYPT_MODE、javax.crypto.Cipher#UNWRAP_MODE模式,则为0
// sun.security.mscapi.RSACipher
// 1. 参数 var1:模式,例如:解密模式=Cipher.DECRYPT_MODE
// 2. 参数 var2:秘钥,例如:非对称加密的公钥、秘钥
private void init(int var1, java.security.Key var2) throws InvalidKeyException {
    boolean var3;
    switch(var1) {
    case 1:
    case 3:
        this.paddingLength = 11;
        var3 = true;
        break;
    case 2:
    case 4:
        this.paddingLength = 0;
        var3 = false;
        break;
    default:
        throw new InvalidKeyException("Unknown mode: " + var1);
    }
...
}

所以如果是解密我们可以使用的数据长度为公钥长度,加密长度为私钥长度-11。因为我们的解密数据长度为512/8-0=64 bytes。与报错相符

解决方案

加大密钥长度

直接增加一倍至1024,不可行**,**因为随着key增长,加密后的数据也随之增长,依然触发了长度限制

keyPairGenerator.initialize(1024);

二次加密

方案引用:https://stackoverflow.com/questions/10007147/getting-a-illegalblocksizeexception-data-must-not-be-longer-than-256-bytes-when

  1. 使用对称加密算法生成一个"对称密钥"
  2. 使用"对称密钥"加密数据
  3. 使用RSA算法加密"对称密钥"
  4. 发送**加密后的****“对称密钥”**以及数据
  5. 使用RSA算法解密加密后的****"对称密钥"
  6. 使用解密后的"对称密钥"解密数据

分段加密

时间复杂度高不建议使用

private static String encryptByPublicKey(byte[] data, String publicKey, int maxEncryptBlockSize) throws Exception {
    KeyFactory keyFactory = KeyFactory.getInstance(RSA);
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
    PublicKey key = keyFactory.generatePublic(x509KeySpec);
    Cipher cipher = Cipher.getInstance(RSA);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    int inputLen = data.length;
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] encryptedData;
    try {
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxEncryptBlockSize) {
                cache = cipher.doFinal(data, offSet, maxEncryptBlockSize);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxEncryptBlockSize;
        }
        encryptedData = out.toByteArray();
    } finally {
        out.close();
    }
    return Base64.encodeBase64String(encryptedData);
}

数据转16进制字符串

减小数据长度。对加密后的数据不要直接转String,而是转16进制字符串。对于数据小,并且超长不多,且所有数据长度固定,可以通过该方式处理。如果数据长度不可控该方法也是不可行的。
加密侧使用如下代码进行byte数组转字符串

private final static String MOBILE_STR = "0123456789ABCDEF";
/**
 * 将字节数组转换为十六进制字符串
 * @param bytearray :
 * @return :
 */
public static String byteArrayToHexString(byte[] bytearray) {
    StringBuilder strDigest = new StringBuilder();
    for (byte b : bytearray) {
        strDigest.append(byteToHexString(b));
    }
    return strDigest.toString();
}

/**
 * 将字节转换为十六进制字符串
 * @param ib :
 * @return :
 */
private static String byteToHexString(byte ib) {
    char[] digit = MOBILE_STR.toCharArray();
    char[] ob = new char[2];
    ob[0] = digit[(ib >>> 4) & 0X0F];
    ob[1] = digit[ib & 0X0F];
    return new String(ob);
}

解密侧使用下面的代码将数据字符串转为byte数组

private final static String MOBILE_STR = "0123456789ABCDEF";
/**
 * 16进制字符串转为字节数组
 * @param hex :
 * @return :
 */
public static byte[] hexStringToByte(String hex) {
    int len = (hex.length() / 2);
    byte[] result = new byte[len];
    char[] character = hex.toCharArray();
    for (int i = 0; i < len; i++) {
        int pos = i * 2;
        result[i] = (byte) (toByte(character[pos]) << 4 | toByte(character[pos + 1]));
    }

    return result;
}

public static int toByte(char c) {
    return (byte) MOBILE_STR.indexOf(c);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值