对称加密加密原理和开发场景解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 加密是自古以来人们都在不断使用的技术,目的是为了隐藏信息,只是随着时代在不断的变化,加密也在不断的更新。从古代的藏宝图对藏宝地点进行隐藏。到二战时候,破译敌方电台,都是属于加密和破解的过程。进入21世纪后,加密在互联网时代也有了新的加密方法。也创造了密码学这个学科。目前在加密的场景下,通常分为:可逆加密和不可逆加密。而在可逆加密场景里又分为:对称加密和非对称加密。本次主要讨论集中在可逆加密上。可逆加密顾名思义就是在对明文进行加密后生成密文,能够通过解密把密文再还原成明文。数据加密一般主要解决三个问题:可信问题(非对称加密可解决),防篡改问题(不可逆加密解决),防窃听问题...

摘要

加密是自古以来人们都在不断使用的技术,目的是为了隐藏信息,只是随着时代在不断的变化,加密也在不断的更新。从古代的藏宝图对藏宝地点进行隐藏。到二战时候,破译敌方电台,都是属于加密和破解的过程。进入21世纪后,加密在互联网时代也有了新的加密方法。也创造了密码学这个学科。目前在加密的场景下,通常分为:可逆加密和不可逆加密。而在可逆加密场景里又分为:对称加密和非对称加密。本次主要讨论集中在可逆加密上。可逆加密顾名思义就是在对明文进行加密后生成密文,能够通过解密把密文再还原成明文。数据加密一般主要解决三个问题:可信问题(非对称加密可解决),防篡改问题(不可逆加密解决),防窃听问题(对称加密可解决,非对称部分可解决)。

对称加密

对称加密的原理就如字面意思所说,加密和解密的密钥是同一个。打个比喻就是:两个人开同一把锁,不分身份都可以用同一把钥匙打开锁。对称加密解决的最主要问题就是:防窃听问题。即在信息传输过程中,不能被第三方获取到信息的明文内容。

  • 优点:
  1. 加解密使用同一个密钥,比较方便。
  2. 加解密的速度快,适合大数据加密。
  • 缺点:
  • 密钥是信息接收和发送双方都在保存。对于密钥保存要求非常高。出现泄密的话,无法确定密钥泄露方。

对称加密算法简介

主流算法及优缺点

目前主流的对称加密算法有:DES,3DES,AES以及中国国密的SM4算法。

算法 优点 缺点
DES(Data Encrypt Standard) 数据标准加密:速度比较快,适合大数据量的数据加密。 速度快,但是安全性不够高
3DES 基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。 安全性低于AES
AES(Advanced Encryption Standard) 高级加密标准:是下一代加密算法标准,速度快,安全级别更高。可以使用128位,192位,256位的密钥。 -
SM4 中国自主设计,安全性同AES保持一致。由于自主设计,安全可控。 目前应用范围比较小。

AES算法

以AES加密为例来解释对称加密算法:
AES使用的密钥可以是:128位,192位和256位。不同长度的密钥带来的只有复杂度的区分。具体的加密过程分为:轮密钥加,字节代换,行位移,列混合。这四个操作全部操作一遍,为一轮加密。

  1. 轮密钥加:是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作。
  2. 字节代换:AES的字符代换其实就是一个简单的查表操作,AES定义了一个S盒和一个逆S盒。
  3. 行位移:就是一个简单的左循环移位操作。
  4. 列混合:是通过矩阵相乘来实现的,经过移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。

其中AES-128位会进行10轮的加密操作。每轮都包含这4个操作(第一轮和第十轮会稍有不同)。在进行循环加密结束后,生成的密文就可以进行传输使用了。这4个操作都是可逆操作,也就保证了解密的可行性。在解密的过程就是对这密文进行逆操作的过程。

轮密钥加

  • 轮密钥加

在这个步骤进行操作的时候,输入的内容是两个:明文和子密钥K[0](可以把k[0]当做密钥本身),这两个都是128bit的大小。两个输入内容按照矩阵排列,分别进行异或操作。其中明文使用P矩阵来表示,子密钥矩阵使用K矩阵。在进行异或操作后生成新的矩阵结果。
image.png

//这里对实际代码进行了一部分简化,方便理解。
void AddRoundKey(unsigned char(*P)[4], unsigned char(*K)[4])
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            P[i][j] ^= K[i][j];
        }
    }
}
//假设P矩阵如下,每个矩阵元素8bit, 0-255的范围.(无符号)
[2, @, 1, 0]
[a, 4, ^, 1]
[4, -, 1, )]
[9, a, c, .]
//K[0]矩阵如下
[%, *, a, 2]
[=, *, +, )]
[<, 2, #, 1]
[", *, d, ;]
则最后的加密结果矩阵是(16进制表示,因为有些字符是不可见字符):
[0x17, 0x6a, 0x50, 0x2]
[0x5c, 0x1e, 0x75, 0x18]
[0x8, 0x1f, 0x12, 0x18]
[0x1b, 0x4b, 0x7, 0x15]

字节代换

字节代换的操作比较简单,就是有一个S盒矩阵,将输入的字节,根据S盒矩阵,代换为另一个字节。这里的S盒矩阵是通过某种计算方法生成的,S盒矩阵大小为256,16行*16列。在映射的时候,把每个数据8bit的前4bit映射为行(4bit能表示的数字范围为0-15,刚好16),后4bit数据映射到列。进行数据代换。在解密的时候,有一个逆S盒,操作是一样的,叫做逆字节代换。

假设S盒如下:
image.png
逆S盒如下:
image.png
在上一轮的轮密钥加中我们得到矩阵:

[0x17, 0x6a, 0x50, 0x2]
[0x5c, 0x1e, 0x75, 0x18]
[0x8, 0x1f, 0x12, 0x18]
[0x1b, 0x4b, 0x7, 0x15]

我们先对矩阵第一个数据 0x17进行字节代换操作,根据S盒矩阵。如下图,得到0xF0
image.png
而得到的0xF0在逆S盒中,进行解密的逆字节代换可以发现又得到了0x17。
image.png

上一步的矩阵,逐个进行字节代换,得到最终的矩阵:

//S盒
const unsigned char S_Table[16][16] =
{
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};

//字节代换
int replaceSTable(unsigned char (*P)[4])
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; ++j)
        {
            P[i][j] = S_Table[P[i][j] >> 4][P[i][j] & 0x0F];
        }
    }
}
得到结果矩阵如下:
[0xF0, 0x02, 0x53, 0x77]
[0x4A, 0x72, 0x9D, 0xAD]
[0x30, 0xC0, 0xC9, 0xAD]
[0xAF, 0xB3, 0xC5, 0x59]

行位移

行位移是非常简单的操作,就是在一个4*4的矩阵上,对矩阵的行元素进行一次向左移动操作。而在AES中为何要加入这个如此简单的操作呢? 是因为这是为了下一步的列混合做准备,对矩阵的元素进行一次矩阵乘法,只要对矩阵的元素进行一次行位移,那么就会影响当前矩阵的状态,在下一步的列混合的时候,就会得到完全不一致的结果。达到一个雪崩效应的变化。
上一步骤得到的矩阵结果是:

[0xF0, 0x02, 0x53, 0x77] //不变
[0x4A, 0x72, 0x9D, 0xAD] //向左位移一个
[0x30, 0xC0, 0xC9, 0xAD] //向左位移两个
[0xAF, 0xB3, 0xC5, 0x59] //向左位移三个

void shiftRows(unsigned char *P[4]){
    unsigned char * tmp = (unsigned char *) malloc(sizeof(unsigned char) * 4);
    for (int i = 0 ; i < 4; i++) {
        for (int j = 0; j < i; j++) {
            tmp[j] = P[i][j];
        }
        for (int j = 0; j < 4 - i; j++) {
            P[i][j] = P[i][j+i];
        }
        for (int j = 4 - i; j < 4; j++) {
            P[i][j] = tmp[j+i-4];
        }
    }
    free(tmp);
}
//得到的结果数组为:
[0xf0 0x2 0x53 0x77]
[0x72 0x9d 0xad 0x4a]
[0xc9 0xad 0x30 0xc0]
[0x59 0xaf 0xb3 0xc5]

列混合

列混合是在这个加密逻辑中,比较复杂的操作,是通过将上一步行位移之后的结果,与一个给定的矩阵进行矩阵乘法。不过这个乘法,是在扩展域的乘法和加法。

//列混淆左乘矩阵
const unsigned char MixArray[4][4] =
{
0x02, 0x03, 0x01, 0x01,
0x01, 0x02, 0x03, 0x01,
0x01, 0x01, 0x02, 0x03,
0x03, 0x01, 0x01, 0x02
};

void MixColum(unsigned char(*PlainArray)[4])
{
    //定义变量
    unsigned char ArrayTemp[4][4];
    //初始化变量
    memcpy(ArrayTemp, PlainArray, 16);
    //矩阵乘法 4*4
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            PlainArray[i][j] =
            MixArray[i][0] * ArrayTemp[0][j] +
            MixArray[i][1] * ArrayTemp[1][j] +
            MixArray[i][2] * ArrayTemp[2][j] +
            MixArray[i][3] * ArrayTemp[3][j];
        }
    }
}

在扩展域内的加法等于异或运算,而在扩展域内的乘法就是伽罗瓦域乘法。对于伽罗瓦域乘法有兴趣的伽罗瓦域(有限域)
所以需要对这个矩阵乘法,进行伽罗瓦域乘法计算替换。这里不展开这个计算方法。对于逆列混合的时候,不是对矩阵做除法,而是使用逆矩阵,进行正向的乘法计算即可。

对称加密的开发场景应用

在实际的开发场景中,AES加密是用的最多的,第一是因为安全性是有公认的保障的,安全级别足够。第二是加密的速度比较快,作为数据加密算法合适。目前HTTPS的数据传输就是使用的AES加密。

使用场景

一般对称加密的主要应用场景是业务数据信息传输,防止消息被窃听。

  1. 对安全有极高要求的通讯软件。例如:军事领域,安全领域。
  2. 行业安全通信标准。例如:https。

可以简单概括为:只要是在网络中进行传输的数据,如果有防窃听的诉求,都需要对称加密的接入。

使用步骤

  1. 双方互换AES密钥。
  2. 密钥进行明文AES加密。
  3. 使用对方的AES密钥进行解密。

示例代码


import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AESCryptoUtil {

    /** AES密钥位数 */
    public static int SECRET_KEY_LENGTH = 128;

    /**
     * 生成AES密钥
     * 
     * @return
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static byte[] getAutoCreateAESKey() throws NoSuchAlgorithmException {
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(128);//要生成多少位,只需要修改这里即可128, 192或256  
        SecretKey sk = kg.generateKey();
        byte[] skBuffer = sk.getEncoded();
        return skBuffer;
    }

    /**
     * 使用AES对称算法进行加密
     * 
     * @param secretKey
     * @param text
     * @return
     * @throws NoSuchPaddingException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidKeyException 
     * @throws UnsupportedEncodingException 
     * @throws BadPaddingException 
     * @throws IllegalBlockSizeException 
     */
    public static byte[] getAESEncode(byte[] secretKey,
                                      String text) throws NoSuchAlgorithmException,
                                                   NoSuchPaddingException, InvalidKeyException,
                                                   IllegalBlockSizeException, BadPaddingException,
                                                   UnsupportedEncodingException {
        SecretKeySpec sKeySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
        byte[] result = cipher.doFinal(text.getBytes("UTF-8"));
        return result;
    }

    /**
     * AES对称算法 解密
     * 
     * @param secretKey
     * @param text
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     */
    public static String getAESDecode(byte[] secretKey,
                                      byte[] text) throws NoSuchAlgorithmException,
                                                   NoSuchPaddingException, InvalidKeyException,
                                                   IllegalBlockSizeException, BadPaddingException,
                                                   UnsupportedEncodingException {
        SecretKeySpec sKeySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
        byte[] result = cipher.doFinal(text);
        return new String(result, "UTF-8");
    }

}

运算效率测试

AES加密

对长度500字节的内容进行加密1000次操作,耗时383毫秒。数据大小约为500KB。

public static void main(String[] args) {
        String content
            =
            "1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvw";
        String key = "1234567890abcdef";
        Long timer = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            encrypt(content, key);
        }
        System.out.println("time:" + (System.currentTimeMillis() - timer));
    }

image.png

AES解密

对生成的密文进行1000次解密,总共耗时344毫秒,大小同样为500KB。与加密性能基本一致。因为从上面的AES加解密步骤可以看到,其正向加密和逆向解密的复杂度是基本一模一样的。所以加解密的耗时基本一致,也在预料之中。

public static void main(String[] args) {
        String content
            =
            "1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvw";
        String key = "1234567890abcdef";
        Long timer = System.currentTimeMillis();
        String res = encrypt(content, key);

        for (int i = 0; i < 1000; i++) {
            decrypt(res, key);
        }
        System.out.println("time:" + (System.currentTimeMillis() - timer));
    }

image.png

交付场景思考

目前对称加密在交付项目上,可以对很多场景进行数据加密。例如:聊天沟通场景,用户A和用户B之间进行消息发送,文件发送,图片发送等等。都需要保证安全的话,就需要对消息进行加密传输。来保证就算被人中间窃取信息,也无法被获取信息。
但是这里有一个比较重要的问题是需要进行:密钥交换。如果保证在协商秘钥期间不会中间人给攻击,就涉及到了比较复杂的密钥交换环节。有感兴趣的可以看:DH密钥交换

目录
相关文章
|
2月前
|
存储 安全 数据安全/隐私保护
Codota的数据加密技术包括静态数据加密和传输中的数据加密
Codota的数据加密技术包括静态数据加密和传输中的数据加密
58 4
|
2月前
|
安全 数据库 数据安全/隐私保护
对称加密与非对称加密的区别
对称加密与非对称加密的区别
207 64
|
3天前
|
JSON 供应链 搜索推荐
淘宝APP分类API接口:开发、运用与收益全解析
淘宝APP作为国内领先的购物平台,拥有丰富的商品资源和庞大的用户群体。分类API接口是实现商品分类管理、查询及个性化推荐的关键工具。通过开发和使用该接口,商家可以构建分类树、进行商品查询与搜索、提供个性化推荐,从而提高销售额、增加商品曝光、提升用户体验并降低运营成本。此外,它还能帮助拓展业务范围,满足用户的多样化需求,推动电商业务的发展和创新。
20 5
|
4天前
|
存储 缓存 人工智能
深度解析CPFS 在 LLM 场景下的高性能存储技术
本文深入探讨了CPFS在大语言模型(LLM)训练中的端到端性能优化策略,涵盖计算端缓存加速、智能网卡加速、数据并行访问及数据流优化等方面。重点分析了大模型对存储系统的挑战,包括计算规模扩大、算力多样性及数据集增长带来的压力。通过分布式P2P读缓存、IO加速、高性能存算通路技术以及智能数据管理等手段,显著提升了存储系统的吞吐量和响应速度,有效提高了GPU利用率,降低了延迟,从而加速了大模型的训练进程。总结了CPFS在AI训练场景中的创新与优化实践,为未来大模型发展提供了有力支持。
|
12天前
|
安全 算法 网络协议
【网络原理】——图解HTTPS如何加密(通俗简单易懂)
HTTPS加密过程,明文,密文,密钥,对称加密,非对称加密,公钥和私钥,证书加密
|
1月前
|
安全 前端开发 Android开发
探索移动应用与系统:从开发到操作系统的深度解析
在数字化时代的浪潮中,移动应用和操作系统成为了我们日常生活的重要组成部分。本文将深入探讨移动应用的开发流程、关键技术和最佳实践,同时分析移动操作系统的核心功能、架构和安全性。通过实际案例和代码示例,我们将揭示如何构建高效、安全且用户友好的移动应用,并理解不同操作系统之间的差异及其对应用开发的影响。无论你是开发者还是对移动技术感兴趣的读者,这篇文章都将为你提供宝贵的见解和知识。
|
2月前
|
存储 缓存 监控
后端开发中的缓存机制:深度解析与最佳实践####
本文深入探讨了后端开发中不可或缺的一环——缓存机制,旨在为读者提供一份详尽的指南,涵盖缓存的基本原理、常见类型(如内存缓存、磁盘缓存、分布式缓存等)、主流技术选型(Redis、Memcached、Ehcache等),以及在实际项目中如何根据业务需求设计并实施高效的缓存策略。不同于常规摘要的概述性质,本摘要直接点明文章将围绕“深度解析”与“最佳实践”两大核心展开,既适合初学者构建基础认知框架,也为有经验的开发者提供优化建议与实战技巧。 ####
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
1月前
|
SQL 安全 算法
网络安全之盾:漏洞防御与加密技术解析
在数字时代的浪潮中,网络安全和信息安全成为维护个人隐私和企业资产的重要防线。本文将深入探讨网络安全的薄弱环节—漏洞,并分析如何通过加密技术来加固这道防线。文章还将分享提升安全意识的重要性,以预防潜在的网络威胁,确保数据的安全与隐私。
67 2
|
2月前
|
安全 算法 网络安全
网络安全的盾牌与剑:漏洞防御与加密技术深度解析
在数字信息的海洋中,网络安全是航行者不可或缺的指南针。本文将深入探讨网络安全的两大支柱——漏洞防御和加密技术,揭示它们如何共同构筑起信息时代的安全屏障。从最新的网络攻击手段到防御策略,再到加密技术的奥秘,我们将一起揭开网络安全的神秘面纱,理解其背后的科学原理,并掌握保护个人和企业数据的关键技能。
63 3

热门文章

最新文章