RSA 加密算法详解

RSA 加密算法详解

​ RSA 是一种非对称加密算法(Asymmetric Cryptography),由 Ron Rivest、Adi Shamir 和 Leonard Adleman 于 1977 年提出,其核心基于「大数分解难题」(大质数相乘容易,分解乘积为两个质数极难),是目前工业界应用最广泛的加密算法之一,也是 Java 后端开发中处理敏感数据、接口验签、密钥交换的核心技术。

一、核心特性(与开发直接相关)

1. 非对称密钥体系

  • 公钥(Public Key) :公开分发,用于加密数据验证签名,任何人都可获取。
  • 私钥(Private Key) :严格保密,用于解密数据生成签名,仅持有者可访问。
  • 核心逻辑:公钥加密的数据只能用对应私钥解密,私钥签名的数据只能用对应公钥验证。

2. 双重功能

  • 加密解密:保护数据机密性(如用户手机号、身份证号加密存储)。
  • 签名验签:保证数据完整性(防篡改)和身份真实性(防伪造),是 API 接口验签的核心方案。

3. 性能特点

  • 优点:安全性高(密钥长度足够时,目前无有效破解方法)、无需安全信道分发密钥。
  • 缺点:运算速度慢(比 AES 等对称加密慢 100-1000 倍),不适合大数据量加密(如文件、流数据)。

二、底层原理

RSA 原理基于数论,核心是「公钥加密、私钥解密」的数学不可逆性,简化步骤如下:

1. 密钥生成(核心步骤)

  1. 选择两个大质数 pq(实际开发中需 1024 位以上,推荐 2048/4096 位)。
  2. 计算乘积 n = p * qn 是公钥和私钥的共同模数,决定密钥长度)。
  3. 计算欧拉函数 φ(n) = (p-1) * (q-1)(用于后续计算私钥)。
  4. 选择公钥指数 e:满足 1 < e < φ(n)eφ(n) 互质(常用固定值 65537,兼顾安全性和性能)。
  5. 计算私钥指数 d:满足 (e * d) mod φ(n) = 1deφ(n) 下的模逆元)。
  6. 最终密钥对:
    • 公钥:(e, n)(通常编码为 X.509 格式,Base64 字符串存储)。
    • 私钥:(d, n)(通常编码为 PKCS#8 格式,加密存储在密钥库或配置中心)。

2. 加密 / 解密过程

  • 加密 (公钥加密):明文 m → 密文 c = m^e mod nm 必须小于 n,否则需分段)。
  • 解密 (私钥解密):密文 c → 明文 m = c^d mod n

3. 签名 / 验签过程(API 验签核心)

  • 签名 (私钥签名):对数据 data 计算哈希值 hash(data) → 用私钥加密哈希值得到签名 sig = hash(data)^d mod n
  • 验签 (公钥验签):接收方用公钥解密签名 hash'(data) = sig^e mod n → 对比 hash'(data) 与自身计算的 hash(data),一致则数据未篡改且身份合法。

三、实战部分

  • 签名 / 验签过程
  • 加密 / 解密过程

签名- 验签过程

java 复制代码
package com.fuiou.service.utils;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @Author LQ
 * @Date 2025/12/25 19:23
 * @Description:
 */
public class RsaSignVerify {

    /**
     * RSA算法标识(JDK原生固定值)
     */
    private static final String RSA_ALGORITHM = "RSA";

    /**
     * 签名算法:SHA256withRSA(JDK8+原生支持,安全级别高)
     */
    private static final String SIGN_ALGORITHM = "SHA256withRSA";

    /**
     * RSA密钥长度:2048位(平衡安全性与性能,JDK原生支持)
     */
    private static final int RSA_KEY_SIZE = 2048;

    /**
     * Base64编码器(JDK8+内置,避免第三方依赖)
     */
    private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
    /**
     * Base64解码器
     */
    private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

    // ====================== 密钥对生成(纯原生API) ======================
    /**
     * 生成RSA密钥对(公钥+私钥)
     * @return KeyPair 密钥对对象
     * @throws NoSuchAlgorithmException 算法不支持(JDK8+必支持RSA,此异常仅兜底)
     */
    public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
        // 初始化RSA密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        // 设置密钥长度和随机源(增强随机性)
        keyPairGenerator.initialize(RSA_KEY_SIZE, new SecureRandom());
        // 生成密钥对
        return keyPairGenerator.generateKeyPair();
    }

    // ====================== 密钥与Base64互转(原生解析) ======================
    /**
     * 公钥转Base64字符串(公钥默认采用X.509编码)
     * @param publicKey 公钥对象
     * @return Base64编码的公钥字符串
     */
    public static String publicKeyToBase64(PublicKey publicKey) {
        if (publicKey == null) {
            throw new IllegalArgumentException("公钥对象不能为空");
        }
        return BASE64_ENCODER.encodeToString(publicKey.getEncoded());
    }

    /**
     * 私钥转Base64字符串(私钥默认采用PKCS#8编码)
     * @param privateKey 私钥对象
     * @return Base64编码的私钥字符串
     */
    public static String privateKeyToBase64(PrivateKey privateKey) {
        if (privateKey == null) {
            throw new IllegalArgumentException("私钥对象不能为空");
        }
        return BASE64_ENCODER.encodeToString(privateKey.getEncoded());
    }

    /**
     * Base64公钥字符串解析为PublicKey对象(X.509格式)
     * @param base64PublicKey Base64编码的公钥字符串
     * @return PublicKey 公钥对象
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeySpecException 密钥格式错误
     */
    public static PublicKey base64ToPublicKey(String base64PublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (base64PublicKey == null || base64PublicKey.trim().isEmpty()) {
            throw new IllegalArgumentException("Base64公钥字符串不能为空");
        }
        // 解码Base64为字节数组
        byte[] publicKeyBytes = BASE64_DECODER.decode(base64PublicKey.trim());
        // 初始化X.509公钥规范
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        // 生成公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * Base64私钥字符串解析为PrivateKey对象(PKCS#8格式)
     * @param base64PrivateKey Base64编码的私钥字符串
     * @return PrivateKey 私钥对象
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeySpecException 密钥格式错误
     */
    public static PrivateKey base64ToPrivateKey(String base64PrivateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (base64PrivateKey == null || base64PrivateKey.trim().isEmpty()) {
            throw new IllegalArgumentException("Base64私钥字符串不能为空");
        }
        // 解码Base64为字节数组
        byte[] privateKeyBytes = BASE64_DECODER.decode(base64PrivateKey.trim());
        // 初始化PKCS#8私钥规范
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        // 生成私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }

    // ====================== 私钥加签(原生Signature API) ======================
    /**
     * 私钥签名(SHA256withRSA)
     * @param data 待签名的原始数据(UTF-8编码)
     * @param privateKey 私钥对象
     * @return Base64编码的签名字符串
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeyException 私钥无效
     * @throws SignatureException 签名过程异常
     */
    public static String sign(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        if (data == null || data.trim().isEmpty()) {
            throw new IllegalArgumentException("待签名数据不能为空");
        }
        if (privateKey == null) {
            throw new IllegalArgumentException("私钥对象不能为空");
        }
        // 初始化签名对象(SHA256withRSA算法)
        Signature signature = Signature.getInstance(SIGN_ALGORITHM);
        // 初始化签名模式(私钥签名)
        signature.initSign(privateKey);
        // 更新待签名数据(UTF-8编码,避免乱码)
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        // 生成签名字节数组并转Base64
        byte[] signBytes = signature.sign();
        return BASE64_ENCODER.encodeToString(signBytes);
    }

    /**
     * 重载:Base64私钥字符串签名
     * @param data 待签名数据
     * @param base64PrivateKey Base64编码的私钥字符串
     * @return Base64编码的签名字符串
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeySpecException 密钥格式错误
     * @throws InvalidKeyException 私钥无效
     * @throws SignatureException 签名过程异常
     */
    public static String sign(String data, String base64PrivateKey) {
        try {
            PrivateKey privateKey = base64ToPrivateKey(base64PrivateKey);
            return sign(data, privateKey);
        } catch (Exception e) {
            return null;
        }
    }

    // ====================== 公钥验签(核心方法) ======================
    /**
     * 公钥验签(验证签名是否合法)
     * @param data 原始数据(UTF-8编码,需与签名时一致)
     * @param sign Base64编码的签名字符串
     * @param publicKey 公钥对象
     * @return true=验签通过,false=验签失败
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeyException 公钥无效
     * @throws SignatureException 验签过程异常
     */
    public static boolean verify(String data, String sign, PublicKey publicKey){
        try {
            if (data == null || data.trim().isEmpty()) {
                throw new IllegalArgumentException("原始数据不能为空");
            }
            if (sign == null || sign.trim().isEmpty()) {
                throw new IllegalArgumentException("签名字符串不能为空");
            }
            if (publicKey == null) {
                throw new IllegalArgumentException("公钥对象不能为空");
            }
            // 初始化签名对象(与签名算法一致)
            Signature signature = Signature.getInstance(SIGN_ALGORITHM);
            // 初始化验签模式(公钥验签)
            signature.initVerify(publicKey);
            // 更新原始数据(UTF-8编码)
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            // 解码签名字符串并验签
            byte[] signBytes = BASE64_DECODER.decode(sign.trim());
            return signature.verify(signBytes);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 重载:Base64公钥字符串验签
     * @param data 原始数据
     * @param sign Base64编码的签名字符串
     * @param base64PublicKey Base64编码的公钥字符串
     * @return true=验签通过,false=验签失败
     * @throws NoSuchAlgorithmException 算法不支持
     * @throws InvalidKeySpecException 密钥格式错误
     * @throws InvalidKeyException 公钥无效
     * @throws SignatureException 验签过程异常
     */
    public static boolean verify(String data, String sign, String base64PublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        PublicKey publicKey = base64ToPublicKey(base64PublicKey);
        boolean result = verify(data, sign, publicKey);
        return result;
    }

    // ====================== 测试示例 ======================
    public static void main(String[] args) {
        try {
            // 1. 生成密钥对
            KeyPair keyPair = generateRsaKeyPair();
            String base64PublicKey = publicKeyToBase64(keyPair.getPublic());
            String base64PrivateKey = privateKeyToBase64(keyPair.getPrivate());
            System.out.println("原生生成Base64公钥:\n" + base64PublicKey);
            System.out.println("原生生成Base64私钥:\n" + base64PrivateKey);

            // 2. 待签名原始数据
            String originalData = "Pure Java Native RSA Sign & Verify Test";

            // 3. 私钥加签
            String signResult = sign(originalData, base64PrivateKey);
            System.out.println("签名结果(Base64):\n" + signResult);

            // 4. 公钥验签(正常场景,预期通过)
            boolean verifySuccess = verify(originalData, signResult, base64PublicKey);
            System.out.println("正常数据验签结果:" + (verifySuccess ? "通过" : "失败"));

            // 5. 篡改数据验签(预期失败)
            String tamperedData = originalData + "_hack";
            boolean verifyFail = verify(tamperedData, signResult, base64PublicKey);
            System.out.println("篡改数据验签结果:" + (verifyFail ? "通过" : "失败"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

加密 / 解密过程

java 复制代码
public class RsaEncryptUtil {

    // RSA加密算法(指定PKCS1Padding填充,避免无填充的安全问题)
    private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
    // RSA密钥长度(2048位为安全基线,1024位已不安全)
    private static final int KEY_SIZE = 2048;
    // 2048位RSA+PKCS1Padding的最大明文长度(2048/8 - 11 = 245 - 11 = 234?修正:2048位密钥的模长是256字节,PKCS1Padding占11字节,所以最大明文长度=256-11=245字节)
    private static final int MAX_ENCRYPT_BLOCK = 245;
    // 2048位RSA解密的最大块长度(=密钥模长=256字节)
    private static final int MAX_DECRYPT_BLOCK = 256;


    /**
     * Base64编码器(JDK8+内置,避免第三方依赖)
     */
    private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
    /**
     * Base64解码器
     */
    private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

    /**
     * 生成RSA密钥对(公钥+私钥)
     */
    public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(KEY_SIZE);
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * 公钥加密:明文→Base64编码的密文(方便传输/存储)
     * @param plainText 明文字符串
     * @param publicKey RSA公钥
     * @return Base64编码的密文
     * @throws Exception 加密异常
     */
    public static String encryptByPublicKey(String plainText, PublicKey publicKey) throws Exception {
        byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 初始化加密模式(公钥)
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        // 分段加密(解决RSA单次加密长度限制)
        int inputLen = plainBytes.length;
        byte[] encryptedBytes = new byte[0];
        int offSet = 0;
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(plainBytes, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(plainBytes, offSet, inputLen - offSet);
            }
            // 拼接分段加密结果
            byte[] temp = new byte[encryptedBytes.length + cache.length];
            System.arraycopy(encryptedBytes, 0, temp, 0, encryptedBytes.length);
            System.arraycopy(cache, 0, temp, encryptedBytes.length, cache.length);
            encryptedBytes = temp;
            offSet += MAX_ENCRYPT_BLOCK;
        }
        // 加密后的二进制转Base64明文(密文的可读形式)
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 私钥解密:Base64编码的密文→明文
     * @param cipherTextBase64 Base64编码的密文
     * @param privateKey RSA私钥
     * @return 明文字符串
     * @throws Exception 解密异常
     */
    public static String decryptByPrivateKey(String cipherTextBase64, PrivateKey privateKey) throws Exception {
        // 1. Base64密文转回二进制字节数组
        byte[] cipherBytes = Base64.getDecoder().decode(cipherTextBase64);
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        // 2. 初始化解密模式(私钥)
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        // 3. 分段解密(对应分段加密)
        int inputLen = cipherBytes.length;
        byte[] decryptedBytes = new byte[0];
        int offSet = 0;
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(cipherBytes, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(cipherBytes, offSet, inputLen - offSet);
            }
            // 拼接分段解密结果
            byte[] temp = new byte[decryptedBytes.length + cache.length];
            System.arraycopy(decryptedBytes, 0, temp, 0, decryptedBytes.length);
            System.arraycopy(cache, 0, temp, decryptedBytes.length, cache.length);
            decryptedBytes = temp;
            offSet += MAX_DECRYPT_BLOCK;
        }
        // 4. 解密后的字节转明文字符串
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }

    // -------------- 复用:密钥与Base64字符串互转 --------------
    public static String publicKeyToBase64(PublicKey publicKey) {
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }

    public static PublicKey base64ToPublicKey(String pubKeyBase64) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(pubKeyBase64);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    public static String privateKeyToBase64(PrivateKey privateKey) {
        return Base64.getEncoder().encodeToString(privateKey.getEncoded());
    }

    public static PrivateKey base64ToPrivateKey(String priKeyBase64) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(priKeyBase64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    // 测试主方法
    public static void main(String[] args) throws Exception {
        // ========== 1. 生成RSA密钥对 ==========
        KeyPair keyPair = generateRsaKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("公钥(Base64):" + publicKeyToBase64(publicKey));
        System.out.println("私钥(Base64):" + privateKeyToBase64(privateKey));
        System.out.println("----------------------------------------");

        // ========== 2. 待加密的明文(支持长文本,分段加密) ==========
        String plainText = "RSA解密测试:这是一段需要加密的长文本,RSA非对称加密的特点是公钥加密、私钥解密,且单次加密长度有限,因此需要分段处理!RSA解密测试:这是一段需要加密的长文本,RSA非对称加密的特点是公钥加密、私钥解密,且单次加密长度有限,因此需要分段处理!";
        System.out.println("原始明文:" + plainText);
        System.out.println("----------------------------------------");

        // ========== 3. 公钥加密 ==========
        String cipherText = encryptByPublicKey(plainText, publicKey);
        System.out.println("加密后密文(Base64):" + cipherText);
        System.out.println("----------------------------------------");

        // ========== 4. 私钥解密 ==========
        String decryptedText = decryptByPrivateKey(cipherText, privateKey);
        System.out.println("解密后明文:" + decryptedText);
        System.out.println("----------------------------------------");

        // 验证解密结果是否与原始明文一致
        System.out.println("解密结果验证:" + plainText.equals(decryptedText));
    }
}

四、处理大数据量加密

RSA 加密的明文长度限制为「密钥长度 - 填充长度」

  • 2048 位密钥 + OAEP 填充:最大明文长度 = 2048/8 - 42 = 214 字节。

  • 超过限制需

    分段加密或「RSA + AES 混合加密」(推荐):

    1. 用 AES 加密大数据(如文件、长文本),AES 密钥随机生成。
    2. 用 RSA 加密 AES 密钥。
    3. 传输时发送「AES 加密后的密文 + RSA 加密后的 AES 密钥」。

五、springboot中拦截器的运用

java 复制代码
@Component
public class SignInterceptor implements HandlerInterceptor {
    @Value("${rsa.public-key}")
    private String publicKeyStr;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求参数和签名
        String requestParams = getRequestParams(request); // 拼接所有请求参数(含 GET/POST)
        String sign = request.getHeader("X-Sign"); // 从请求头获取签名

        // 2. 验签
        PublicKey publicKey = RSAUtils.getPublicKey(publicKeyStr);
        boolean verifyResult = RSAUtils.verify(requestParams, sign, publicKey);
        if (!verifyResult) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.getWriter().write("Sign verification failed");
            return false;
        }
        return true;
    }

    // 拼接请求参数(按字典序排序,避免参数顺序导致验签失败)
    private String getRequestParams(HttpServletRequest request) {
        Map<String, String[]> paramMap = request.getParameterMap();
        return paramMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
                .collect(Collectors.joining("&"));
    }
}

六、安全注意事项

  1. 密钥长度:至少 2048 位(1024 位已被破解),金融级场景推荐 4096 位。
  2. 加密模式 :禁用 RSA/ECB/PKCS1Padding(ECB 模式无随机性,易被攻击),使用 OAEPWithSHA-256AndMGF1Padding
  3. 签名算法 :禁用 MD5withRSA(MD5 哈希易碰撞),使用 SHA256withRSASHA512withRSA
  4. 私钥安全:绝对禁止泄露私钥,定期轮换密钥(如每 6 个月)。
  5. 公钥分发:确保公钥来源可信(如通过 HTTPS 下载、配置中心分发),避免公钥被篡改。
  6. 随机数 :密钥生成时使用 SecureRandom(而非 Random),确保密钥随机性。

七、与对称加密(AES)的对比及组合使用

特性 RSA(非对称) AES(对称)
密钥数量 公钥 + 私钥 单密钥
运算速度 慢(适合小数据) 快(适合大数据)
密钥分发 公钥可公开(无需安全信道) 需安全信道分发密钥
适用场景 加密密钥、签名验签 加密数据(文件、长文本)

组合使用方案(推荐)

  1. 用 AES 加密业务数据(如订单信息、用户详情)。
  2. 用 RSA 加密 AES 的随机密钥。
  3. 传输 / 存储时,将「AES 密文 + RSA 加密后的 AES 密钥」一起发送 / 存储。
  4. 接收方用 RSA 私钥解密得到 AES 密钥,再用 AES 解密业务数据。
相关推荐
Knight_AL2 小时前
HTTP 状态码一览:理解 2xx、3xx、4xx 和 5xx 分类
网络·网络协议·http
网硕互联的小客服2 小时前
人工智能服务器是什么,人工智能服务器的有什么用?
运维·服务器·网络·安全
应用市场2 小时前
地理距离计算方法详解:正交投影、Haversine与Vincenty公式
网络·tcp/ip·汽车
乾元2 小时前
AI 驱动的网络攻防演练与安全态势推演——从“规则检测”到“行为级对抗”的工程体系
网络·人工智能·安全·web安全·架构·自动化·运维开发
源代码•宸2 小时前
goframe框架签到系统项目开发(用户认证、基于 JWT 实现认证、携带access token获取用户信息)
服务器·开发语言·网络·分布式·后端·golang·jwt
一执念2 小时前
【路由器-AP、DHCP、ARP、广播帧、交换机、信道】-初级知识串联(二)
网络·智能路由器
G_H_S_3_3 小时前
【网络运维】SQL 语言:MySQL数据库基础与管理
运维·网络·数据库·mysql
bing_feilong3 小时前
windows和ubuntu: ssh失败
网络·ubuntu·ssh
浔川python社3 小时前
快手遭黑灰产猛烈攻击事件暴露出哪些安全漏洞?
网络·安全