JAVA常用加解密的学习

关于加解密

一、名词解释

单向加密

  • 单向加密:通过对数据进行摘要计算生成密文,密文不可逆推还原。只能加密,不能解密。
    常用于提取数据的指纹信息以此来验证数据的完整性。但是会引发雪崩效应(雪崩效应就是一种不稳定的平衡状态也是加密算法的一种特征,它指明文或密钥的少量变化会引起密文的很大变化,就像雪崩前,山上看上去很平静,但是只要有一点问题,就会造成一片大崩溃。 可以用在很多场合对于Hash码,雪崩效应是指少量消息位的变化会引起信息摘要的许多位变化。)
  • 算法代表:Base64,MD5,SHA。

对称加密

  • 对称加密:加密和解密是使用同一个密钥;加密和解密的速度比较快,效率比较高;但是密钥传输过程不安全,容易破解,而且密钥管理也比较麻烦。
  • 算法代表:DES,3DES,AES,IDEA,RC4,RC5。

非对称加密

  • 非对称加密:非对称加密是一种“信息公开的密钥交换协议”。非对称加密需要公开密钥和私有密钥两组密钥,公开密钥和私有密钥是配对起来的,也就是说使用公开密钥进行数据加密,只有对应的私有密钥才能解密。这两个密钥是数学相关,用某用户密钥加密后的密文,只能使用该用户的加密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个密钥性质。这里把公开的密钥为公钥,不公开的密钥为私钥。
  • 算法代表:RSA,DSA。

加密盐

  • 加密盐也是比较常听到的一个概念,盐就是一个随机字符串用来和我们的加密串拼接后进行加密。加盐主要是为了提供加密字符串的安全性。假如有一个加盐后的加密串,黑客通过一定手段这个加密串,他拿到的明文,并不是我们加密前的字符串,而是加密前的字符串和盐组合的字符串,这样相对来说又增加了字符串的安全性。

散列函数

  • 散列函数在密码学中也是不可缺少的一部分。散列函数(hash function)是将任意长度的输入转化为定长输出的算法。谈到散列函数,肯定会想到MD5加密,这种就是一种最为常见的散列函数。
  • 特点:1、抗原像性(单向性)给定一个散列,计算上无法找到或者构造出生成它的消息。即不能还原,MD5即是一种单项加密,因此,经常用于密码加密,实现即使管理员也无法知道用户的密码的功能。2、抗第二原像性(弱抗碰撞性)给定一条消息和它的散列,计算上无法找到一条不同的消息具有相同的散列。3、强抗碰撞性 计算上无法找到两条散列相同的消息。

数字签名

  • 在通过散列函数来验证消息完整性的时候,仅仅在信息和数据的散列分开传输的时候才可以,否则中间人可以修改数据的同时修改散列,从而避开检测。数字签名主要是验证数据的真伪。微信通过对称加密生成的签名,支付宝通过非对称加密生成签名。效果差别不大。只要足够证明自己的身份即可。
    在这里插入图片描述

二、各种加密方式详解

BASE64加密/解密

1. 简介
  • Base64 编码是我们程序开发中经常使用到的编码方法,它用 64 个可打印字符来表示二进制数据。这 64 个字符是:小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+“、”/“(再加上作为垫字的”=",实际上是 65 个字符),其他所有符号都转换成这个字符集中的字符。Base64 编码通常用作存储、传输一些二进制数据编码方法,所以说它本质上是一种将二进制数据转成文本数据的方案。
2. 加密原理
编码过程
    1. 将待编码的数据(二进制)划分为每3个字节一组。
    1. 将每组的3个字节拆分成4组6位的二进制数。
    1. 将这些6位的二进制数转换为十进制数,然后映射到Base64字符集。
    1. 将得到的Base64字符连接起来,即为编码后的结果。
解码过程
    1. 将Base64字符逐个转换为对应的6位二进制数。
    1. 将这些6位二进制数合并成每组3个字节。
    1. 这样就得到了原始的二进制数据。
3. 应用场景
    1. 数据传输: 在网络通信中,Base64经常用于传输二进制数据,尤其是在URL参数中。通过Base64编码,可以确保数据的可读性和传输的稳定性。
    1. 文件上传: 在文件上传的过程中,为了避免乱码和数据损坏,通常会使用Base64对文件进行编码。这样即便是二进制文件也能以文本的形式进行传输。
    1. 数据存储: 有些数据库或配置文件需要存储二进制数据,但是直接存储可能导致问题。通过Base64编码,可以将二进制数据转换为文本格式,更适合存储和管理。
4. 示例
    /***
     * BASE64加密
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(byte[] key) throws Exception{
        return (new BASE64Encoder()).encode(key);
    }

	/***
     * BASE64解密
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryBASE64(String key) throws Exception{
        return (new BASE64Decoder()).decodeBuffer(key);
    }
    
    /***
     * BASE64加密
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(String key) throws Exception{
    	// 加密
        String encodedString = Base64.getEncoder().encodeToString(key.getBytes());
        System.out.println("加密后的字符串: " + encodedString);
        return encodedString ;
    }
    
	/***
     * BASE64解密
     * @param key
     * @return
     * @throws Exception
     */
    public static String decryBASE64(String key) throws Exception{
    	byte[] decodedBytes = Base64.getDecoder().decode(key);
        String decodedString = new String(decodedBytes);
        System.out.println("解密后的字符串: " + decodedString);
        return decodedString ;
    }

MD5加密

1. 简介
  • MD5是一种常用的哈希函数,广泛用于对数据进行加密和验证完整性。
  • MD5 是将任意长度的数据字符串转化成短小的固定长度的值的单向操作,任意两个字符串不应有相同的散列值。因此 MD5 经常用于校验字符串或者文件,因为如果文件的 MD5 不一样,说明文件内容也是不一样的。
  • MD5 主要用做数据一致性验证、数字签名和安全访问认证,而不是用作加密。比如说用户在某个网站注册账户时,输入的密码一般经过 MD5 编码,更安全的做法还会加一层盐(salt),这样密码就具有不可逆性。然后把编码后的密码存入数据库,下次登录的时候把密码 MD5 编码,然后和数据库中的作对比,这样就提升了用户账户的安全性。
2. 加密原理
  • 不可逆性: MD5 是一种单向哈希函数,意味着无法从 MD5 哈希值还原出原始数据。即使是微小的数据变化,也会导致相当大的哈希值变化。
  • 固定长度: MD5 始终产生固定长度的输出,即128位,无论输入数据的大小是多少。
  • 输入敏感: 即使输入数据仅有微小的变化,MD5 哈希值也会有显著的不同。这使得 MD5 适用于验证数据的完整性,因为任何数据的修改都会导致哈希值的变化。
3. 缺点
  • 由于其固定长度输出和演算法特性,MD5 存在碰撞问题,即两个不同的输入可以生成相同的 MD5 哈希值。因此,MD5 在安全敏感的场景中已经被认为不够安全,不建议用于密码存储等需要高度安全性的用途。
4. 示例
    /***
     * MD5加密(生成唯一的MD5值)
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptSHA(byte[] data) throws Exception{
		MessageDigest md = MessageDigest.getInstance("MD5");
		md.update(data);
		return md.digest();
	}

    /***
     * Spring提供的包 MD5加密
     * @param data
     * @return
     * @throws Exception
     */
    public static String MD5Util(String str) throws Exception {
       	String rel = Md5Utils.encryptToMd5(str);
        System.out.println("MD5加密方法一:" + Md5Utils.encryptToMd5(str));
        System.out.println("MD5加密方法二:" + Md5Utils.encrypt2ToMd5(str));
        System.out.println("MD5加密方法三:" + Md5Utils.encrypt3ToMd5(str));
        return rel;
    }

SHA加密

1. 简介
  • SHA全名叫做安全散列算法,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。数字签名等密码学应用中重要的工具,被广泛地应用于电子商务等信息安全领域。
2. 步骤
    1. 创建MessageDigest对象,并指定算法为SHA-256。
    1. 将要加密的字符串转换成字节数组。
    1. 使用MessageDigest对象对字节数组进行加密。
    1. 将加密后的字节数组转换成十六进制的字符串。
3. 原理
  • SHA-1是一种数据加密算法,该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码,并把它们转化为长度较短、位数固定的输出序列即散列值的过程。
  • 单向散列函数的安全性在于其产生散列值的操作过程具有较强的单向性。如果在输入序列中嵌入密码,那么任何人在不知道密码的情况下都不能产生正确的散列值,从而保证了其安全性。SHA将输入流按照每块512位(64个字节)进行分块,并产生20个字节的被称为信息认证代码或信息摘要的输出。
  • 该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512 位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。
  • 通过散列算法可实现数字签名实现,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。
4. 示例
import java.security.MessageDigest;
 
public class SHAUtil {
    public static final String KEY_SHA = "SHA";  
    public static final String ALGORITHM = "SHA-256";
    
    /***
     * SHA加密(比MD5更安全)
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptSHA(byte[] data) throws Exception{
        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
        sha.update(data);
        return sha.digest();
    }
    
    public static String SHAEncrypt(final String content) {
        try {
            MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
            byte[] sha_byte = sha.digest(content.getBytes());
            StringBuffer hexValue = new StringBuffer();
            for (byte b : sha_byte) {
                //将其中的每个字节转成十六进制字符串:byte类型的数据最高位是符号位,通过和0xff进行与操作,转换为int类型的正整数。
                String toHexString = Integer.toHexString(b & 0xff);
                hexValue.append(toHexString.length() == 1 ? "0" + toHexString : toHexString);
            }
            return hexValue.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
       return "";
    }
    
    //SHA-256加密
    public static String SHA256Encrypt(String sourceStr) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance(ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if (null != md) {
            md.update(sourceStr.getBytes());
            String digestStr = getDigestStr(md.digest());
            return digestStr;
        }
        return null;
    }
 
    private static String getDigestStr(byte[] origBytes) {
        String tempStr = null;
        StringBuilder stb = new StringBuilder();
        for (int i = 0; i < origBytes.length; i++) {
            tempStr = Integer.toHexString(origBytes[i] & 0xff);
            if (tempStr.length() == 1) {
                stb.append("0");
            }
            stb.append(tempStr);
        }
        return stb.toString();
    }
}

DES对称加密/解密

1. 简介
  • DES 是一种对称加密算法,所谓对称加密算法就是:加密和解密使用相同密钥的算法。
  • DES算法使用一个56位的密钥(实际上有64位,但其中8位用于奇偶校验,不参与加密过程),并通过一系列复杂的操作生成16个48位的子密钥,每个子密钥用于加密算法的一轮迭代中。密钥生成的过程包括置换、移位和压缩等操作,以确保生成的子密钥具有足够的随机性和复杂性。
  • 3DES是 DES 向 AES 过渡的加密算法,使用 3 条 56 位的密钥对数据进行三次加密。是 DES 的一个更安全的变形。它以 DES 为基本模块,通过组合分组方法设计出分组加密算法。比起最初的 DES,3DES 更为安全。
  • 使用 Java 实现 DES 加密解密,注意密码长度要是 8 的倍数。加密和解密的 Cipher 构造参数一定要相同,不然会报错。
2. 特点
  • 分组加密算法: 以64位为分组。64位明文输入,64位密文输出。
  • 对称算法: 加密和解密使用同一秘钥,有效秘钥长度为56位,秘钥通常表示为64位数,但每个第8位用作奇偶校验,可以忽略。
  • 代替和置换: DES算法是两种加密技术的组合:混乱和扩散。先替代后置换。
  • 易于实现: DES算法只是使用了标准的算术和逻辑运算,其作用的数最多也只有64 位,因此用70年代末期的硬件技术即可实现,算法的重复特性使得它可以非常理想地用在一个专用芯片中。
  • 密钥长度较短: DES使用56位密钥,相对于现代加密算法来说,密钥长度较短,容易受到暴力破解攻击。
  • 加密速度快: 由于DES算法相对简单,加密和解密速度较快,适用于对性能要求较高的场景。
  • 安全性较低: 由于密钥长度较短和算法设计的局限性,DES算法的安全性已经受到质疑,不再适用于高安全性的应用。
3. Feistel网络
  • DES算法的核心是一个称为Feistel网络的结构。Feistel网络将初始置换后的明文数据分成左右两部分,每部分32位。然后,通过16轮迭代,对这两部分数据进行交替的加密操作。
  • 在每一轮迭代中,Feistel网络使用当前轮的子密钥对右半部分数据进行加密,并将加密结果与左半部分数据进行异或运算。然后,将异或运算的结果作为下一轮的右半部分数据,而将原始的左半部分数据作为下一轮的左半部分数据。这样,左右两部分数据在每一轮迭代中都会进行交换和更新。
  • 每一轮迭代中的加密操作包括扩展置换、S盒代替、P盒置换和异或运算四个步骤。扩展置换将32位的数据扩展成48位,以便与48位的子密钥进行异或运算。S盒代替是一种非线性替换操作,将48位的数据分成8个6位的部分,并分别通过8个不同的S盒进行替换,得到8个4位的结果。P盒置换是一种置换操作,将8个4位的结果合并成一个32位的结果。最后,异或运算将P盒置换的结果与左半部分数据进行异或运算,得到加密后的右半部分数据。
4. 示例
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
 
public class DesUtil {
    private static Key key;
    private static String KEY_STR="12345678";
    private static String CHARSETNAME="UTF-8";
    private static String ALGORITHM="DES";
    
    static {
        try {
            //生成DES算法对象
            KeyGenerator generator=KeyGenerator.getInstance(ALGORITHM);
            //运用SHA1安全策略
            SecureRandom secureRandom=SecureRandom.getInstance("SHA1PRNG");
            //设置上密钥种子
            secureRandom.setSeed(KEY_STR.getBytes());
            //初始化基于SHA1的算法对象
            generator.init(secureRandom);
            //生成密钥对象
            key=generator.generateKey();
            generator=null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /***
     * 获取加密的信息
     * @param str
     * @return
     */
    public static String getEncryptString(String str) {
        //基于BASE64编码,接收byte[]并转换成String
        BASE64Encoder encoder = new BASE64Encoder();
        try {
            //按utf8编码
            byte[] bytes = str.getBytes(CHARSETNAME);
            //获取加密对象
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            //初始化密码信息
            cipher.init(Cipher.ENCRYPT_MODE, key);
            //加密
            byte[] doFinal = cipher.doFinal(bytes);
            //byte[]to encode好的String 并返回
            return encoder.encode(doFinal);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /***
     * 获取解密之后的信息
     * @param str
     * @return
     */
    public static String getDecryptString(String str) {
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            //将字符串decode成byte[]
            byte[] bytes = decoder.decodeBuffer(str);
            //获取解密对象
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            //初始化解密信息
            cipher.init(Cipher.DECRYPT_MODE, key);
            //解密
            byte[] doFial = cipher.doFinal(bytes);
            return new String(doFial, CHARSETNAME);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } 
}

3DES对称加密/解密

1. 原理
  • 3DES是一种应用三重数据加密算法对数据进行加密的方法,它通过三次应用DES算法来提高安全性。
  • 3DES是DES算法的一种改进版本,旨在提高安全性。它使用三个不同的密钥对明文进行三次DES加密操作。具体来说,3DES可以采用两种模式:加密-解密-加密(EDE)模式和加密-加密-解密(EEE)模式。其中,EDE模式更为常用。
  • 在EDE模式下,首先使用第一个密钥对明文进行DES加密;然后使用第二个密钥对加密后的结果进行DES解密;最后使用第三个密钥再次对解密后的结果进行DES加密。这样,通过增加密钥数量和加密轮数,3DES提高了算法的安全性和复杂性。
2. 特点
  • 安全性高: 由于使用了三个密钥和三轮加密操作,3DES算法的安全性相对于DES算法有了显著的提升。它提供了更高的密钥长度和更复杂的加密过程,使得破解更加困难。
  • 加密速度较慢: 与DES算法相比,3DES算法的加密和解密速度较慢。这是因为它需要进行三轮加密操作,每轮操作都需要进行复杂的替换、置换和异或等计算。
  • 秘钥管理较复杂: 由于使用了三个密钥,3DES算法的密钥管理相对复杂。需要确保三个密钥的安全性和独立性,以防止密钥泄露和攻击。
3. 示例
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class DES3Util{
    /**
     * 生成3DES密钥
     */
    public static SecretKey generateTripleDESKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
        keyGenerator.init(168); // 3DES密钥长度通常是168位,但实际上会使用192位(24字节),最后24位作为奇偶校验位
        SecretKey secretKey = keyGenerator.generateKey();
        return secretKey;
    }

    /**
     * 加密方法
     */
    public static String encrypt(String data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("DESede");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密方法
     */
    public static String decrypt(String data, SecretKey key) throws Exception {
        byte[] decodedBytes = Base64.getDecoder().decode(data);
        Cipher cipher = Cipher.getInstance("DESede");
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

AES对称加密/解密

1. 简介
  • AES就是 DES 的增强版,比 DES 的加密强度更高。与 DES 一样,一共有四种加密模式:电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB)。
  • AES支持多种密钥长度,最常见的是128位、192位和256位。密钥长度越长,加密强度越高,相应地,计算资源消耗也会增加。在实际应用中,通常需要根据数据的重要性和安全需求选择合适的密钥长度。
2. 原理
  • AES算法通过多轮次的置换-置换网络(SPN)结构来实现加密过程。每轮操作包括字节替换、行移位、列混合和添加轮密钥四个步骤。这些步骤的组合使得AES算法能够有效地混淆和扩散输入数据,从而生成难以破解的密文。
  • 解密过程是加密过程的逆操作,通过相反的顺序执行逆字节替换、逆行移位、逆列混合和添加轮密钥等步骤来还原原始数据。
3. 加密过程

AES的加密过程包括多个轮次的处理,每个轮次都包含以下四个步骤,经过多轮处理后,算法输出加密后的密文数据。

    1. 字节替换: 算法使用一个称为S盒的固定置换表来替换输入数据的每个字节。S盒是一个非线性置换,它增加了数据的混淆程度,使得加密过程更加难以预测。
    1. 行移位: 行移位操作将数据块中的每一行进行循环左移。不同行的移动距离不同,这有助于在加密过程中进一步扩散数据。
    1. 列混合: 算法使用一个固定的矩阵与数据块的每一列进行矩阵乘法运算。这个操作进一步混淆了数据,并增强了加密过程的非线性。然而,在最后一轮加密中省略了这一步,以简化解密过程。
    1. 轮密钥加: 算法将当前轮次的轮密钥与数据块进行异或运算。这个操作将密钥信息融入到加密过程中,确保了每轮加密都使用不同的密钥。

AES的解密过程是加密过程的逆操作。它首先使用与加密过程相同的密钥扩展算法生成轮密钥。然后,从最后一轮开始逆向执行解密操作,包括逆行移位、逆字节替换、逆列混合(除第一轮外)和轮密钥加等步骤。最终,解密过程输出原始的明文数据。

4. AES的安全注意事项
  • 密钥管理: 保护好密钥是至关重要的。泄露密钥将导致加密数据的安全性受到威胁。因此,需要采取适当的措施来存储、传输和销毁密钥。
  • 模式选择: 选择合适的加密模式对于确保数据的安全性至关重要。不同的模式适用于不同的场景,需要根据具体需求进行选择。
  • 侧信道攻击: 除了直接破解密文外,攻击者还可能通过侧信道攻击(如时间分析、功耗分析等)来获取密钥信息。因此,在实现AES算法时,需要注意防止这类攻击。
5. 示例
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
public class AESUtil {
    public static final String algorithm = "AES";
    // AES/CBC/NOPaddin
    // AES 默认模式
    // 使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new
    // IvParameterSpec(key.getBytes());
    // NOPadding: 使用NOPadding模式时, 原文长度必须是8byte的整数倍
    public static final String transformation = "AES/CBC/NOPadding";
    public static final String key = "1234567812345678";
 
    /***
     * 加密
     * @param original 需要加密的参数(注意必须是16位)
     * @return
     * @throws Exception
     */
    public static String encryptByAES(String original) throws Exception {
        // 获取Cipher
        Cipher cipher = Cipher.getInstance(transformation);
        // 生成密钥
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
        // 指定模式(加密)和密钥
        // 创建初始化向量
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
        // cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        // 加密
        byte[] bytes = cipher.doFinal(original.getBytes());
        return Base64Util.encryptBASE64(bytes);
    }
 
    /**
     * 解密
     * @param encrypted 需要解密的参数
     * @return
     * @throws Exception
     */
    public static String decryptByAES(String encrypted) throws Exception {
        // 获取Cipher
        Cipher cipher = Cipher.getInstance(transformation);
        // 生成密钥
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
        // 指定模式(解密)和密钥
        // 创建初始化向量
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
        // cipher.init(Cipher.DECRYPT_MODE, keySpec);
        // 解密
        byte[] bytes = cipher.doFinal(Base64Util.decryBASE64(encrypted));
        return new String(bytes);
    }
}

RSA非对称加密/解密

1. 简介
  • RSA是一种应用比较广泛的非对称加密算法。秘钥分为公钥和私钥。公钥是可以发布的,供任何人使用,私钥则为自己所有,供解密之用。
2.原理
  • RSA基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即为公钥,而两个大素数组合成为私钥。
3. 过程
秘钥生成过程
  • 随机找两个质数P和Q,P与Q越大,越安全。(例如:61和53)
  • 计算P和Q的乘积n。(n=61*53=3233,n的长度就是密钥长度。3233写成二进制是110010100001,一共有12位,所以这个密钥就是12位。)
  • 计算n的欧拉函数φ(n)。(根据公式 φ(n) = (p-1)(q-1) 算出 φ(3233) = 60 * 52,即3120。)
  • 随机选择一个整数e,条件是1<e<φ(n),且e与φ(n) 互质。(条件是1<e<φ(n),且e与φ(n) 互质。1到3120之间,随机选择了17。)
  • 有一个整数d,可以使得ed 除以φ(n) 的余数为 1。(ed ≡ 1 (mod φ(n)),即17*2753 mode 3120=1)
  • 将n和e封装成公钥,n和d封装成私钥。(n=3233,e=17,d=2753,所以公钥就是:3233,17,私钥就是:3233, 2753。)
加密过程
  • 秘钥生成: 首先需要生成一对密钥,这对密钥包括一个公钥和一个私钥。公钥是公开的,可以分发给任何人,而私钥必须保密,只有信息的接收方自己知道。
  • 信息加密: 发送方使用接收方提供的公钥对信息进行加密。加密过程可以用数学公式表示为:c = m^e mod n,其中c是密文,m是明文,e是公钥指数,n是模数。
解密过程
  • 信息解密: 接收方收到密文后,使用自己的私钥对密文进行解密。解密过程可以用数学公式表示为:m = c^d mod n,其中m是恢复出的明文,c是密文,d是私钥指数,n是模数。
优缺点
  • 优点: RSA算法是国际标准算法,属于主流算法之一,应用广泛,兼容性比较广,能够适用于各种不同的系统之中,不容易出现限制问题。
  • 缺点: RSA算法加密长度为2048位,对于服务器的消耗是比较大的,计算速度也比较慢,效率偏低,一般只适用于处理小量数据。

尽管RSA加密算法运行消耗大,效率低,但是由于其优秀兼容性和安全性,它依旧是使用最广泛的非对称加密算法。

4. 步骤
  1. 初始化密钥对。
  2. 获取公钥字符串: 初始化后的密钥对 -> 取公钥 -> Base64编码 -> 公钥字符串。
  3. 生成私钥字符串: 初始化后的密钥对 -> 取私钥 -> Base64编码 -> 私钥字符串。
  4. 公钥加密: 明文 -> 获取公钥(公钥字符串 -> Base64解码 -> 公钥) -> 公钥加密 -> Hex编码 -> 密文。
  5. 私钥解密: 密文 -> 获取私钥(私钥字符串 -> Base64解码 -> 私钥) -> 私钥解密 -> Hex解码 -> 明文。
5. 示例
import com.sun.org.apache.xml.internal.security.utils.Base64;
import javax.crypto.Cipher;
 
import org.apache.commons.io.FileUtils;
 
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
 
public class RsaUtil {
 
    /**
     * 生成密钥对并保存在本地文件中
     *
     * @param algorithm : 算法
     * @param pubPath   : 公钥保存路径
     * @param priPath   : 私钥保存路径
     * @throws Exception
     */
    private static void generateKeyToFile(String algorithm, String pubPath, String priPath) throws Exception {
        // 获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥
        PublicKey publicKey = keyPair.getPublic();
        // 获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 获取byte数组
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 进行Base64编码
        String publicKeyString = Base64.encode(publicKeyEncoded);
        String privateKeyString = Base64.encode(privateKeyEncoded);
        // 保存文件
        FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.forName("UTF-8"));
        FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.forName("UTF-8"));
 
    }
 
    /**
     * 从文件中加载公钥
     *
     * @param algorithm : 算法
     * @param filePath  : 文件路径
     * @return : 公钥
     * @throws Exception
     */
    private static PublicKey loadPublicKeyFromFile(String algorithm, String filePath) throws Exception {
        // 将文件内容转为字符串
        String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
 
        return loadPublicKeyFromString(algorithm, keyString);
 
    }
 
    /**
     * 从字符串中加载公钥
     *
     * @param algorithm : 算法
     * @param keyString : 公钥字符串
     * @return : 公钥
     * @throws Exception
     */
    private static PublicKey loadPublicKeyFromString(String algorithm, String keyString) throws Exception {
        // 进行Base64解码
        byte[] decode = Base64.decode(keyString);
        // 获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 构建密钥规范
        X509EncodedKeySpec keyspec = new X509EncodedKeySpec(decode);
        // 获取公钥
        return keyFactory.generatePublic(keyspec);
 
    }
 
    /**
     * 从文件中加载私钥
     *
     * @param algorithm : 算法
     * @param filePath  : 文件路径
     * @return : 私钥
     * @throws Exception
     */
    private static PrivateKey loadPrivateKeyFromFile(String algorithm, String filePath) throws Exception {
        // 将文件内容转为字符串
        String keyString = FileUtils.readFileToString(new File(filePath), Charset.forName("UTF-8"));
        return loadPrivateKeyFromString(algorithm, keyString);
 
    }
 
    /**
     * 从字符串中加载私钥
     *
     * @param algorithm : 算法
     * @param keyString : 私钥字符串
     * @return : 私钥
     * @throws Exception
     */
    private static PrivateKey loadPrivateKeyFromString(String algorithm, String keyString) throws Exception {
        // 进行Base64解码
        byte[] decode = Base64.decode(keyString);
        // 获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 构建密钥规范
        PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(decode);
        // 生成私钥
        return keyFactory.generatePrivate(keyspec);
 
    }
 
    /**
     * 使用密钥加密数据
     *
     * @param algorithm      : 算法
     * @param input          : 原文
     * @param key            : 密钥
     * @param maxEncryptSize : 最大加密长度(需要根据实际情况进行调整)
     * @return : 密文
     * @throws Exception
     */
    private static String encrypt(String algorithm, String input, Key key, int maxEncryptSize) throws Exception {
        // 获取Cipher对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 初始化模式(加密)和密钥
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // 将原文转为byte数组
        byte[] data = input.getBytes();
        // 总数据长度
        int total = data.length;
        // 输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        decodeByte(maxEncryptSize, cipher, data, total, baos);
        // 对密文进行Base64编码
        return Base64.encode(baos.toByteArray());
 
    }
 
    /**
     * 解密数据
     *
     * @param algorithm      : 算法
     * @param encrypted      : 密文
     * @param key            : 密钥
     * @param maxDecryptSize : 最大解密长度(需要根据实际情况进行调整)
     * @return : 原文
     * @throws Exception
     */
    private static String decrypt(String algorithm, String encrypted, Key key, int maxDecryptSize) throws Exception {
        // 获取Cipher对象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 初始化模式(解密)和密钥
        cipher.init(Cipher.DECRYPT_MODE, key);
        // 由于密文进行了Base64编码, 在这里需要进行解码
        byte[] data = Base64.decode(encrypted);
        // 总数据长度
        int total = data.length;
        // 输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
        decodeByte(maxDecryptSize, cipher, data, total, baos);
        // 输出原文
        return baos.toString();
 
    }
 
    /**
     * 分段处理数据
     *
     * @param maxSize : 最大处理能力
     * @param cipher  : Cipher对象
     * @param data    : 要处理的byte数组
     * @param total   : 总数据长度
     * @param baos    : 输出流
     * @throws Exception
     */
    private static void decodeByte(int maxSize, Cipher cipher, byte[] data, int total, ByteArrayOutputStream baos) throws Exception {
        // 偏移量
        int offset = 0;
        // 缓冲区
        byte[] buffer;
        // 如果数据没有处理完, 就一直继续
        while (total - offset > 0) {
            // 如果剩余的数据 >= 最大处理能力, 就按照最大处理能力来加密数据
            if (total - offset >= maxSize) {
                // 加密数据
                buffer = cipher.doFinal(data, offset, maxSize);
                // 偏移量向右侧偏移最大数据能力个
                offset += maxSize;
            } else {
                // 如果剩余的数据 < 最大处理能力, 就按照剩余的个数来加密数据
                buffer = cipher.doFinal(data, offset, total - offset);
                // 偏移量设置为总数据长度, 这样可以跳出循环
                offset = total;
            }
            // 向输出流写入数据
            baos.write(buffer);
        }
    }
}

AES+RSA混合加密

用AES的密钥来加密数据,用RSA来加密传输AES的密钥。

1. 混合加密原因
  • 单纯的使用 RSA(非对称加密)方式,效率会很低,因为非对称加密解密方式虽然很保险,但是过程复杂,耗费时间长,性能不高;
  • RSA 优势在于数据传输安全,且对于几个字节的数据,加密和解密时间基本可以忽略,所以用它非常适合加密 AES 秘钥(一般16个字节);
  • 单纯的使用 AES(对称加密)方式的话,非常不安全。这种方式使用的密钥是一个固定的密钥,客户端和服务端是一样的,一旦密钥被人获取,那么,我们所发的每一条数据都会被都对方破解;
  • AES有个很大的优点,那就是加密解密效率很高,而我们传输正文数据时,正好需要这种加解密效率高的,所以这种方式适合用于传输量大的数据内容;
2. 构思的基本流程
  • 前端页面与服务器端的秘钥交换。
    在这里插入图片描述
  • 交换秘钥后的数据交互
    在这里插入图片描述
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值