对称加解密

对称加解密

概述

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。

算法

本文介绍的对称加解密算法:SM4、AES、DESede、DES

Hutool 对称加解密算法可以直接参考提供的示例即可。

AES 算法

简介

高级加密标准 (AES,Advanced Encryption Standard) 为最常见的对称加密算法,相对 DES 更安全。AES 加密算法采用分组密码体制,每个分组数据的长度为128位16个字节,密钥长度可以是128位16个字节、192位或256位,一共有四种加密模式,一般采用需要初始向量 IV 的 CBC 模式,初始向量的长度也是128位16个字节。

特别提醒:由于jdk对密钥长度支持受限制,使用256位密钥会抛 java.security.InvalidKeyException: Illegal key size or default parameters 异常,可以通过替换 jre 的jar包,具体方式可自行查询

  1. 分组密码体制

所谓分组密码体制就是指将明文切成一段一段的来加密,然后再把一段一段的密文拼起来形成最终密文的加密方式。AES 采用分组密码体制,即 AES 加密会首先把明文切成一段一段的,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节了,就需要用 Padding 来把这段数据填满16个字节,然后分别对每段数据进行加密,最后再把每段加密数据拼起来形成最终的密文。

  1. Padding

填补 (Padding) 就是用来把不满16个字节的分组数据填满16个字节用的,它有三种模式 PKCS5、PKCS7 和 NOPADDING。

PKCS5 是指分组数据缺少几个字节,就在数据的末尾填充几个字节的几,比如缺少5个字节,就在末尾填充5个字节的5;

PKCS7 是指分组数据缺少几个字节,就在数据的末尾填充几个字节的0,比如缺少7个字节,就在末尾填充7个字节的0;

NoPadding 是指不需要填充,也就是说数据的发送方肯定会保证最后一段数据也正好是16个字节。

特别提醒:使用 Nopadding 要加密数据必须是16字节的倍数,否则抛异常

  1. 初始向量 IV

初始向量 IV 的作用是使加密更加安全可靠,我们使用 AES 加密时需要主动提供初始向量,而且只需要提供一个初始向量就够了,后面每段数据的加密向量都是前面一段的密文。初始向量 IV 的长度规定为128位16个字节,初始向量的来源为随机生成。至于为什么初始向量能使加密更安全可靠,会在下面的加密模式中提到。

  1. 密钥

AES 要求密钥的长度可以是128位16个字节、192位或者256位,位数越高,加密强度自然越大,但是加密的效率自然会低一些,因此要做好衡量。我们开发通常采用128位16个字节的密钥,我们使用 AES 加密时需要主动提供密钥,而且只需要提供一个密钥就够了,每段数据加密使用的都是这一个密钥,密钥来源为随机生成(我们开发时传入的那个为初始密钥,除了初始密钥以外,后面每一轮的密钥都是由上一轮的密钥扩展而来的,密钥扩展有四个步骤:排列、置换、与轮常量异或、生成下一轮密钥的其他列)。

  1. 加密模式

AES 一共有四种加密模式,分别是 ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB(密码反馈模式)、OFB(输出反馈模式) 我们一般使用的是 CBC 模式。

ECB 模式是最基本的加密模式,使用明文和密钥来加解密数据,相同的明文块会加密成相同的密文块;

其他模式区别不大,比如 CBC 模式比 ECB 模式多了一个初始向量 IV,加密的时候,第一个明文块会首先和初始向量 IV 做异或操作,然后再经过密钥加密,然后第一个密文块又会作为第二个明文块的加密向量来异或,依次类推下去,这样相同的明文块加密出的密文块就是不同的,明文的结构和密文的结构也将是不同的,因此更加安全。

小试牛刀

示例如下:

    /**
     * 加解密算法/加密模式/填充方式
     */
    private static final String ALGORITHM_ALL = "AES/CBC/PKCS7Padding";

    /**
     * 初始向量必须是16字节
     */
    private final static String IV_PARAMETER = "1234567812345678";

    private static final String CHARSET = "UTF-8";

    public static void main(String[] args) {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        String data = "AES demo";
        Key key = createKey();
        byte[] encrytData = encrypt(data, key);
        String encodeBase64String = Base64.encodeBase64String(encrytData);
        System.out.println("密文Base64 = " + encodeBase64String);
        byte[] decryData = decrypt(encrytData, key);
        System.out.println("明文 = " + new String(decryData));
    }

    /**
     * 加密
     *
     * @param data 明文
     * @param key  密钥
     * @return 返回密文字符数组
     */
    private static byte[] encrypt(String data, Key key) {
        try {
            //初始向量,若是 ECB 模式则不需要初始向量
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            Cipher cipher = Cipher.getInstance(ALGORITHM_ALL);
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            return cipher.doFinal(data.getBytes());
        } catch (Exception e) {
            log.error("加密异常", e);
        }
        return null;
    }

    /**
     * 解密
     *
     * @param result 密文字符数组
     * @param key    密钥
     * @return 返回明文字符数组
     */
    private static byte[] decrypt(byte[] result, Key key) {
        try {
            IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
            Cipher cipher = Cipher.getInstance(ALGORITHM_ALL);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            return cipher.doFinal(result);
        } catch (Exception e) {
            log.error("解密异常", e);
        }
        return null;
    }

    /**
     * 生成密钥
     *
     * @return 返回结果
     */
    private static Key createKey() {
        try {
            // 生成key
            KeyGenerator keyGenerator;
            // 构造密钥生成器,指定为AES算法
            keyGenerator = KeyGenerator.getInstance("AES");
            // 生成一个128位的随机源,根据传入的字节数组
            keyGenerator.init(128);
            // 产生原始对称密钥
            SecretKey secretKey = keyGenerator.generateKey();
            // 获得原始对称密钥的字节数组
            byte[] keyBytes = secretKey.getEncoded();
            // key转换,根据字节数组生成AES密钥
            return new SecretKeySpec(keyBytes, "AES");
        } catch (NoSuchAlgorithmException e) {
            log.error("生成密钥异常", e);
        }
        return null;
    }

DES 算法

简介

DES 全称 Data Encryption Standard,是一种使用密钥加密的块算法。现在认为是一种不安全的加密算法,因为现在已经有用穷举法攻破 DES 密码的报道了。尽管如此,该加密算法还是运用非常普遍,是一种标准的加密算法,3DES 即 DESede 是 DES 的加强版本。

DES 加密算法出自 IBM 的研究,后来被美国政府正式采用,之后开始广泛流传,但是近些年使用越来越少,因为 DES 使用56位密钥(密钥长度越长越安全),以现代计算能力24小时内即可被破解。虽然如此,在某些简单应用中,我们还是可以使用 DES 加密算法。

加密原理:

DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行"异或"运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。

AES 加密算法中的 Padding、加密模式、初始向量也适用 DES。

小试牛刀

示例如下:

     /**
     * 加解密算法/加密模式/填充方式
     */
    private static final String CIPHER_ALGORITHM = "DES/ECB/PKCS7Padding";

    private static final String ALGORITHM = "DES";

    private static final String CHARSET = "utf-8";

    public static void main(String[] args) {
        //关键点:初始化BC
        Security.addProvider(new BouncyCastleProvider());
        String text = "DES demo";
        String password = "12345678";
        String encrypt = encrypt(password, text);
        System.out.println(encrypt);
        String decrypt = decrypt(password, encrypt);
        System.out.println(decrypt);
    }

    private static Key generateKey(String password) throws Exception {
        DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        return keyFactory.generateSecret(dks);
    }

    private static String encrypt(String password, String data) {
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            //使用 ECB 模式不需要 初始向量
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] bytes = cipher.doFinal(data.getBytes(CHARSET));
            return Base64.encodeBase64String(bytes);

        } catch (Exception e) {
           log.error("加密异常", e);
        }
        return data;
    }

    private static String decrypt(String password, String data) {
        try {
            Key secretKey = generateKey(password);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            return new String(cipher.doFinal(Base64.decodeBase64(data)), CHARSET);
        } catch (Exception e) {
            log.error("解密异常", e);
        }
        return data;
    }

DESede 算法

简介

DESede 算法也就是 3DES(或称为 Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。它相当于是对每个数据块应用三次 DES 加密算法。由于计算机运算能力的增强,原版 DES 密码的密钥长度变得容易被暴力破解;3DES 即是设计用来提供一种相对简单的方法,即通过增加 DES 的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。

3DES 是 DES 向 AES 过渡的加密算法,它使用3条56位的密钥对数据进行三次加密,所以 3DES 的密钥为128位24字节,是 DES 的一个更安全的变形。相比DES,3DES因密钥长度变长,安全性有所提高,但其处理速度不高。因此又出现了 AES 加密算法,AES较于 3DES 速度更快、安全性更高。

小试牛刀

示例如下:

private static final String ALGORITHM = "DESede";

    private static byte[] encrypt(byte[] keybyte, byte[] src) {
        try {
            SecretKey deskey = new SecretKeySpec(keybyte, ALGORITHM);
            Cipher c1 = Cipher.getInstance(ALGORITHM);
            c1.init(Cipher.ENCRYPT_MODE, deskey);
            return c1.doFinal(src);
        } catch (Exception e) {
           log.error("加密异常", e);
        }
        return null;
    }

    private static byte[] decrypt(byte[] keybyte, byte[] src) {
        try {
            SecretKey deskey = new SecretKeySpec(keybyte, ALGORITHM);
            Cipher c1 = Cipher.getInstance(ALGORITHM);
            c1.init(Cipher.DECRYPT_MODE, deskey);
            return c1.doFinal(src);
        } catch (Exception e) {
            log.error("解密异常", e);
        }
        return null;
    }

    public static void main(String[] args) {
        // 添加新安全算法,如果用JCE就要把它添加进去
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        String key = "123456781234567812345678";

        byte[] keyBytes = key.getBytes();

        String szSrc = "DESede demo";
        byte[] encryData = encrypt(keyBytes, szSrc.getBytes());

        String s = Base64.encodeBase64String(encryData);
        System.out.println("密文: " + s);

        byte[] decryptData = decrypt(keyBytes, encryData);
        System.out.println("明文:" + (new String(decryptData)));
    }

SM4 算法

简介

SM4 属于对称加解密算法,是在2021年已经被国家商用密码管理局确定为管家密码行业标准,标准编号 GM/T 0002-2021,在国内广泛使用在 WAP 无线网络标准中,还有政府系统的数据传输加密,是一种32轮的迭代非平衡 Feiste 结构的分组加密算法,其密钥长度和分组长度均为128位。

小试牛刀

示例如下:

private static final String ALGORITHM_NAME_ECB_PADDING = "SM4/CBC/PKCS5Padding";
    static final String INITIAL_VECTOR = "905EF8C3C12C3087";
    private static final String ALGORIGTHM_NAME = "SM4";
    private static final int DEFAULT_KEY_SIZE = 128;
    private static final String ENCODING = "UTF-8";
    
    public static void main(String[] args) throws IOException {
        Security.addProvider(new BouncyCastleProvider());
        try {
            String text = "SM4 demo";
            byte[] bytes = generateKey();
            System.out.println("密钥:" + Base64.encodeBase64String(bytes));

            byte[] encrypt = encrypt(bytes, text.getBytes());
            System.out.println("密文:" + Base64.encodeBase64String(encrypt));

            byte[] decrypt = decrypt(bytes, encrypt);
            System.out.println("明文:" + new String(decrypt, ENCODING));
        }catch (Exception e) {
            log.error("加解密异常", e);
        }
    }
     private static byte[] generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORIGTHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        keyGenerator.init(DEFAULT_KEY_SIZE, new SecureRandom());
        SecretKey secretKey = keyGenerator.generateKey();
        return secretKey.getEncoded();
    }

    private static byte[] encrypt(byte[] key, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING,BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
        IvParameterSpec iv = new IvParameterSpec(INITIAL_VECTOR.getBytes(ENCODING));
        cipher.init(Cipher.ENCRYPT_MODE, sm4Key, iv);
        return cipher.doFinal(data);
    }

    public static byte[] decrypt(byte[] key, byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING,BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
        IvParameterSpec iv = new IvParameterSpec(INITIAL_VECTOR.getBytes(ENCODING));
        cipher.init(Cipher.DECRYPT_MODE, sm4Key, iv);
        return cipher.doFinal(data);
    } 

优缺点

优点

  • 加密计算量小,速度比较快,实现比较简单

缺点

  • 需要确保密钥传输安全,一旦密钥泄露那么数据传输内容就会被轻易破解,所以密钥可以通过非对称加密方式传输;

场景

对称加密算法适合各种数据的加解密,通过设定的密钥对数据进行加解密,包括字符串,文件等,对于文件加解密使用对称加解密,因为非对称加解密速度比较慢,当然也可以将对称和非对称加解密结合,例如:使用非对称加解密算法对对称加解密算法的密钥进行加解密,文件本身使用对称加解密算法,需要考虑的是对称加解密算法的密钥存储方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值