2024-03-21
原文作者:小小工匠 原文地址: https://artisan.blog.csdn.net/article/details/136385385

202403212042098761.png


概述

对称加密算法是一种加密技术,使用相同的密钥来进行加密和解密数据。在这种算法中,发送方使用密钥将明文(未加密的数据)转换为密文(加密的数据),而接收方使用相同的密钥将密文还原为明文。

对称加密算法的安全性依赖于密钥的保密性,因为任何持有相同密钥的人都能够解密数据。

常见的对称加密算法包括AES(高级加密标准)和DES(数据加密标准)。虽然对称加密算法在性能上通常比非对称加密算法更高效,但在密钥管理和分发方面存在挑战。

通俗来讲,可以这么理解: 对称加密算法就像是一把钥匙可以打开一个锁。在这里,你有一个钥匙(密钥),用它来锁住(加密)你的信息,然后你可以用同样的钥匙(密钥)来解锁(解密)它。这意味着发送方和接收方都使用相同的密钥来加密和解密信息。


常用的对称加密算法

常用的对称加密算法包括:

  1. AES(Advanced Encryption Standard):这是目前最常用的对称加密算法之一。它使用128、192或256位密钥来加密数据,并已被广泛采用于许多安全应用中。
  2. DES(Data Encryption Standard):虽然已被AES所取代,但仍然在一些遗留系统中使用。DES使用56位密钥对数据进行加密。
  3. 3DES(Triple Data Encryption Standard):3DES是DES的改进版本,它对数据应用三次DES算法,提高了安全性。但由于计算成本高昂,现在已经不太常用。
  4. Blowfish:这是一个可扩展的对称加密算法,可以使用变长密钥,从32位到448位。它曾经很流行,但由于一些安全性方面的考虑,现在使用较少。
  5. RC4(Rivest Cipher 4):尽管曾被广泛使用,但由于存在一些严重的安全漏洞,现在已经不建议使用。

从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:

    secret = encrypt(key, message);

而解密则相反,它接收密码和密文,然后输出明文:

    plain = decrypt(key, secret);

从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:

    secret = encrypt(key, message);

而解密则相反,它接收密码和密文,然后输出明文:

    plain = decrypt(key, secret);

加密和解密确实可以被视为类似上面的函数,但实际上,它们可能会更为复杂一些,尤其是在实现对称加密算法时。

在使用对称加密算法时,这两个函数通常被称为加密函数和解密函数。例如,在使用AES算法时,加密函数会接收密钥(key)和明文(message),然后输出密文(ciphertext)。而解密函数则接收密钥(key)和密文(ciphertext),然后输出明文(message)

算法 密钥长度 工作模式 填充模式
AES 128/192/256 ECB/CBC/PCBC/CTR/… NoPadding/PKCS5Padding/PKCS7Padding/…
DES 56/64 ECB/CBC/PCBC/CTR/… NoPadding/PKCS5Padding/…

密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。


这些对称加密算法在不同的场景中都有各自的优缺点,选择合适的算法取决于安全性需求、性能和应用环境。 AES通常被认为是最安全和高效的对称加密算法之一,因此在许多情况下被首选使用


AES

AES算法是目前应用最广泛的加密算法。

https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html

ECB模式

ECB : Electronic codebook, 电子密码本. 需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密

202403212042107072.png

CODE

先用ECB模式加密并解密. 代码如下

    package com.artisan.securityalgjava.aes;
    
    import java.security.*;
    import java.util.Base64;
    
    import javax.crypto.*;
    import javax.crypto.spec.*;
    
    /**
     * @author 小工匠
     * @version 1.0
     * @mark: show me the code , change the world
     */
    public class AesECBExample {
    
        public static void main(String[] args) throws Exception {
            // 原文:
            String message = "Hello, Artisan!";
            System.out.println("Message: " + message);
    
            // 128位密钥 = 16 bytes Key:
            byte[] key = "1234567890abcdef".getBytes("UTF-8");
    
            // 加密:
            byte[] data = message.getBytes("UTF-8");
            byte[] encrypted = encrypt(key, data);
            System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
    
            // 解密:
            byte[] decrypted = decrypt(key, encrypted);
            System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
        }
    
        // 加密方法
        public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
            // 创建加密对象
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            // 创建密钥规范
            SecretKey keySpec = new SecretKeySpec(key, "AES");
            // 初始化加密对象
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            // 执行加密操作
            return cipher.doFinal(input);
        }
    
        // 解密方法
        public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
            // 创建解密对象
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            // 创建密钥规范
            SecretKey keySpec = new SecretKeySpec(key, "AES");
            // 初始化解密对象
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            // 执行解密操作
            return cipher.doFinal(input);
        }
    }

ECB模式是最简单的AES加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低

  • 优点 : 可以并行处理数据
  • 缺点 : 同样的原文生成同样的密文, 不能很好的保护数据
  • 同时加密,原文是一样的,加密出来的密文也是一样的

CBC模式 (推荐)

CBC : Cipher-block chaining, 密码块链接. 每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块.

202403212042111033.png

CODE

    package com.artisan.securityalgjava.aes;
    
    import java.security.*;
    import java.util.Base64;
    
    import javax.crypto.*;
    import javax.crypto.spec.*;
    
    /**
     * @author 小工匠
     * @version 1.0
     * @mark: show me the code , change the world
     * @desc: AES CBC模式加解密示例
     */
    public class AesCBCExample {
    
        public static void main(String[] args) throws Exception {
            // 原文:
            String message = "Hello, Artisan!";
            System.out.println("Message: " + message);
            // 256位密钥 = 32 bytes Key:
            byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
            // 加密:
            byte[] data = message.getBytes("UTF-8");
            byte[] encrypted = encrypt(key, data);
            System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
            // 解密:
            byte[] decrypted = decrypt(key, encrypted);
            System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
        }
    
        /**
         * 加密
         * @param key
         * @param input
         * @return
         * @throws GeneralSecurityException
         */
        public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
            // 创建AES加密器
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // 创建密钥规范
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            // CBC模式需要生成一个16 bytes的初始化向量
            SecureRandom sr = SecureRandom.getInstanceStrong();
            byte[] iv = sr.generateSeed(16);
            IvParameterSpec ivps = new IvParameterSpec(iv);
            // 初始化加密器
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
            // 执行加密操作
            byte[] data = cipher.doFinal(input);
            // IV不需要保密,把IV和密文一起返回
            return join(iv, data);
        }
    
        /**
         * 解密
         * @param key
         * @param input
         * @return
         * @throws GeneralSecurityException
         */
        public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
            // 把input分割成IV和密文
            byte[] iv = new byte[16];
            byte[] data = new byte[input.length - 16];
            System.arraycopy(input, 0, iv, 0, 16);
            System.arraycopy(input, 16, data, 0, data.length);
            // 创建AES解密器
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // 创建密钥规范
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            IvParameterSpec ivps = new IvParameterSpec(iv);
            // 初始化解密器
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
            // 执行解密操作
            return cipher.doFinal(data);
        }
    
        /**
         * 拼接两个字节数组
         * @param bs1
         * @param bs2
         * @return
         */
        public static byte[] join(byte[] bs1, byte[] bs2) {
            byte[] r = new byte[bs1.length + bs2.length];
            System.arraycopy(bs1, 0, r, 0, bs1.length);
            System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
            return r;
        }
    
    }

CBC模式下,需要一个随机生成的16字节IV参数,必须使用SecureRandom生成。因为多了一个IvParameterSpec实例,因此,初始化方法需要调用Cipher的一个重载方法并传入IvParameterSpec

观察输出,可以发现每次生成的IV不同,密文也不同, 如下图所示

202403212042115404.png

202403212042119545.png

202403212042123556.png

代码实现了AES CBC模式的加密和解密功能。在加密过程中,生成了一个16字节的初始化向量(IV),在解密时使用了这个IV来确保安全性。

因此,CBC模式,它需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同 .

  • 优点 : 同样的原文生成的密文不一样
  • 缺点 : 串行处理数据.

ECB VS CBC

AES有几种不同的模式,其中最常见的两种是ECB(Electronic Codebook)模式和CBC(Cipher Block Chaining)模式。

  1. ECB模式(电子密码本模式)
  • 特点

    • 将明文分成块,每个块使用相同的密钥进行加密。
    • 相同的明文块在加密后会得到相同的密文块。
    • 每个块的加密是独立的,不受其他块的影响。
  • 优点

    • 简单,容易并行化处理。
    • 适用于对称加密需求较简单的场景。
  • 缺点

    • 容易受到重放攻击的影响,因为相同的明文块会产生相同的密文块,没有隐藏明文块之间的关系。
    • 不适合加密大量数据或需要保护隐私的数据,因为无法隐藏明文块之间的模式。
  1. CBC模式(密码块链接模式)
  • 特点

    • 在加密前,会对明文块进行异或运算,并与前一个密文块进行混合,然后再加密。
    • 需要一个初始化向量(IV)来增加随机性,防止重放攻击。
    • 密文块的加密依赖于前一个密文块,因此密文块之间存在依赖关系。
  • 优点

    • 对于相同的明文块,使用不同的IV会产生不同的密文块,增加了安全性。
    • 可以加密大量数据,并且可以隐藏明文块之间的模式。
  • 缺点

    • 加密速度较ECB模式慢,因为需要依赖前一个密文块。
    • 不太容易并行化处理,因为每个块的加密都依赖于前一个块的加密结果。

综上所述, ECB模式简单快速,适合简单的加密需求,但安全性较差,不适合加密大量数据或需要保护隐私的数据。而CBC模式相对更安全,能够隐藏明文块之间的模式,适合加密大量数据或需要保护隐私的数据,但加密速度较慢


填充模式

填充模式(Padding)在加密算法中用于处理数据长度不符合块处理需求的情况。常见的填充模式有NoPadding和PKCS5Padding。

  • NoPadding :不填充。在这种模式下,如果原文长度不符合块处理需求,就会报错。例如,在DES加密算法下,要求原文长度必须是8字节的整数倍,在AES加密算法下,要求原文长度必须是16字节的整数倍。如果不满足这些条件,就会出现异常。
  • PKCS5Padding :在数据块大小为8位时,使用PKCS5Padding填充方式。如果原文长度不足8位,则在原文末尾填充相应数量的字节,使得原文长度等于8的整数倍。填充的字节值通常等于需要填充的数量,例如,如果原文长度不足8字节,则填充1个字节的值为0x01,如果原文长度不足16字节,则填充2个字节的值为0x02,以此类推。

加密模式和填充模式

    AES/CBC/NoPadding (128)
    AES/CBC/PKCS5Padding (128)
    AES/ECB/NoPadding (128)
    AES/ECB/PKCS5Padding (128)
    DES/CBC/NoPadding (56)
    DES/CBC/PKCS5Padding (56)
    DES/ECB/NoPadding (56)
    DES/ECB/PKCS5Padding (56)
    DESede/CBC/NoPadding (168)
    DESede/CBC/PKCS5Padding (168)
    DESede/ECB/NoPadding (168)
    DESede/ECB/PKCS5Padding (168)
    RSA/ECB/PKCS1Padding (1024, 2048)
    RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
    RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
  • 默认情况下, 加密模式和填充模式为 : ECB/PKCS5Padding
  • 如果使用CBC模式, 在初始化Cipher对象时, 需要增加参数, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());

附:AES工具类

    package com.artisan.securityalgjava.aes;
    
    import javax.crypto.Cipher;
    import javax.crypto.SecretKey;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.SecureRandom;
    import java.security.spec.KeySpec;
    import java.util.Arrays;
    import java.util.Base64;
    
    /**
     * @author 小工匠
     * @version 1.0
     * @mark: show me the code , change the world
     * @desc: AES加密解密工具类, 提供了AES加密解密的功能,使用了CBC模式和PBKDF2算法生成密钥
     */
    public class AESCipher {
    
        /**
         * 密钥生成算法
         */
        private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256";
        /**
         * 加密算法
         */
        private static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
        /**
         * 密钥长度
         */
        private static final int KEY_SIZE = 256;
        /**
         * 迭代次数
         */
        private static final int ITERATION_COUNT = 65536;
        /**
         * 初始化向量长度
         */
        private static final int IV_SIZE = 16;
    
        /**
         * 生成密钥
         *
         * @param password
         * @param salt
         * @return
         * @throws Exception
         */
        private static SecretKeySpec generateKey(String password, byte[] salt) throws Exception {
            SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_SIZE);
            SecretKey tmp = factory.generateSecret(spec);
            return new SecretKeySpec(tmp.getEncoded(), "AES");
        }
    
        /**
         * 加密
         *
         * @param plaintext
         * @param password
         * @return
         * @throws Exception
         */
        public static String encrypt(String plaintext, String password) throws Exception {
            // 生成盐
            byte[] salt = new byte[16];
            // 生成初始化向量
            byte[] iv = new byte[IV_SIZE];
            byte[] encryptedBytes;
    
            // 生成盐和初始化向量
            SecureRandom random = new SecureRandom();
            random.nextBytes(salt);
            random.nextBytes(iv);
    
            // 生成密钥
            SecretKeySpec keySpec = generateKey(password, salt);
            Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
    
            // 执行加密操作
            encryptedBytes = cipher.doFinal(plaintext.getBytes());
    
            // 将盐、初始化向量和密文拼接并返回Base64编码字符串
            byte[] combined = new byte[salt.length + iv.length + encryptedBytes.length];
            System.arraycopy(salt, 0, combined, 0, salt.length);
            System.arraycopy(iv, 0, combined, salt.length, iv.length);
            System.arraycopy(encryptedBytes, 0, combined, salt.length + iv.length, encryptedBytes.length);
    
            return Base64.getEncoder().encodeToString(combined);
        }
    
        /**
         * 解密
         *
         * @param encryptedText
         * @param password
         * @return
         * @throws Exception
         */
        public static String decrypt(String encryptedText, String password) throws Exception {
            // 解析Base64编码字符串并分离盐、初始化向量和密文
            byte[] combined = Base64.getDecoder().decode(encryptedText);
            byte[] salt = Arrays.copyOfRange(combined, 0, 16);
            byte[] iv = Arrays.copyOfRange(combined, 16, 32);
            byte[] encryptedBytes = Arrays.copyOfRange(combined, 32, combined.length);
    
            // 生成密钥
            SecretKeySpec keySpec = generateKey(password, salt);
            Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
    
            // 执行解密操作并返回明文字符串
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            return new String(decryptedBytes);
        }
    
        /**
         * 测试方法
         *
         * @param args
         * @throws Exception
         */
        public static void main(String[] args) throws Exception {
            // 测试加密解密功能
            String encrypted = encrypt("组热么共和国的啥范德萨", "dddd");
            System.out.println("Encrypted: " + encrypted);
            String decrypted = decrypt(encrypted, "dddd");
            System.out.println("Decrypted: " + decrypted);
        }
    }

总结

对称加密算法使用同一个密钥进行加密和解密,适用于需要高效加解密的场景。常见的对称加密算法包括DES、AES和3DES等。

对称加密算法使用同一个密钥进行加密和解密,常用的算法包括DES、AES和3DES等。

  • DES(Data Encryption Standard) :DES是一种较早的对称加密算法,使用56位密钥进行加密和解密。由于密钥长度较短,DES已经不再被推荐使用,因为它容易受到穷举攻击。
  • AES(Advanced Encryption Standard) :AES是目前广泛使用的对称加密算法之一。它使用128位、192位或256位密钥进行加密和解密。AES算法的安全性和性能较高,因此被广泛应用于各种安全领域。
  • 3DES(Triple Data Encryption Standard) :3DES是对DES算法的改进,通过对数据应用三次DES算法来提高安全性。3DES使用的密钥长度为56位,因此它的安全性较DES提高了很多。但由于AES的出现和3DES的计算复杂性,3DES的使用逐渐减少。

密钥长度由算法设计决定。对于AES算法,它支持的密钥长度为128位、192位和256位。一般来说,密钥长度越长,加密的安全性越高,但同时也带来了更高的计算成本。

在使用对称加密算法时,需要指定以下参数:

  • 算法名称 :即使用的加密算法,例如DES、AES和3DES等。
  • 工作模式 :指定了加密算法在加密大块数据时的工作模式,常见的工作模式包括ECB、CBC、CFB、OFB等。
  • 填充模式 :指定了在加密数据块大小不足时如何填充数据,常见的填充模式包括PKCS5Padding、NoPadding等。

选择合适的算法名称、工作模式和填充模式,可以根据具体的安全需求和性能要求进行调整。

202403212042128627.png

阅读全文