Java实现对称加密(AES)和非对称加密(RAS)

Java实现对称加密(AES)和非对称加密(RAS)

一、对称加密和非对称加密区别介绍

对称加密: 对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。对称加密通常使用的是相对较小的密钥,一般小于256 bit。因为密钥越大,加密越强,但加密与解密的过程越慢。如果你只用1 bit来做这个密钥,那黑客们可以先试着用0来解密,不行的话就再用1解;但如果你的密钥有1 MB大,黑客们可能永远也无法破解,但加密和解密的过程要花费很长的时间。密钥的大小既要照顾到安全性,也要照顾到效率,是一个trade-off。对称加密的一大缺点是密钥的管理与分配,换句话说,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传输到需要的人。
非对称加密: 非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public key)和私钥(private key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。比如,你向银行请求公钥,银行将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人–银行才能对你的消息解密。与对称加密不同的是,银行不需要将私钥通过网络发送出去,因此安全性大大提高。

二、优缺点

对称加密: 速度快,但是安全性不高。
非对称加密: 加密解密速度慢,但是安全性高。

三、实现

1、对称加密AES实现

public class AESUtils {

    /*
    * 后端PKCS5Padding补码方式和前端的CryptoJS.pad.Pkcs7效果一致
    * key的字符串长度限制为16
    * */
    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";

    //加密
    public static String encrypt(String content, String key) {
        try {
            byte[] raw = key.getBytes();  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  //根据指定算法ALGORITHM自成密码器
            cipher.init(Cipher.ENCRYPT_MODE, skey); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] byteContent = content.getBytes("utf-8"); //获取加密内容的字节数组(设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte [] encodeContent = cipher.doFinal(byteContent); //密码器加密数据
            return java.util.Base64.getEncoder().encodeToString(encodeContent); //将加密后的数据转换为字符串返回
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //解密
    public static String decrypt(String encryptStr, String decryptKey) {
        try {
            byte[] raw = decryptKey.getBytes();  //获得密码的字节数组
            SecretKeySpec skey = new SecretKeySpec(raw, "AES"); //根据密码生成AES密钥
            Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  //根据指定算法ALGORITHM自成密码器
            cipher.init(Cipher.DECRYPT_MODE, skey); //初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
            byte [] encodeContent = Base64.getDecoder().decode(encryptStr); //把密文字符串转回密文字节数组
            byte [] byteContent = cipher.doFinal(encodeContent); //密码器解密数据
            return new String(byteContent,"utf-8"); //将解密后的数据转换为字符串返回
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

2、非对称加密RAS实现

public class RSAUtil {

    private static final String PUB_KEY = "publicKey";
    private static final String PRI_KEY = "privateKey";
    private static final String CHARSET = "UTF-8";
    private static final String RSA_ALGORITHM = "RSA";
    private static final Integer KEY_LENGTH = 1024;

    public static final Map<String, String> keyMap = createKeys(KEY_LENGTH);

    /**
     * 生成秘钥对
     *
     * @param keySize
     * @return Map<String,String>
     */
    private static Map<String, String> createKeys(int keySize) {
        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 = new String(Base64.encodeBase64(publicKey.getEncoded()));
        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));
        Map<String, String> keyPairMap = new HashMap<>(1);
        keyPairMap.put(PUB_KEY, publicKeyStr);
        keyPairMap.put(PRI_KEY, privateKeyStr);

        return keyPairMap;
    }

    /**
     * 得到公钥
     *
     * @param keyMap 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static String getPublicKey(Map<String, String> keyMap)  {
        return keyMap.get(PUB_KEY);
    }

    /**
     * 得到私钥
     *
     * @param keyMap 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static String getPrivateKey(Map<String, String> keyMap)  {
        return keyMap.get(PRI_KEY);
    }

    /**
     * 公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String data, String publicKey) {
        try {
            //通过X509编码的Key指令获得公钥对象
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
            RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return new String(Base64.encodeBase64(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), key.getModulus().bitLength())));
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

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

    public static String privateDecrypt(String data, String privateKey) {
        try {
            //通过PKCS#8编码的Key指令获得私钥对象
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
            RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), key.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new CustomizeException(StatusCode.FAILED_CODE,"解密字符串[" + data + "]时遇到异常");
        }
    }

    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) throws IOException {
        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;
        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;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        out.close();
        return resultDatas;
    }

}

四、应用场景

问题: 例如你上传一个html文件时,攻击者可能通过抓包的方式修改了你上传文件的内容(比如往你的文件中添加了一些恶意的js代码),如何才能解决这种xss攻击。
解决思路: 我们可以对上传的文件内容进行加密。这里说到对文件内容进行加密,对于这种内容是比较多的,我们肯定是使用对称加密进行加密的,但是前端需要将加密的秘钥传输给我们的,这时候又有问题了,这种秘钥容易被黑客给获取到,这时候就需要使用非对称加密的方式进行对秘钥再加密的。
解决方案实现: 首先后端服务生成一对秘钥(公钥和私钥),对外暴露一个获取公钥的接口给前端。客户端这边可以使用AES生成一个秘钥(这里要注意AES的秘钥只能是16个字节的),用生成的秘钥和文件内容进行对称加密得到密文传输到后端,同时将生成的秘钥用刚刚通过接口获取的公钥进行一次非对称加密传输到后端。此时后端只需要将前端传输的秘钥使用自己的私钥进行解密得到新的秘钥,在将新的秘钥解密密文即可得到文件内容明文了。
注意:这里我们可能会用到MultipartFile这个类,这就涉及到如何修改MultipartFile中的文件内容了,这里就涉及到了获取MultipartFile中的你内容然后修改后再写回到MultipartFile中了,网上提供了一个MockMultipartFile类实现,但是这种有两个问题:

1、在服务器高并发情况下,生成大量小文件会导致服务器inode被打满,另外生成文件还需要考虑命名处理防止重名.
2、MockMultipartFile这个方法看起来很方便,实际上这个方法是spring-test中提供的,生产环境根本不会打包test。

这里提供一个比较好的解决方案:自己自定义一个实现MultipartFile的类
1、读取MultipartFile内容

public static String readMultipartFile(MultipartFile multipartFile) {
        StringBuilder stringBuilder = null;
        try {
            if (multipartFile != null) {
                InputStream bb = multipartFile.getInputStream();
                InputStreamReader streamReader = new InputStreamReader(bb, StandardCharsets.UTF_8);
                BufferedReader reader = new BufferedReader(streamReader);
                String line;
                stringBuilder = new StringBuilder();
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
                reader.close();
                bb.close();
            } else{
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return String.valueOf(stringBuilder);
    }

2、自定义实现MultipartFile类

public class CustomMultipartFile implements MultipartFile {
    private MultipartFile multipartFile;
    private byte[] scriptByte;
    public CustomMultipartFile(MultipartFile file,byte[] scriptByte) {
        this.multipartFile = file;
        this.scriptByte = scriptByte;
    }
    @Override
    public String getName() {
        return multipartFile.getName();
    }

    @Override
    public String getOriginalFilename() {
        return multipartFile.getOriginalFilename();
    }

    @Override
    public String getContentType() {
        return multipartFile.getContentType();
    }

    @Override
    public boolean isEmpty() {
        return StringUtils.isEmpty(scriptByte.toString());
    }

    @Override
    public long getSize() {
        return scriptByte.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return scriptByte;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(scriptByte);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        new FileOutputStream(dest).write(scriptByte);
    }
}

完结

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值