欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

对称加解密

程序员文章站 2024-03-14 15:38:46
...

对称加解密

概述

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

算法

本文介绍的对称加解密算法: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);
    } 

优缺点

优点

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

缺点

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

场景

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