加密与签名技术之密钥派生与密码学随机数

概述

密钥派生函数(KDF)用于从密码、主密钥或其他密钥材料派生加密密钥。密码学安全的随机数生成器用于生成密钥、IV等随机值。

目录

  1. PBKDF2
  2. bcrypt
  3. scrypt
  4. Argon2
  5. HKDF
  6. 密码学随机数生成器

PBKDF2

原理

PBKDF2 (Password-Based Key Derivation Function 2) 使用迭代哈希从密码派生密钥。通过增加迭代次数提高安全性。

算法:

ini 复制代码
DK = PBKDF2(PRF, Password, Salt, c, dkLen)

技术规格

属性
迭代次数 建议10000+(2024年建议100000+)
Salt长度 至少64位(推荐128位)
输出长度 可变
PRF HMAC-SHA1, HMAC-SHA256等

应用场景

  1. 密码存储:从密码派生哈希值
  2. 密钥派生:从主密钥派生子密钥
  3. 加密密钥生成:从用户密码派生加密密钥

性能影响

  • 计算速度:慢(设计如此,抗暴力破解)
  • 内存占用:低
  • CPU使用率:高(迭代次数多)
  • 可调性:通过迭代次数调整安全性与性能

Java实现示例

java 复制代码
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
​
public class PBKDF2Example {
    
    /**
     * PBKDF2密钥派生(用于密码哈希)
     */
    public static class PasswordHash {
        public String hash;
        public String salt;
        public int iterations;
        
        public PasswordHash(String hash, String salt, int iterations) {
            this.hash = hash;
            this.salt = salt;
            this.iterations = iterations;
        }
    }
    
    /**
     * 生成密码哈希
     */
    public static PasswordHash hashPassword(String password, int iterations) throws Exception {
        // 生成随机Salt
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16]; // 128位Salt
        random.nextBytes(salt);
        
        // PBKDF2 with HMAC-SHA256
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 256);
        byte[] hash = factory.generateSecret(spec).getEncoded();
        
        return new PasswordHash(
            Base64.getEncoder().encodeToString(hash),
            Base64.getEncoder().encodeToString(salt),
            iterations
        );
    }
    
    /**
     * 验证密码
     */
    public static boolean verifyPassword(String password, PasswordHash stored) throws Exception {
        byte[] salt = Base64.getDecoder().decode(stored.salt);
        
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, stored.iterations, 256);
        byte[] hash = factory.generateSecret(spec).getEncoded();
        
        String computedHash = Base64.getEncoder().encodeToString(hash);
        return computedHash.equals(stored.hash);
    }
    
    /**
     * 派生加密密钥
     */
    public static byte[] deriveKey(String password, byte[] salt, int iterations, int keyLength) 
            throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength * 8);
        return factory.generateSecret(spec).getEncoded();
    }
    
    /**
     * 完整示例
     */
    public static void main(String[] args) throws Exception {
        String password = "用户密码123";
        
        System.out.println("=== PBKDF2密码哈希 ===");
        PasswordHash hash = hashPassword(password, 100000);
        System.out.println("密码: " + password);
        System.out.println("Salt: " + hash.salt);
        System.out.println("迭代次数: " + hash.iterations);
        System.out.println("哈希: " + hash.hash);
        
        System.out.println("\n=== 密码验证 ===");
        boolean isValid = verifyPassword(password, hash);
        System.out.println("验证结果: " + isValid);
        boolean isInvalid = verifyPassword("错误密码", hash);
        System.out.println("错误密码验证: " + isInvalid);
        
        System.out.println("\n=== 派生加密密钥 ===");
        byte[] salt = new byte[16];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);
        byte[] key = deriveKey(password, salt, 100000, 32); // 256位密钥
        System.out.println("派生密钥长度: " + key.length + " 字节 (256位)");
        
        System.out.println("\n=== 性能测试(不同迭代次数) ===");
        int[] iterationsArray = {10000, 50000, 100000, 200000};
        for (int iter : iterationsArray) {
            long start = System.currentTimeMillis();
            hashPassword("test", iter);
            long end = System.currentTimeMillis();
            System.out.println("迭代次数 " + iter + ": " + (end - start) + "ms");
        }
    }
}

安全建议

推荐配置:

  • 迭代次数:至少100000次(2024年)
  • Salt长度:至少128位
  • 使用HMAC-SHA256而非SHA1

⚠️ 注意:

  • 迭代次数应根据硬件性能调整(至少100ms计算时间)
  • 定期增加迭代次数以适应硬件发展

bcrypt

原理

bcrypt是基于Blowfish密码的密钥派生函数,专门设计用于密码哈希。通过"成本因子"控制计算复杂度。

技术规格

属性
成本因子 4-31(推荐10-12)
Salt长度 128位(自动生成)
输出长度 60字符(包含元数据)
轮数 2^成本因子

应用场景

  1. 密码存储:广泛用于Web应用
  2. 数据库密码:某些数据库系统
  3. 系统认证:Unix/Linux系统

性能影响

  • 计算速度:慢(设计如此)
  • 内存占用:中等
  • CPU使用率:高
  • 可调性:通过成本因子调整

Java实现示例

arduino 复制代码
// 需要使用第三方库,如jBCrypt或Spring Security
​
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.regex.Pattern;
​
public class BCryptExample {
    
    private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
    
    /**
     * 生成密码哈希
     */
    public static String hashPassword(String password) {
        return encoder.encode(password);
    }
    
    /**
     * 验证密码
     */
    public static boolean verifyPassword(String password, String hash) {
        return encoder.matches(password, hash);
    }
    
    /**
     * 使用自定义成本因子
     */
    public static String hashPasswordWithStrength(String password, int strength) {
        BCryptPasswordEncoder customEncoder = new BCryptPasswordEncoder(strength);
        return customEncoder.encode(password);
    }
    
    /**
     * 检查哈希是否需要升级(成本因子增加)
     */
    public static boolean needsUpgrade(String hash, int minStrength) {
        // bcrypt哈希格式:$2a$10$...
        Pattern pattern = Pattern.compile("\$2[aby]?\$(\d+)\$");
        java.util.regex.Matcher matcher = pattern.matcher(hash);
        if (matcher.find()) {
            int strength = Integer.parseInt(matcher.group(1));
            return strength < minStrength;
        }
        return true;
    }
    
    public static void main(String[] args) {
        String password = "用户密码";
        
        System.out.println("=== bcrypt密码哈希 ===");
        String hash = hashPassword(password);
        System.out.println("密码: " + password);
        System.out.println("哈希: " + hash);
        System.out.println("哈希长度: " + hash.length() + " 字符");
        
        System.out.println("\n=== 密码验证 ===");
        boolean isValid = verifyPassword(password, hash);
        System.out.println("验证结果: " + isValid);
        
        System.out.println("\n=== 不同成本因子性能测试 ===");
        for (int strength = 10; strength <= 14; strength++) {
            long start = System.currentTimeMillis();
            hashPasswordWithStrength(password, strength);
            long end = System.currentTimeMillis();
            System.out.println("成本因子 " + strength + ": " + (end - start) + "ms");
        }
        
        System.out.println("\n=== 检查是否需要升级 ===");
        String oldHash = hashPasswordWithStrength(password, 10);
        System.out.println("旧哈希需要升级: " + needsUpgrade(oldHash, 12));
    }
}

安全建议

推荐配置:

  • 成本因子:12-14(2024年)
  • 自动升级:检测旧哈希并升级

⚠️ 注意:

  • bcrypt输出包含Salt和成本因子
  • 成本因子每增加1,时间翻倍

scrypt

原理

scrypt设计用于抵抗硬件攻击(GPU、ASIC),通过同时使用CPU和内存资源提高安全性。

技术规格

参数 说明 推荐值
N CPU/内存成本 16384-32768
r 块大小参数 8
p 并行度 1-4

应用场景

  1. 加密货币:某些币种使用
  2. 高安全应用:需要抗GPU攻击
  3. 密码存储:需要更高安全性

性能影响

  • 计算速度:很慢(设计如此)
  • 内存占用:高(N*r参数决定)
  • CPU使用率:高
  • 抗攻击性:强(抗GPU/ASIC)

Java实现示例

arduino 复制代码
// 需要使用第三方库,如BouncyCastle
​
import org.bouncycastle.crypto.generators.SCrypt;
import java.security.SecureRandom;
import java.util.Base64;
​
public class SCryptExample {
    
    /**
     * scrypt密钥派生
     */
    public static class SCryptHash {
        public String hash;
        public String salt;
        public int N;
        public int r;
        public int p;
        
        public SCryptHash(String hash, String salt, int N, int r, int p) {
            this.hash = hash;
            this.salt = salt;
            this.N = N;
            this.r = r;
            this.p = p;
        }
    }
    
    /**
     * 生成密码哈希
     */
    public static SCryptHash hashPassword(String password, int N, int r, int p) {
        // 生成Salt
        byte[] salt = new byte[32];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);
        
        // scrypt派生(输出256位)
        byte[] hash = SCrypt.generate(
            password.getBytes(),
            salt,
            N,      // CPU/内存成本
            r,      // 块大小
            p,      // 并行度
            32      // 输出长度(256位)
        );
        
        return new SCryptHash(
            Base64.getEncoder().encodeToString(hash),
            Base64.getEncoder().encodeToString(salt),
            N, r, p
        );
    }
    
    /**
     * 验证密码
     */
    public static boolean verifyPassword(String password, SCryptHash stored) {
        byte[] salt = Base64.getDecoder().decode(stored.salt);
        byte[] hash = SCrypt.generate(
            password.getBytes(),
            salt,
            stored.N,
            stored.r,
            stored.p,
            32
        );
        
        String computedHash = Base64.getEncoder().encodeToString(hash);
        return computedHash.equals(stored.hash);
    }
    
    public static void main(String[] args) {
        String password = "用户密码";
        
        System.out.println("=== scrypt密码哈希 ===");
        SCryptHash hash = hashPassword(password, 16384, 8, 1);
        System.out.println("密码: " + password);
        System.out.println("N: " + hash.N);
        System.out.println("r: " + hash.r);
        System.out.println("p: " + hash.p);
        System.out.println("Salt: " + hash.salt);
        System.out.println("哈希: " + hash.hash);
        
        System.out.println("\n=== 密码验证 ===");
        boolean isValid = verifyPassword(password, hash);
        System.out.println("验证结果: " + isValid);
        
        System.out.println("\n=== 性能测试 ===");
        long start = System.currentTimeMillis();
        hashPassword(password, 16384, 8, 1);
        long end = System.currentTimeMillis();
        System.out.println("scrypt (N=16384): " + (end - start) + "ms");
    }
}

安全建议

推荐配置:

  • N=16384, r=8, p=1(交互式登录)
  • N=32768, r=8, p=1(更高安全)
  • 根据可用内存调整N和r

⚠️ 注意:

  • 内存需求 = 128 * N * r 字节
  • 内存不足会严重影响性能

Argon2

原理

Argon2是密码哈希竞赛(PHC)的获胜者,提供三种变体:

  • Argon2d:抗GPU攻击
  • Argon2i:抗侧信道攻击
  • Argon2id:混合模式(推荐)

技术规格

参数 说明 推荐值
memory 内存成本(KB) 65536 (64MB)
iterations 迭代次数 3
parallelism 并行度 4
hashLength 输出长度 32字节

应用场景

  1. 密码存储:现代应用首选
  2. 高安全场景:需要抗各类攻击
  3. 新项目:推荐使用Argon2

性能影响

  • 计算速度:慢(设计如此)
  • 内存占用:高(可配置)
  • CPU使用率:高
  • 抗攻击性:最强

Java实现示例

arduino 复制代码
// 需要使用第三方库,如BouncyCastle
​
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
import org.bouncycastle.crypto.params.Argon2Parameters;
import java.security.SecureRandom;
import java.util.Base64;
​
public class Argon2Example {
    
    public static class Argon2Hash {
        public String hash;
        public String salt;
        public int memory;
        public int iterations;
        public int parallelism;
        
        public Argon2Hash(String hash, String salt, int memory, int iterations, int parallelism) {
            this.hash = hash;
            this.salt = salt;
            this.memory = memory;
            this.iterations = iterations;
            this.parallelism = parallelism;
        }
    }
    
    /**
     * Argon2id密码哈希(推荐)
     */
    public static Argon2Hash hashPassword(String password, int memory, int iterations, 
                                          int parallelism) {
        // 生成Salt
        byte[] salt = new byte[16];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);
        
        // Argon2id参数
        Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
            .withSalt(salt)
            .withMemoryAsKB(memory)
            .withIterations(iterations)
            .withParallelism(parallelism);
        
        Argon2BytesGenerator generator = new Argon2BytesGenerator();
        generator.init(builder.build());
        
        // 生成哈希(256位)
        byte[] hash = new byte[32];
        generator.generateBytes(password.getBytes(), hash);
        
        return new Argon2Hash(
            Base64.getEncoder().encodeToString(hash),
            Base64.getEncoder().encodeToString(salt),
            memory,
            iterations,
            parallelism
        );
    }
    
    /**
     * 验证密码
     */
    public static boolean verifyPassword(String password, Argon2Hash stored) {
        byte[] salt = Base64.getDecoder().decode(stored.salt);
        
        Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
            .withSalt(salt)
            .withMemoryAsKB(stored.memory)
            .withIterations(stored.iterations)
            .withParallelism(stored.parallelism);
        
        Argon2BytesGenerator generator = new Argon2BytesGenerator();
        generator.init(builder.build());
        
        byte[] hash = new byte[32];
        generator.generateBytes(password.getBytes(), hash);
        
        String computedHash = Base64.getEncoder().encodeToString(hash);
        return computedHash.equals(stored.hash);
    }
    
    public static void main(String[] args) {
        String password = "用户密码";
        
        System.out.println("=== Argon2id密码哈希 ===");
        Argon2Hash hash = hashPassword(password, 65536, 3, 4);
        System.out.println("密码: " + password);
        System.out.println("内存: " + hash.memory + " KB");
        System.out.println("迭代: " + hash.iterations);
        System.out.println("并行度: " + hash.parallelism);
        System.out.println("Salt: " + hash.salt);
        System.out.println("哈希: " + hash.hash);
        
        System.out.println("\n=== 密码验证 ===");
        boolean isValid = verifyPassword(password, hash);
        System.out.println("验证结果: " + isValid);
        
        System.out.println("\n=== 不同配置性能测试 ===");
        int[] memorySizes = {32768, 65536, 131072};
        for (int mem : memorySizes) {
            long start = System.currentTimeMillis();
            hashPassword(password, mem, 3, 4);
            long end = System.currentTimeMillis();
            System.out.println("内存 " + mem + " KB: " + (end - start) + "ms");
        }
    }
}

安全建议

推荐配置:

  • Argon2id:混合模式,推荐
  • 内存:64MB(65536 KB)
  • 迭代次数:3
  • 并行度:4
  • 输出长度:32字节

⚠️ 注意:

  • 内存和并行度可根据服务器资源调整
  • 交互式登录建议100-500ms计算时间

HKDF

原理

HKDF (HMAC-based Key Derivation Function) 用于从主密钥派生多个子密钥。包括提取(Extract)和扩展(Expand)两个阶段。

技术规格

属性
输入 主密钥 + Salt(可选)
输出 可变长度子密钥
算法 HMAC-SHA256/512

应用场景

  1. 密钥派生:从主密钥派生多个子密钥
  2. TLS:密钥派生
  3. 密钥分层:密钥管理系统

性能影响

  • 计算速度:快
  • 内存占用:低
  • CPU使用率:低

Java实现示例

ini 复制代码
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
​
public class HKDFExample {
    
    /**
     * HKDF提取阶段
     */
    private static byte[] extract(byte[] salt, byte[] inputKeyMaterial) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(salt, "HmacSHA256");
        mac.init(keySpec);
        return mac.doFinal(inputKeyMaterial);
    }
    
    /**
     * HKDF扩展阶段
     */
    private static byte[] expand(byte[] prk, byte[] info, int length) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(prk, "HmacSHA256");
        mac.init(keySpec);
        
        byte[] output = new byte[length];
        byte[] t = new byte[0];
        int offset = 0;
        int counter = 1;
        
        while (offset < length) {
            mac.update(t);
            mac.update(info);
            mac.update((byte) counter);
            t = mac.doFinal();
            
            int toCopy = Math.min(t.length, length - offset);
            System.arraycopy(t, 0, output, offset, toCopy);
            offset += toCopy;
            counter++;
        }
        
        return output;
    }
    
    /**
     * HKDF完整流程
     */
    public static byte[] derive(byte[] inputKeyMaterial, byte[] salt, byte[] info, int length) 
            throws Exception {
        // 如果没有提供Salt,使用全零
        if (salt == null || salt.length == 0) {
            salt = new byte[32]; // 256位全零
        }
        
        // 提取阶段
        byte[] prk = extract(salt, inputKeyMaterial);
        
        // 扩展阶段
        return expand(prk, info, length);
    }
    
    /**
     * 派生多个密钥
     */
    public static byte[][] deriveMultipleKeys(byte[] masterKey, int keyCount, int keyLength) 
            throws Exception {
        byte[][] keys = new byte[keyCount][];
        for (int i = 0; i < keyCount; i++) {
            byte[] info = ("key-" + i).getBytes();
            keys[i] = derive(masterKey, null, info, keyLength);
        }
        return keys;
    }
    
    public static void main(String[] args) throws Exception {
        // 生成主密钥
        byte[] masterKey = new byte[32];
        SecureRandom random = new SecureRandom();
        random.nextBytes(masterKey);
        
        System.out.println("=== HKDF密钥派生 ===");
        byte[] salt = new byte[32];
        random.nextBytes(salt);
        byte[] info = "application-key".getBytes();
        
        byte[] derivedKey = derive(masterKey, salt, info, 32);
        System.out.println("主密钥长度: " + masterKey.length + " 字节");
        System.out.println("派生密钥长度: " + derivedKey.length + " 字节");
        
        System.out.println("\n=== 派生多个密钥 ===");
        byte[][] keys = deriveMultipleKeys(masterKey, 3, 32);
        System.out.println("派生了 " + keys.length + " 个密钥");
        for (int i = 0; i < keys.length; i++) {
            System.out.println("密钥 " + i + " 长度: " + keys[i].length + " 字节");
        }
    }
}

密码学随机数生成器

原理

密码学安全的随机数生成器(CSPRNG)生成不可预测的随机数,用于密钥、IV、nonce等。

Java SecureRandom

Java的SecureRandom提供密码学安全的随机数生成。

Java实现示例

arduino 复制代码
import java.security.SecureRandom;
import java.util.Base64;
​
public class SecureRandomExample {
    
    /**
     * 生成随机字节
     */
    public static byte[] generateRandomBytes(int length) {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[length];
        random.nextBytes(bytes);
        return bytes;
    }
    
    /**
     * 生成随机密钥
     */
    public static String generateRandomKey(int keyLengthBits) {
        byte[] key = generateRandomBytes(keyLengthBits / 8);
        return Base64.getEncoder().encodeToString(key);
    }
    
    /**
     * 生成随机Salt
     */
    public static String generateSalt(int saltLengthBytes) {
        return Base64.getEncoder().encodeToString(generateRandomBytes(saltLengthBytes));
    }
    
    /**
     * 生成随机IV
     */
    public static byte[] generateIV(int ivLengthBytes) {
        return generateRandomBytes(ivLengthBytes);
    }
    
    /**
     * 生成随机Nonce
     */
    public static byte[] generateNonce(int nonceLengthBytes) {
        return generateRandomBytes(nonceLengthBytes);
    }
    
    /**
     * 生成随机整数(在范围内)
     */
    public static int generateRandomInt(int min, int max) {
        SecureRandom random = new SecureRandom();
        return random.nextInt(max - min + 1) + min;
    }
    
    /**
     * 使用特定算法
     */
    public static SecureRandom createSecureRandom(String algorithm) throws Exception {
        return SecureRandom.getInstance(algorithm);
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println("=== 密码学随机数生成 ===");
        
        System.out.println("\n随机密钥:");
        System.out.println("256位密钥: " + generateRandomKey(256));
        
        System.out.println("\n随机Salt:");
        System.out.println("128位Salt: " + generateSalt(16));
        
        System.out.println("\n随机IV:");
        byte[] iv = generateIV(16);
        System.out.println("IV: " + Base64.getEncoder().encodeToString(iv));
        
        System.out.println("\n随机Nonce:");
        byte[] nonce = generateNonce(12);
        System.out.println("Nonce: " + Base64.getEncoder().encodeToString(nonce));
        
        System.out.println("\n随机整数 (1-100):");
        for (int i = 0; i < 5; i++) {
            System.out.println(generateRandomInt(1, 100));
        }
        
        System.out.println("\n可用的SecureRandom算法:");
        String[] algorithms = {"NativePRNG", "SHA1PRNG", "Windows-PRNG"};
        for (String alg : algorithms) {
            try {
                SecureRandom sr = createSecureRandom(alg);
                System.out.println(alg + ": ✓");
            } catch (Exception e) {
                System.out.println(alg + ": ✗");
            }
        }
    }
}

安全建议

推荐:

  • 使用SecureRandom而非Random
  • 生成足够长度的随机值(密钥至少128位)
  • IV和Nonce必须唯一(不重复使用)

⚠️ 避免:

  • 使用java.util.Random(不安全)
  • 重复使用IV/Nonce
  • 可预测的随机数

密码哈希算法对比

算法 抗暴力破解 抗GPU攻击 抗侧信道 内存需求 推荐度
Argon2id ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
scrypt ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
bcrypt ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
PBKDF2 ⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐

选择建议

新项目: Argon2id 现有项目: bcrypt或PBKDF2(逐步迁移) 高安全需求: Argon2id或scrypt 资源受限: PBKDF2


总结

密钥派生选择

密码哈希:

  • 首选:Argon2id
  • 备选:bcrypt

密钥派生:

  • 首选:HKDF
  • 备选:PBKDF2

随机数生成

必须使用SecureRandom

选择决策树

复制代码
需要密钥派生?
├─ 密码哈希?→ Argon2id
├─ 从主密钥派生?→ HKDF
└─ 兼容性要求?→ PBKDF2
​
需要随机数?
└─ 使用SecureRandom
相关推荐
uuuuuuu1 小时前
数组中的排序问题
算法
绝无仅有1 小时前
面试之高级实战:在大型项目中如何利用AOP、Redis及缓存设计
后端·面试·架构
爱找乐子的李寻欢1 小时前
谁懂啊!测试环境 RocketMQ 延迟消息崩了,罪魁祸首是个…
后端
milixiang1 小时前
项目部署时接口短暂访问异常问题修复:Nacos+Gateway活跃节点监听
后端·spring cloud
绝无仅有1 小时前
redis缓存功能结合实际项目面试之问题与解析
后端·面试·架构
Stream1 小时前
加密与签名技术之哈希算法
后端·算法
少许极端1 小时前
算法奇妙屋(十五)-BFS解决边权为1的最短路径问题
数据结构·算法·bfs·宽度优先·队列·图解算法·边权为1的最短路径问题
z***D6481 小时前
SpringBoot 新特性
java·spring boot·后端
c骑着乌龟追兔子1 小时前
Day 27 常见的降维算法
人工智能·算法·机器学习