RSA算法实践整理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wojiushiwo945you/article/details/78912184

背景

Web应用中对前端的密码进行加密,然后后台再解密,前端加密工具有jsencrypt.min.js可以使用,该工具在前端用公钥加密,后端私钥解密,以提高应用系统敏感数据的安全度。本文将整理RAS非对称加密算法的使用过程及注意事项,作为网络笔记,以备后用。

RSA和Base64

RSA加密算法是对输入的字节数组数据进行加密,输出另一个加密后的字节数组。这个字节数值数据该怎么存储呢?答案是使用Base64编码。

Base64是网络上常见的传送8比特字节码的编码方式之一,使用它可以将RSA算法的加密结果进行编码,转换为字符串存储到文件或其他地方,便于使用。

所以RSA加密算法通常是将加密结果转换为Base64编码后的字符串,在解密时先将Base64的字符串解码还原为字节数组,再调用RSA的解密算法进行解密。

字符串和密钥转换

RSA算法依赖两个密钥对象,即PrivateKey和PublicKey,一种简单的处理方法是只生成一次密钥对,并转换为字符串存储起来,提供给应用。

密钥都有一个getEncoded()方法,其返回该密钥的字节数组,同理利用Base64编码后存储为字符串。几种涉及到的转换过程如下:

第一种,密钥转换为字符串,公钥、私钥方法一样,典型代码:

      public static String getKey(Key key) throws Exception {
            byte[] keyBytes = key.getEncoded();
            return (new BASE64Encoder()).encode(keyBytes);
       }

第二种,Base64字符串转换为私钥:

String KEY_PRIVATE = "";//Base64转码生成的字符串。
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(KEY_PRIVATE));
PrivateKey key = KeyFactory.getInstance("RSA").generatePrivate(keySpec);

先对Base64字符串进行解码,得到原始密钥的字节数组,然后再调用算法生成PKCS8EncodedKeySpec类型的私钥,该类继承EncodedKeySpec类,以编码格式来表示私钥。

第三,Base64字符串转换为公钥:

String KEY_PUBLIC = "";//Base64转码生成的字符串。
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(KEY_PUBLIC));
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(keySpec);

同理,先得到密钥的字节数组,再创建X509EncodedKeySpec实例,该类继承自EncodedKeySpec类,以编码格式来表示公钥。

jsencrypt.min.js

一个前端加密工具,在web应用中,如果需要对表单数据进行加密,可以引入该js。它使用公钥进行加密,常见的调用方法如下:

/**
 * 密码加密
 * @param password 需要加密的明文密码
 * @returns 加密之后转换为Base64的字符串
 */
function encryptPassword(password){
    var encrypt = new JSEncrypt();
    var KEY_PUBLIC = "";//公钥Base64编码后的字符串
    encrypt.setPublicKey(KEY_PUBLIC);
    var encrypted = encrypt.encrypt(password);
    return encrypted;
}

这里使用KEY_PUBLIC就是和后台私钥对应的一对密钥,上述代码使用公钥对表单数据进行RSA加密,并将加密结果存储为一个Base64的字符串,传递给后台处理。

后台解密

私钥保存在服务器端,对前端的数据进行解密,对应的解密代码如下:

public String decrypt(String cipherText){
    //页面js加密RSA公钥对应的私钥
    String KEY_PRIVATE = "";

    try {
         //Base64转码为字节数组
        byte[] encryptByte = new BASE64Decoder().decodeBuffer(cipherText);
        Cipher cipher = Cipher.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(KEY_PRIVATE));
        PrivateKey key;
        key = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    /   /用私钥解密,Decrypt模式
        cipher.init(Cipher.DECRYPT_MODE, key);

        byte[] deBytes = cipher.doFinal(encryptByte);
        String plainPwd = new String(deBytes, "UTF-8");
        return plainPwd;
    } catch (Exception e) {
        logger.error("解密密码异常",e);
        return "";
    }
}

Cipher 对象的初始化方法init需要两个参数,操作模式和密钥信息,必须和目标操作一致,才能正确的加密或者解密。即:此处是解密操作,那么必须传递DECRYPT_MODE这个常量,否则就可能是二次加密了。

注意事项

RSA证书对加密的数据大小有限制,加密时支持的最大字节数=证书位数/8 -11;解密时最初字节数=证书位数/8。

1024位证书,加密时最大支持117个字节,解密时为128,PKCS#1建议的padding就占用11个字节,所以算上padding,最大支持117个字节。
Java的RSA算法默认的就是1024位的证书。

所以在处理超长的明文或密文时,需要按照对应的块最大长度限制,分段处理。
例如:一个密文二进制数组长度超过128位,那么解密时就需要分段处理:

 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 byte[] data = new byte[cipher.getOutputSize(fis.available())];
 int len = 0;
 while((len = fis.read(data)) > 0){
      bos.write(cipher.doFinal(data, 0, len));
 }

cipher.getOutputSize会返回解密操作的最大长度128,然后对输入文件流,逐段进行解密,每次只处理128字节的数据;上述代码如果直接调用doFinal,那么当数据超过128字节时,会出现data must not no longer than 128 byte的异常,导致解密失败。

同理,加密操作也要根据明文长度,逐段处理:

int blockSize = cipher.getBlockSize();
int blocksNum = encryptByte.length % blockSize == 0 ? 
        encryptByte.length / blockSize : encryptByte.length / blockSize + 1;
for (int i = 0; i < blocksNum; i++) {
    if (i < blocksNum - 1) {
        out.write(cipher.doFinal(encryptByte, i * blockSize, blockSize));
    } else {
        out.write(cipher.doFinal(encryptByte, i * blockSize, encryptByte.length - i * blockSize));
    }
}

上述代码在处理encryptByte这个明文字节数组时,根据它的分块个数,保证每次处理的长度为117。

猜你喜欢

转载自blog.csdn.net/wojiushiwo945you/article/details/78912184