数据加密标准(DES)解析

概述

数据加密标准(Data Encryption Standard, DES)是1977年由美国国家标准局(NIST)采纳的对称密钥加密算法,作为首个公开的联邦信息处理标准(FIPS PUB 46)。DES采用64位分组 大小和56位有效密钥 长度(外加8位奇偶校验位),通过16轮Feistel网络结构实现数据加密。尽管已被AES取代,DES仍是理解现代密码学的基石。

历史意义

  • 首个公开的加密标准(1977-2005)
  • 推动了密码学研究的发展
  • 启发了3DES、AES等后续算法
  • 证明了Feistel网络的实际可行性

核心结构与特点

1. Feistel网络结构

复制代码
1. 64位明文输入
2. 初始置换(IP)
3. 分割为L0(左32位)和R0(右32位)
4. 16轮迭代处理:
   - 每轮:R_i = L_{i-1} ⊕ F(R_{i-1}, K_i)
   -        L_i = R_{i-1}
5. 合并R16和L16
6. 逆初始置换(IP⁻¹)
7. 输出64位密文

2. 密钥特性

特性 说明
有效长度 56位(64位含8位奇偶校验)
密钥调度 16轮子密钥生成算法
密钥空间 2⁵⁶ ≈ 7.2×10¹⁶种可能
密钥输入 64位(含8位校验位)
子密钥 16个48位轮密钥

3. 安全设计要素

  • 混淆:通过8个S盒实现非线性变换
  • 扩散:通过P盒置换实现比特扩散
  • 雪崩效应:1比特输入变化导致约50%输出比特改变
  • 抗差分分析:S盒精心设计抵抗差分攻击
  • 轮结构:16轮迭代确保充分混淆扩散

DES加解密详细过程

加密流程

复制代码
1. 64位明文输入
2. 初始置换IP
3. 分割为L0(左32位)和R0(右32位)
4. 16轮迭代:
   第1轮:R1 = L0 ⊕ F(R0, K1), L1 = R0
   第2轮:R2 = L1 ⊕ F(R1, K2), L2 = R1
   ...
   第16轮:R16 = L15 ⊕ F(R15, K16), L16 = R15
5. 合并R16和L16(注意:不交换最后结果)
6. 逆初始置换IP⁻¹
7. 输出64位密文

轮函数F核心处理

复制代码
1. 32位输入
2. 扩展置换E:32位→48位
3. 与48位轮密钥异或
4. S盒替换:48位→32位(8个S盒并行处理)
5. P盒置换:32位→32位
6. 32位输出

密钥调度算法

复制代码
1. 64位密钥输入
2. 置换选择PC-1:64位→56位(移除校验位)
3. 分割为C0(左28位)和D0(右28位)
4. 16轮处理:
   - 循环左移(1或2位,根据轮次)
   - 置换选择PC-2:56位→48位(生成轮密钥Ki)
5. 输出16个48位轮密钥(K1-K16)

Java实现(完整注释版)

java 复制代码
import java.security.SecureRandom;
import java.util.Arrays;

public class DESAlgorithm {
    private static final int BLOCK_SIZE = 8; // DES分组大小
    // 初始置换表 (IP)
    private static final int[] INITIAL_PERMUTATION = {
            58, 50, 42, 34, 26, 18, 10, 2,
            60, 52, 44, 36, 28, 20, 12, 4,
            62, 54, 46, 38, 30, 22, 14, 6,
            64, 56, 48, 40, 32, 24, 16, 8,
            57, 49, 41, 33, 25, 17, 9,  1,
            59, 51, 43, 35, 27, 19, 11, 3,
            61, 53, 45, 37, 29, 21, 13, 5,
            63, 55, 47, 39, 31, 23, 15, 7
    };

    // 逆初始置换表 (IP⁻¹)
    private static final int[] INVERSE_PERMUTATION = {
            40, 8, 48, 16, 56, 24, 64, 32,
            39, 7, 47, 15, 55, 23, 63, 31,
            38, 6, 46, 14, 54, 22, 62, 30,
            37, 5, 45, 13, 53, 21, 61, 29,
            36, 4, 44, 12, 52, 20, 60, 28,
            35, 3, 43, 11, 51, 19, 59, 27,
            34, 2, 42, 10, 50, 18, 58, 26,
            33, 1, 41, 9, 49, 17, 57, 25
    };

    // 扩展置换表 (E)
    private static final int[] EXPANSION_TABLE = {
            32,  1,  2,  3,  4,  5,
            4,  5,  6,  7,  8,  9,
            8,  9, 10, 11, 12, 13,
            12, 13, 14, 15, 16, 17,
            16, 17, 18, 19, 20, 21,
            20, 21, 22, 23, 24, 25,
            24, 25, 26, 27, 28, 29,
            28, 29, 30, 31, 32,  1
    };

    // DES标准的8个S盒定义(4行×16列)
    private static final int[][][] S_BOXES = {
            // S1
            {
                    {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
                    {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
                    {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
                    {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
            },
            // S2
            {
                    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
                    {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
                    {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
                    {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
            },
            // S3
            {
                    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
                    {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
                    {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
                    {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
            },
            // S4
            {
                    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
                    {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
                    {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
                    {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
            },
            // S5
            {
                    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
                    {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
                    {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
                    {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
            },
            // S6
            {
                    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
                    {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
                    {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
                    {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
            },
            // S7
            {
                    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
                    {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
                    {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
                    {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
            },
            // S8
            {
                    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
                    {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
                    {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
                    {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
            }
    };
    // P盒置换表
    private static final int[] P_BOX_PERMUTATION = {
            16,  7, 20, 21,
            29, 12, 28, 17,
            1, 15, 23, 26,
            5, 18, 31, 10,
            2,  8, 24, 14,
            32, 27,  3,  9,
            19, 13, 30,  6,
            22, 11,  4, 25
    };

    // 密钥置换表 (PC-1)
    private static final int[] PC1 = {
            57, 49, 41, 33, 25, 17, 9,
            1, 58, 50, 42, 34, 26, 18,
            10,  2, 59, 51, 43, 35, 27,
            19, 11,  3, 60, 52, 44, 36,
            63, 55, 47, 39, 31, 23, 15,
            7, 62, 54, 46, 38, 30, 22,
            14,  6, 61, 53, 45, 37, 29,
            21, 13,  5, 28, 20, 12,  4
    };

    // 轮移位表
    private static final int[] SHIFT_SCHEDULE = {
            1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
    };

    // 密钥压缩置换 (PC-2)
    private static final int[] PC2 = {
            14, 17, 11, 24,  1,  5,
            3, 28, 15,  6, 21, 10,
            23, 19, 12,  4, 26,  8,
            16,  7, 27, 20, 13,  2,
            41, 52, 31, 37, 47, 55,
            30, 40, 51, 45, 33, 48,
            44, 49, 39, 56, 34, 53,
            46, 42, 50, 36, 29, 32
    };

    /**
     * 执行位置换操作
     * @param input 输入位数组
     * @param table 置换表
     * @return 置换后的位数组
     */
    private static boolean[] permute(boolean[] input, int[] table) {
        boolean[] output = new boolean[table.length];
        for (int i = 0; i < table.length; i++) {
            output[i] = input[table[i] - 1];//注意减去1,因为数组从0开始
        }
        return output;
    }

    /**
     * 执行S盒替换 (DES核心非线性变换)
     * @param input 48位输入
     * @return 32位输出
     */
    private static boolean[] sBoxSubstitution(boolean[] input) {
        if (input.length != 48)
            throw new IllegalArgumentException("S盒输入必须为48位");

        boolean[] output = new boolean[32];

        for (int i = 0; i < 8; i++) {
            // 提取6位输入
            boolean[] bs = Arrays.copyOfRange(input, i*6, (i+1)*6);

            // 计算行号 (第1和6位)
            int row = (bs[0] ? 2 : 0) | (bs[5] ? 1 : 0);//b1和b6位组合

            // 计算列号 (中间4位)
            int col = 0;
            for (int j = 1; j <= 4; j++) {
                if (bs[j]) col |= (1 << (4 - j));//中间4位组合
            }

            // 从S盒获取4位输出
            int sBoxValue = S_BOXES[i][row][col];
            // 转换为4位布尔数组
            int outputPos = 0;
            for (int j = 0; j < 4; j++) {
                output[outputPos++] = ((sBoxValue >> (3 - j)) & 1) == 1;
            }
        }

        return output;
    }

    /**
     * 轮函数F
     * @param right 32位右半部分
     * @param roundKey 48位轮密钥
     * @return 32位输出
     */
    private static boolean[] fFunction(boolean[] right, boolean[] roundKey) {
        // 1. 扩展置换 (32→48位)
        boolean[] expanded = permute(right, EXPANSION_TABLE);

        // 2. 与轮密钥异或
        boolean[] xored = new boolean[48];
        for (int i = 0; i < 48; i++) {
            xored[i] = expanded[i] ^ roundKey[i];
        }

        // 3. S盒替换 (48→32位)
        boolean[] substituted = sBoxSubstitution(xored);

        // 4. P盒置换
        return permute(substituted, P_BOX_PERMUTATION);
    }

    /**
     * 密钥调度算法 (生成16轮子密钥)
     * @param key 64位密钥 (含8位奇偶校验)
     * @return 16个48位轮密钥
     */
    private static boolean[][] generateSubKeys(boolean[] key) {
        // 1. PC-1置换 (64→56位)
        boolean[] pc1 = permute(key, PC1);

        // 2. 分为左右28位
        boolean[] c0 = Arrays.copyOfRange(pc1, 0, 28);
        boolean[] d0 = Arrays.copyOfRange(pc1, 28, 56);

        boolean[][] subKeys = new boolean[16][48];

        for (int round = 0; round < 16; round++) {
            // 3. 循环左移
            int shift = SHIFT_SCHEDULE[round];
            c0 = rotateLeft(c0, shift);
            d0 = rotateLeft(d0, shift);

            // 4. 合并为56位
            boolean[] cd = new boolean[56];
            System.arraycopy(c0, 0, cd, 0, 28);
            System.arraycopy(d0, 0, cd, 28, 28);

            // 5. PC-2置换 (56→48位)
            subKeys[round] = permute(cd, PC2);
        }

        return subKeys;
    }

    /**
     * 循环左移
     * @param bits 输入位数组
     * @param shift 移位位数
     * @return 移位后位数组
     */
    private static boolean[] rotateLeft(boolean[] bits, int shift) {
        boolean[] result = new boolean[bits.length];
        for (int i = 0; i < bits.length; i++) {
            result[i] = bits[(i + shift) % bits.length];
        }
        return result;
    }

    /**
     * DES加密核心
     * @param block 64位明文分组
     * @param key 64位密钥
     * @return 64位密文分组
     */
    public static boolean[] desEncryptBlock(boolean[] block, boolean[] key) {
        // 1. 生成16轮子密钥
        boolean[][] subKeys = generateSubKeys(key);

        // 2. 初始置换IP
        boolean[] ip = permute(block, INITIAL_PERMUTATION);

        // 3. 分为左右32位
        boolean[] left = Arrays.copyOfRange(ip, 0, 32);
        boolean[] right = Arrays.copyOfRange(ip, 32, 64);

        // 4. 16轮Feistel迭代
        for (int round = 0; round < 16; round++) {
            boolean[] temp = right;
            boolean[] fResult = fFunction(right, subKeys[round]);

            // 左半部分与F函数结果异或
            boolean[] newRight = new boolean[32];
            for (int i = 0; i < 32; i++) {
                newRight[i] = left[i] ^ fResult[i];
            }

            // 更新左右部分
            left = temp;
            right = newRight;
        }

        // 5. 最终交换 (R16  L16)
        boolean[] rl = new boolean[64];
        System.arraycopy(right, 0, rl, 0, 32);
        System.arraycopy(left, 0, rl, 32, 32);

        // 6. 逆初始置换IP⁻¹
        return permute(rl, INVERSE_PERMUTATION);
    }

    /**
     * DES解密核心
     * @param block 64位密文分组
     * @param key 64位密钥
     * @return 64位明文分组
     */
    public static boolean[] desDecryptBlock(boolean[] block, boolean[] key) {
        // 生成子密钥
        boolean[][] subKeys = generateSubKeys(key);

        // 解密时逆序使用子密钥
        boolean[][] reverseKeys = new boolean[16][48];
        for (int i = 0; i < 16; i++) {
            reverseKeys[i] = subKeys[15 - i];
        }

        // 其余步骤与加密相同
        boolean[] ip = permute(block, INITIAL_PERMUTATION);
        boolean[] left = Arrays.copyOfRange(ip, 0, 32);
        boolean[] right = Arrays.copyOfRange(ip, 32, 64);

        for (int round = 0; round < 16; round++) {
            boolean[] temp = right;
            boolean[] fResult = fFunction(right, reverseKeys[round]);

            boolean[] newRight = new boolean[32];
            for (int i = 0; i < 32; i++) {
                newRight[i] = left[i] ^ fResult[i];
            }

            left = temp;
            right = newRight;
        }

        boolean[] rl = new boolean[64];
        System.arraycopy(right, 0, rl, 0, 32);
        System.arraycopy(left, 0, rl, 32, 32);

        return permute(rl, INVERSE_PERMUTATION);
    }

    /**
     * 字节数组转位数组
     * @param bytes 字节数组
     * @return 位数组
     */
    public static boolean[] bytesToBits(byte[] bytes) {
        boolean[] bits = new boolean[bytes.length * 8];
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < 8; j++) {
                bits[i * 8 + j] = ((bytes[i] >> (7 - j)) & 1) == 1;
            }
        }
        return bits;
    }

    /**
     * 位数组转字节数组
     * @param bits 位数组
     * @return 字节数组
     */
    public static byte[] bitsToBytes(boolean[] bits) {
        byte[] bytes = new byte[bits.length / 8];
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < 8; j++) {
                if (bits[i * 8 + j]) {
                    bytes[i] |= (1 << (7 - j));
                }
            }
        }
        return bytes;
    }

    public static byte[] generateRandomKey() {
        SecureRandom random = new SecureRandom();
        byte[] key = new byte[8]; // 64位密钥
        random.nextBytes(key);

        // 设置奇偶校验位(DES标准要求)
        for (int i = 0; i < 8; i++) {
            int parity = Integer.bitCount(key[i] & 0xFF) % 2;
            key[i] = (byte) ((key[i] & 0xFE) | (parity == 0 ? 1 : 0));
        }
        return key;
    }

    /**
     * PKCS#7填充
     * @param data 需要填充的数据
     * @param blockSize 块大小(DES为8字节)
     * @return 填充后的数据
     */
    public static byte[] pkcs7Pad(byte[] data, int blockSize) {
        int paddingLength = blockSize - (data.length % blockSize);
        byte[] padded = new byte[data.length + paddingLength];
        System.arraycopy(data, 0, padded, 0, data.length);

        // 填充字节的值等于填充长度
        Arrays.fill(padded, data.length, padded.length, (byte) paddingLength);

        return padded;
    }

    /**
     * PKCS#7去除填充
     * @param data 带填充的数据
     * @return 去除填充后的数据
     * @throws IllegalArgumentException 如果填充无效
     */
    public static byte[] pkcs7Unpad(byte[] data) {
        // 检查填充长度是否有效
        int paddingLength = data[data.length - 1] & 0xFF;
        if (paddingLength < 1 || paddingLength > data.length) {
            throw new IllegalArgumentException("无效的PKCS#7填充");
        }

        // 验证所有填充字节是否正确
        for (int i = data.length - paddingLength; i < data.length; i++) {
            if (data[i] != paddingLength) {
                throw new IllegalArgumentException("无效的PKCS#7填充");
            }
        }

        return Arrays.copyOfRange(data, 0, data.length - paddingLength);
    }

    /**
     * DES加密长文本
     * @param plaintext 明文
     * @param key 64位密钥(8字节)
     * @return 密文
     */
    public static byte[] encrypt(byte[] plaintext, byte[] key) {
        // 1. 应用PKCS#7填充
        byte[] padded = pkcs7Pad(plaintext, 8);

        // 2. 将密钥转换为位数组
        boolean[] keyBits = bytesToBits(key);

        // 3. 分组加密
        byte[] ciphertext = new byte[padded.length];

        for (int i = 0; i < padded.length; i += 8) {
            // 提取当前分组
            byte[] block = Arrays.copyOfRange(padded, i, i + 8);
            boolean[] blockBits = bytesToBits(block);

            // 加密分组
            boolean[] encryptedBits = desEncryptBlock(blockBits, keyBits);

            // 转换回字节并存储
            byte[] encryptedBlock = bitsToBytes(encryptedBits);
            System.arraycopy(encryptedBlock, 0, ciphertext, i, 8);
        }

        return ciphertext;
    }

    /**
     * DES解密长文本
     * @param ciphertext 密文
     * @param key 64位密钥(8字节)
     * @return 明文
     */
    public static byte[] decrypt(byte[] ciphertext, byte[] key) {
        // 1. 检查输入长度
        if (ciphertext.length % 8 != 0) {
            throw new IllegalArgumentException("密文长度必须是8的倍数");
        }

        // 2. 将密钥转换为位数组
        boolean[] keyBits = bytesToBits(key);

        // 3. 分组解密
        byte[] decryptedWithPadding = new byte[ciphertext.length];

        for (int i = 0; i < ciphertext.length; i += 8) {
            // 提取当前分组
            byte[] block = Arrays.copyOfRange(ciphertext, i, i + 8);
            boolean[] blockBits = bytesToBits(block);

            // 解密分组
            boolean[] decryptedBits = desDecryptBlock(blockBits, keyBits);

            // 转换回字节并存储
            byte[] decryptedBlock = bitsToBytes(decryptedBits);
            System.arraycopy(decryptedBlock, 0, decryptedWithPadding, i, 8);
        }

        // 4. 去除填充
        return pkcs7Unpad(decryptedWithPadding);
    }

}
  • 演示
java 复制代码
public class Main {
    public static void main(String[] args) {
        byte[] key=DESAlgorithm.generateRandomKey();//生成随机密钥
        String str="尽管DES已完成历史使命,其精巧的Feistel结构设计、S盒构造方法和密钥调度算法,仍为密码学研究者提供了宝贵的学习范例。理解DES的工作原理,是掌握现代密码学不可或缺的基础。";
        byte[] msg= str.getBytes();
        byte[] c = DESAlgorithm.encrypt(msg, key);//加密
        String encryptedText = new String(c);
        byte[] m = DESAlgorithm.decrypt(c,key);//解密
        String decryptedText = new String(m);
        System.out.println(encryptedText);
        System.out.println(decryptedText);
    }
}

DES安全分析与演进

安全漏洞

  1. 密钥长度不足:56位密钥易受暴力破解
  2. 弱密钥问题:某些密钥导致加密强度降低
  3. 半弱密钥:某些密钥对产生可逆加密
  4. 线性与差分分析:理论攻击方法复杂度为2⁴³

增强方案:3DES

复制代码
加密:C = E(K3, D(K2, E(K1, P)))
解密:P = D(K1, E(K2, D(K3, C)))

3DES Java实现

java 复制代码
public static byte[] tripleDESEncrypt(byte[] plaintext, byte[] key) throws Exception {
    if (key.length != 24) throw new IllegalArgumentException("3DES需要24字节密钥");
    
    byte[] key1 = Arrays.copyOfRange(key, 0, 8);
    byte[] key2 = Arrays.copyOfRange(key, 8, 16);
    byte[] key3 = Arrays.copyOfRange(key, 16, 24);
    
    // 加密:E(K3, D(K2, E(K1, P)))
    byte[] step1 = encryptWithJCE(plaintext, key1);
    byte[] step2 = decryptWithJCE(step1, key2);
    return encryptWithJCE(step2, key3);
}

DES与AES对比

特性 DES AES
密钥长度 56位 128/192/256位
分组大小 64位 128位
结构 Feistel网络 SP网络
轮数 16 10/12/14
安全性 已破解 当前安全
性能 较慢 更快
应用 遗留系统 现代标准

实际应用建议

  1. 避免使用纯DES:使用3DES或AES替代

  2. 选择安全模式

    java 复制代码
    // 推荐使用CBC或CTR模式
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  3. 密钥管理最佳实践

    java 复制代码
    // 使用安全随机数生成密钥
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256); // 256位密钥
    SecretKey secretKey = keyGen.generateKey();
  4. 完整性保护

    java 复制代码
    // 结合HMAC或使用认证加密模式
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

历史意义与总结

DES作为密码学发展史上的里程碑:

  1. 开创了公开密码算法的标准化时代
  2. 推动了分组密码设计理论研究
  3. 促进了密码分析技术的进步
  4. 为现代加密标准(如AES)奠定了基础

尽管DES已完成历史使命,其精巧的Feistel结构设计、S盒构造方法和密钥调度算法,仍为密码学研究者提供了宝贵的学习范例。理解DES的工作原理,是掌握现代密码学不可或缺的基础。

相关推荐
小马爱记录30 分钟前
sentinel规则持久化
java·spring cloud·sentinel
浩浩测试一下1 小时前
Authpf(OpenBSD)认证防火墙到ssh连接到SSH端口转发技术栈 与渗透网络安全的关联 (RED Team Technique )
网络·网络协议·tcp/ip·安全·网络安全·php
长勺1 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
紫乾20141 小时前
idea json生成实体类
java·json·intellij-idea
wh_xia_jun1 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
网安INF1 小时前
CVE-2020-17518源码分析与漏洞复现(Flink 路径遍历)
java·web安全·网络安全·flink·漏洞
Y第五个季节1 小时前
docker-部署Nginx以及Tomcat
java·开发语言
IT-ZXT8882 小时前
Tomcat 线程模型详解&性能调优
java·tomcat
曼岛_2 小时前
[密码学实战]彻底理解位(bit)与字节(byte)在十六进制处理中的区别
密码学
Snk0xHeart2 小时前
极客大挑战 2019 EasySQL 1(万能账号密码,SQL注入,HackBar)
数据库·sql·网络安全