RSA

RSA加密算法是最常用的非对称加密算法。非对称加密算法的特点就是加密秘钥和解密秘钥不同,秘钥分为公钥和私钥,用私钥加密的明文,只能用公钥解密;用公钥加密的明文,只能用私钥解密。RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名。RSA的安全基于大数分解的难度。其公钥和私钥是一对大素数(100到200位十进制数或更大)的函数。从一个公钥和密文恢复出明文的难度,等价于分解两个大素数之积(这是公认的数学难题)。

注意点

  1. RSA加密算法对于加密数据的长度是有要求的。一般来说,明文长度小于等于密钥长度(Bytes)-11。解决这个问题需要对较长的明文进行分段加解密,这个上面的代码已经实现了。

  2. 一旦涉及到双方开发,语言又不相同,不能够采用同一个工具的时候,切记要约定以下内容。

    • 约定双方的BASE64编码
    • 约定双方分段加解密的方式。我踩的坑也主要是这里,不仅仅是约定大家分段的大小,更重要的是分段加密后的拼装方式。doFinal方法加密完成后得到的仍然是byte[],因为最终呈现的是编码后的字符串,所以你可以分段加密,分段编码和分段加密,一次编码两种方式(上面的代码采用的是后一种,也推荐采用这一种)。相信我不是所有人的脑回路都一样的,尤其是当他采用的开发语言和你不通时。
  3. RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。

    这是因为,RSA算法本身要求加密内容也就是明文长度m必须0<m<密钥长度n。如果小于这个长度就需要进行padding,因为如果没有padding,就无法确定解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难,因为不确定后面的0是内容还是内容结束符。而只要用到padding,那么就要占用实际的明文长度,于是实际明文长度需要减去padding字节长度。我们一般使用的padding标准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。

    这样,对于1024长度的密钥。128字节(1024bits)-减去11字节正好是117字节,但对于RSA加密来讲,padding也是参与加密的,所以,依然按照1024bits去理解,但实际的明文只有117字节了。

    所以如果要对任意长度的数据进行加密,就需要将数据分段后进行逐一加密,并将结果进行拼接。同样,解码也需要分段解码,并将结果进行拼接。

    若生成1024bit的密钥,则RSA最大解密密文大小为128字节,RSA最大加密明文大小为117字节

后端java加密解密

# RSAUtil.java

import org.bouncycastle.util.encoders.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class RSAUtil {

    public static final String CHARSET = "UTF-8";
    public static final String RSA_ALGORITHM = "RSA";


    public static Map<String, String> createKeys(int keySize) {
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        //初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        //生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        //得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.toBase64String(publicKey.getEncoded());
        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.toBase64String(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<String, String>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);

        return keyPairMap;
    }

    /**
     * 得到公钥
     *
     * @param publicKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decode(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        return key;
    }

    /**
     * 得到私钥
     *
     * @param privateKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        return key;
    }

    /**
     * 公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.toBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     *
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.toBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     *
     * @param data
     * @param publicKey
     * @return
     */

    public static String publicDecrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock = 0;
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        byte[] resultDatas = null;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
            resultDatas = out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        } finally {
            try {
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultDatas;
    }
}
# main-test

public static void main(String[] args) throws Exception {
    Map<String, String> keyMap = RSAUtil.createKeys(1024);
    String publicKey = keyMap.get("publicKey");
    String privateKey = keyMap.get("privateKey");
    System.out.println("公钥: \n\r" + publicKey);
    System.out.println("私钥: \n\r" + privateKey);

    String data = "skyyemperor";
    System.out.println("\r明文:\r\n" + data);
    System.out.println("\r明文大小:\r\n" + data.getBytes().length);

    String encodedData = RSAUtil.publicEncrypt(data, RSAUtil.getPublicKey(publicKey));
    System.out.println("密文:\r\n" + encodedData);
    String decodedData = RSAUtil.privateDecrypt(encodedData, RSAUtil.getPrivateKey(privateKey));
    System.out.println("解密后文字: \r\n" + decodedData);
}
# 输出

公钥: 
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjY04JQYjOG0Ka6GRaykb66WYz9vOBPPyshIukIWr0H9TIW/3K4Snmk2Y6ACiKvk5IpvxVW277dyJojnp1+qBE/cC4np/FZ1rKkIAYbFa8eIk+DDF2u4IRVfFi8IzJI8wT2R2TqIgsyYpOERpggONxvY+R1JYGXtG/iwPNEE7zFwIDAQAB
私钥: 
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNjTglBiM4bQproZFrKRvrpZjP284E8/KyEi6QhavQf1Mhb/crhKeaTZjoAKIq+Tkim/FVbbvt3ImiOenX6oET9wLien8VnWsqQgBhsVrx4iT4MMXa7ghFV8WLwjMkjzBPZHZOoiCzJik4RGmCA43G9j5HUlgZe0b+LA80QTvMXAgMBAAECgYAPAr+XoBq2fNCotLMLmEvfH3RRT93dQMIiA2dk4+EUnpwI8FKnwfn8ggxBReRzorpEeHr0SJswpShXHMfpcOYqatLqhW9pBqBqw2mX51s0XEU/1l0eliStyjunSK9vWJu5/SHIkl6RtDG0+RfmgxYza3qgrISCkUEx46ujE3eOKQJBANma8zg+FHzmlh8i1tcWE9d8rMYizQKXTkIiUQoQc2jJvGGTjSV4/SmcuIZrMIs9unVsX5wVKSY3Hs1tmkSLH9UCQQDAN2a9gOwOfX/rVTT1FU1zW+a1XvBVVDWKCDwFaHpLROY0lU4l5Y7VDtN0ywLDfQweVyWAb45GhHhP02Rzz6k7AkEAve78ikPNeDOAJw8uvLAdg4HkAFFR5ggRG7J+T62KPpWPIxA6K9IODusnONSIm5C2llWQoljqANwtu5sGAvv4PQJAZsC3AXLbvdtE+K6yApoCrzpfmHFKFLNRc5p3S4TQa1BHp7Bw+D1BH5AvZe3oakx8n/OCLqhz7CqAjlu5BVfrZQJAZM+JMTBaRH03mgjQ0/HWFC8gL1J1CN9CjOIHrBwMxLaZEmbuxlfSH0kfYXtAxoGHVJSRNdpJAbAmcg9MUCHEFg==
明文:
skyyemperor
明文大小:
11
密文:
T2gUe8GOWNsnWJ2f2AFxXRbPJ6REwnf6GZ/4Ulf4aTdFCvfQv5wKHV3EUWtBwoqw5CvFadBN3eDPbxGGH4qi3L1MeDyYzhRTlmOYk6kP1mRTOGozh3oH0cbb5Lju6m4qK4Pj4blCXyNY2fAVuraCpAnpU/RtNZdG3Ys4ILP7nQo=
解密后文字: 
skyyemperor

前端JS加密

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>RSA_ENCRYPT</title>
  <script src="https://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
  <script type="text/javascript">
function encrypt(content) {
    var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCW7U3OQ7mwYJFj05slM5/NVT0ufxyYajSUGhFeAQbV2FfDHUeA02bWpj0guuUpEkJcy6rrLi31ZLa5fX2YvBvShCiOo87bKANkOCxE7O4cpRmJl3lzSoGZHtt+FaxnLhi3VbJv4RmZQyo7tv1vN6kxy0Y7FwKL0Qjhl889+X6cTQIDAQAB";
    var encrypt = new JSEncrypt();
    encrypt.setPublicKey(publicKey);
    var data = encrypt.encrypt(content);
    console.log(data);
    return data;
}
  </script>
</head>
<body>
<button onclick="encrypt('abcd1234')">加密</button>
</body>
</html>

AES

后端java加密解密

import java.nio.charset.StandardCharsets;
import java.util.UUID;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES工具类,密钥必须是16位字符串
 */
public class AESUtil {

    /**
     * 偏移量,必须是16位字符串
     */
    private static final String IV_STRING = "pian-yi-lianghhh";

    /**
     * 默认的密钥(必须为16位)
     */
    private static final String DEFAULT_KEY = "251c2d5sd2f23sd7";

    /**
     * 产生随机密钥(这里产生密钥必须是16位)
     */
    public static String generateKey() {
        String key = UUID.randomUUID().toString();
        key = key.replace("-", "").substring(0, 16);// 替换掉-号
        return key;
    }

    public static String encryptData(String key, String content) {
        byte[] encryptedBytes = new byte[0];
        try {
            byte[] byteContent = content.getBytes("UTF-8");
            // 注意,为了能与 iOS 统一
            // 这里的 key 不可以使用 KeyGenerator、SecureRandom、SecretKey 生成
            byte[] enCodeFormat = key.getBytes();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
            byte[] initParam = IV_STRING.getBytes();
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
            // 指定加密的算法、工作模式和填充方式
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
            encryptedBytes = cipher.doFinal(byteContent);
            // 同样对加密后数据进行 base64 编码
            return Base64Util.encodeByteToString(encryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decryptData(String key, String content) {
        try {
            // base64 解码
            byte[] encryptedBytes = Base64Util.decodeStringToByte(content);
            byte[] enCodeFormat = key.getBytes();
            SecretKeySpec secretKey = new SecretKeySpec(enCodeFormat, "AES");
            byte[] initParam = IV_STRING.getBytes();
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
            byte[] result = cipher.doFinal(encryptedBytes);
            return new String(result, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "F431E6FF9051DA07";
        String content = "skyyemperor";
        String encryptData = AESUtil.encryptData(key, content);
        System.out.println("aes加密后: " + encryptData);
        String decryptData = AESUtil.decryptData(key, encryptData);
        System.out.println("aes解密后: " + decryptData);
    }
}

前端加密解密

var iv  = CryptoJS.enc.Utf8.parse("pian-yi-lianghhh");   //加密向量
function AESEnc(key,content){
    var key = CryptoJS.enc.Utf8.parse(key);   //加密密钥
    var srcs = CryptoJS.enc.Utf8.parse(content);
    var encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv,mode:CryptoJS.mode.CBC});
    return encrypted.toString();
}

function AESDec(key,content){
    var key = CryptoJS.enc.Utf8.parse(key);   //加密密钥
    var decrypted = CryptoJS.AES.decrypt(content, key, { iv: iv,mode:CryptoJS.mode.CBC});
    return decrypted.toString(CryptoJS.enc.Utf8);
}

function getKey(){
	return uuid(16,16);
}

function uuid(len, radix) {
    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    var uuid = [], i;
    radix = radix || chars.length;

    if (len) {
      // Compact form
      for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
    } else {
      // rfc4122, version 4 form
      var r;

      // rfc4122 requires these characters
      uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
      uuid[14] = '4';

      // Fill in random data.  At i==19 set the high bits of clock sequence as
      // per rfc4122, sec. 4.1.5
      for (i = 0; i < 36; i++) {
        if (!uuid[i]) {
          r = 0 | Math.random()*16;
          uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
        }
      }
    }

    return uuid.join('');
}

加密算法应用场景

为什么要结合使用这两种算法

  如果不清楚非对称算法和对称算法,也许你会问,为什么要结合使用这两种算法,单纯使用一种算法不行吗?这就要结合不同的场景和需求了。

客户端传输重要信息给服务端,服务端返回的信息不需加密的情况

  客户端传输重要信息给服务端,服务端返回的信息不需加密,例如绑定银行卡的时候,需要传递用户的银行卡号,手机号等重要信息,客户端这边就需要对这些重要信息进行加密,使用RSA公钥加密,服务端使用RSA解密,然后返回一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败。这种场景下,仅仅使用RSA加密是可以的。

客户端传输重要信息给服务端,服务端返回的信息需加密的情况

  客户端传输重要信息给服务端,服务端返回的信息需加密,例如客户端登录的时候,传递用户名和密码等资料,需要进行加密,服务端验证登录信息后,返回令牌token需要进行加密,客户端解密后保存。此时就需要结合这两种算法了。至于整个流程是怎样的,在下面会慢慢通过例子向你介绍,因为如果一开始就这么多文字类的操作,可能会让读者感到一头雾水。

RSA算法+AES算法的使用

举一个简单的例子来说明一下吧,例如实名认证功能,需要传递用户真实姓名和身份证号,对于这种重要信息,需要进行加密处理。

客户端使用RSA + AES对重要信息进行加密

客户端加密过程主要分为以下三个步骤:

1.客户端随机产生AES的密钥;

2.对身份证信息(重要信息)进行AES加密;

3.通过使用RSA对AES密钥进行公钥加密。

这样在传输的过程中,即时加密后的AES密钥被别人截取,对其也无济于事,因为他并不知道RSA的私钥,无法解密得到原本的AES密钥,就无法解密用AES加密后的重要信息。

服务端使用RSA + AES对重要信息进行解密

服务端解密过程主要分为以下两个步骤:

1.对加密后的AES密钥进行RSA私钥解密,拿到密钥原文;

2.对加密后的重要信息进行AES解密,拿到原始内容。

现实开发中,服务端有时也需要向客户端传递重要信息,比如登录的时候,返回token给客户端,作为令牌,这个令牌就需要进行加密,原理也是差不多的,比上面多一个步骤而已,就是将解密后的AES密钥,对将要传递给客户端的数据token进行AES加密,返回给客户端,由于客户端和服务端都已经拿到同一把AES钥匙,所以客户端可以解密服务端返回的加密后的数据。如果客户端想要将令牌进行保存,则需要使用自己定义的默认的AES密钥进行加密后保存,需要使用的时候传入默认密钥和密文,解密后得到原token。

上面提及到客户端加密,服务端返回数据不加密的情况,上面说到仅仅使用RSA是可以,但是还是建议同时使用这两种算法,即产生一个AES密钥,使用RSA对该密钥进行公钥加密,对重要信息进行AES加密,服务端通过RSA私钥解密拿到AES密钥,再对加密后的重要信息进行解密。如果仅仅使用RSA,服务端只通过RSA解密,这样会对于性能会有所影响,原因是RSA的解密耗时约等于AES解密数据的100倍,所以如果每个重要信息都只通过RSA加密和解密,则会影响服务端系统的性能,所以建议两种算法一起使用。


hhhhh