0


网络安全实验——安全通信软件safechat的设计

网络安全实验——安全通信软件safechat的设计

仅供参考,请勿直接抄袭,抄袭者后果自负。

仓库地址:

后端地址:https://github.com/yijunquan-afk/safechat-server

前端地址: https://github.com/yijunquan-afk/safechat-client

CosUpload.java中的COS设置,需要自己配

image-20230317214314077

1 设计要求

结合所学安全机制设计实现一个简单的安全通信软件,包含机密性,消息认证等基本功能。并考虑其中涉及的密钥分配方式与机密性算法等相关问题的解决.实现方法不限,使用机制不限。

要求:

1、 独立完成

2、 具有完整的流程设计,报文格式等相关分析。

3、 具备自圆其说的安全性设计思考

2 设计分工

3 设计原理

SHA-2

SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发[3],由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

RSA

RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。

极大整数做因数分解的难度决定了 RSA 算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA 算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的 RSA 钥匙才可能被强力方式破解。到2020年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。

WebSocket协议

WebSocket是双向的,在客户端-服务器通信的场景中使用的全双工协议,与HTTP不同,它以ws://或wss://开头。它是一个有状态协议,这意味着客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止。在通过客户端和服务器中的任何一方关闭连接之后,连接将从两端终止。

以客户端-服务器通信为例,每当启动客户端和服务器之间的连接时,客户端-服务器进行握手随后创建一个新的连接,该连接将保持活动状态,直到被他们中的任何一方终止。建立连接并保持活动状态后,客户端和服务器将使用相同的连接通道进行通信,直到连接终止。

新建的连接被称为WebSocket。一旦通信链接建立和连接打开后,消息交换将以双向模式进行,客户端-服务器之间的连接会持续存在。如果其中任何一方(客户端服务器)宕掉或主动关闭连接,则双方均将关闭连接。套接字的工作方式与HTTP的工作方式略有不同,状态代码101表示WebSocket中的交换协议。

image-20230317210412500

JWT

JWT就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密,签名等相关处理。

基于JWT认证

首先,前端通过Wb表单将自己的用戶名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。

2、后端核对用戶名和密码成功后,将用戶的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同11.Zzz.xx的字符串。token head.payload.signature

3、后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage.上,退出登录时前端删除保存的JWT即可。

4、前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)

5、后端检查JWT是否存在,如存在验证JWT的有效性。检查签名是否正确,检查Token是否过期,检查Token的接收方是否是自己(可选)

JWT结构

jwt生成的字符串包含有三部分

1、 jwt头信息部分header:标头通常由两部分组成:令牌的类型(即JWT所使用的签名算法,例如HMAC、SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分。

2、 在效载荷Payload:令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用戶)和其他数据的声明。同样的,它会使用Ba$64编码组成JWT结构的第二部分

3、 签名哈希Signature:header和payload都是结果Base64编码过的,中间用.隔开,第三部分就是前面两部分合起来做签名,密钥绝对自己保管好,签名值同样做Base64编码拼接在JWT后面。(签名并编码)

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法(荷兰语发音: [ˈrɛindaːl],音似英文的“Rhine doll”),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。

严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中两者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度均可以是128,192或256比特。加密过程中使用的密钥是由Rijndael密钥生成方案产生。

大多数AES计算是在一个特别的有限域完成的。

AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:

① AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。

② SubBytes—透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。

③ ShiftRows—将矩阵中的每个横列进行循环式移位。

④ MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。

4 整体设计方案

网络协议

本次设计中,我使用了HTTP协议处理一般的网络请求:如登录、注册、好友列表获取、个人信息获取、头像更新等功能。

而好友之间点对点的通信,为了持续快速地沟通,我是用WebSocket协议来处理信息发送请求。

客户端技术选型

客户端负责的是与用户进行交互,因此在实用之外还需要考虑到界面美观整洁,以给用户带来良好的使用体验。因此,前端选择使用 vue + AntDesign 组件库进行界面构建。另一方面,由于需要建立 WebSocket 连接,发送 WebSocket 请求,因此需要引入 WebSocket 相关功能的实现。这里使用的是 socket.io 这一 NodeJS 第三方模块。

image-20230317210728995

服务端技术选型

对于服务端,采用了 Java + SpringBoot 为大框架来进行服务端的开发。数据库采用的是经典的关系型数据库 MySql。同时为了建立 WebSocket 连接,处理 WebSocket 请求,选择了 socket.io 的一个 Java 移植版本 netty-socketio。netty-socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架,可用于服务端推送消息给客户端。

image-20230317210745758

整体功能说明

本系统主要包含六个大的功能模块:登陆注册、用户信息获取、信息发送、好友列表显示、头像上传以及退出系统。其中信息发送是本次课程设计最重要的部分,是安全通信的主要体现。

image-20230317210825820

5 安全加密部分代码说明

整体设计

image-20230317210912033

HTTP加密

Token产生

privatestaticStringsign(String userId,String password){Algorithm algorithm =Algorithm.HMAC256(password);String token = JWT.create().withClaim(CLAIM_USERID_NAME,userId).withExpiresAt(newDate(System.currentTimeMillis()+EXPIRED_TIME/2)).sign(algorithm);return token;}/**
* 生成一个登录token
* @param userId
* @param password
* @return
*/publicstaticStringloginSign(String userId,String password){String token =sign(userId,password);
    cache.putToken(token,token);return token;}

每次登录产生Token,并存储在前端的localStorage中,每次发送HTTP的POST和GET请求时加在HTTP Header中的Authorization位。(解决XSS和XSRF问题)

Token认证

后端接收HTTP请求时需要认证Token。

如此做可以认证发送HTTP请求的用户身份,适用于所有HTTP请求

/**
     * 验证客户端传来token是否有效
     * 验证逻辑顺序如下:
     * 1. token是否为空
     * 2. token中账号是否存在
     * 3. 根据token中账号从数据库中获取真实密码等用户信息,并验证用户信息是否有效
     */publicstaticvoidverifyToken(String clientToken,stu.software.chatroom.common.CommonService commonService){if(!StringUtils.hasText(clientToken)){//token为空thrownewRuntimeException("无登录令牌!");}//从客户端登录令牌中获取当前用户账号String userId = JWT.decode(clientToken).getClaim(CLAIM_USERID_NAME).asString();if(!StringUtils.hasText(userId)){//token中账号不存在thrownewRuntimeException("登录令牌失效!");}//取出缓存中的登录令牌String cacheToken = cache.getToken(clientToken);if(!StringUtils.hasText(cacheToken)){//缓存中没有登录令牌thrownewRuntimeException("登录令牌失效!");}User user = commonService.getUserById(userId);if(user==null){//用户不存在thrownewRuntimeException("用户不存在!");}//验证Token有效性try{Algorithm algorithm =Algorithm.HMAC256(user.getU_pwd());JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_USERID_NAME,userId).build();//构建验证器
            jwtVerifier.verify(cacheToken);}catch(TokenExpiredException e){//令牌过期,刷新令牌String newToken =sign(userId,user.getU_pwd());
            cache.putToken(clientToken,newToken);}catch(Exception e){
            e.printStackTrace();//令牌验证未通过thrownewRuntimeException("令牌错误!请登录。");}

注册密码加密

使用SHA256加密注册时用户使用的密码,数据库中存的是密文,这样可防止数据库被攻击导致密码泄露。
image-20230317211223024

/***
* 利用Apache的工具类实现SHA-256加密
* @return str 加密后的报文
*/publicstaticStringgetSHA256Str(String str){MessageDigest messageDigest;String encodeSir = str;try{
        messageDigest =MessageDigest.getInstance("SHA-256");byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
        encodeSir =Hex.encodeHexString(hash);}catch(NoSuchAlgorithmException e){
        e.printStackTrace();}return encodeSir;}/**
* 通过该方法将密码加密(实际上并没有)
*/publicstaticStringencodePwd(String u_pwd){// 密码通过此方法解密并再加密returngetSHA256Str(u_pwd);}

登录密码加密

登录时,前端输入明文密码,使用SHA256加密该密码以后,再加数据发送到后端。后端根据该加密后的密码与数据库比对,从而验证用户身份。

此做法避免了前端请求数据被拦截导致密码泄露。
image-20230317211402183

import{ sha256 }from'js-sha256';/**
 * 加密方法
 */exportfunctionPASSWORD(str){let encodedStr = str;
    encodedStr =sha256(encodedStr);return encodedStr;}constlogin=()=>{post("/user/login",{u_name: u_name.value,u_pwd:PASSWORD(u_pwd.value),}).then((res)=>{
      tip.success(res.message);let token = res.data;setLocalToken(token);
      router.push({name:"Room",query:{usr: u_name.value }});}).catch((err)=>{
      tip.error("账号密码错误!");});};

密钥分配——使用Keytool

参考教程 https://blog.csdn.net/m0_59579040/article/details/124811147

keytool 是个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。它还允许用户储存他们的通信对等者的公钥(以证书形式)。

在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。

通过如下步骤可以产生证书和公钥

keytool -genkeypair -storetype PKCS12 -alias yjq - -keyalg RSA -keysize 1024 -dname "CN=xxx, OU=xxx, O=xxx, L=xx, ST=xx, C=CN" -keystore D:\mygit\大三下笔记\网安课设\safechat-server\src\main\resources\keys-and-certs\yjq.keystore -keypass 123456 -storepass 123456 -validity 36500 -v  

产生二进制文件yjq.keystore,以上部分可由脚本生成。

经过KeyStore的相关操作生成公钥、证书和私钥

image-20230317211728014

当用户需要公钥和私钥时,只需要调用相关方法即可。

publicstaticvoidgenKeyPair(String name)throwsException{//以 PKCS12 规格,创建 KeyStore  KeyStore keyStore =KeyStore.getInstance("PKCS12");  
    path ="keys-and-certs/"+ name +".keystore";//载入 jks 和该 jks 的密码 到 KeyStore 内  
    keyStore.load(newFileInputStream(newClassPathResource("keys-and-certs/yjq.keystore").getFile()),"123456".toCharArray());// 要获取 key,需要提供 KeyStore 的别名 和该 KeyStore 的密码  // 获取 keyStore 内所有别名 alias  Enumeration<String> aliases = keyStore.aliases();String alias =null;  
    alias = aliases.nextElement();char[] keyPassword ="123456".toCharArray();  
    keyPairString.clear();//私钥  
    privateKey =(PrivateKey) keyStore.getKey(alias, keyPassword);  
    keyPairString.put("PR",newString(Base64.getEncoder().encode(privateKey.getEncoded())));//证书  Certificate certificate = keyStore.getCertificate(alias);//公钥  
    publicKey = certificate.getPublicKey();  
    keyPairString.put("PU",newString(Base64.getEncoder().encode(publicKey.getEncoded())));}

使用公钥加密保证消息认证和机密性

参考教程https://blog.csdn.net/m0_59579040/article/details/124811147.

A和B进行通信,首先使用A的私钥对报文M进行加密——数字签名;然后A用B的公钥对上述结果进行加密——保证了保密性。

B收到消息后,用B的私钥解密,再用A的公钥验证签名。

image-20230317211920550

这里我使用RSA作为加密算法、SHA1WithRSA作为签名算法,签名和加密的操作实现在类RSAUtils.java中。

签名

/** 
* 私钥签名 
* @param content 字符串 
* @param priKey 私钥 
* @return 
* @throws Exception 
*/publicstaticbyte[]sign(String content,PrivateKey priKey)throwsException{Signature signature =Signature.getInstance(SIGALG);  
    signature.initSign(priKey);  
    signature.update(content.getBytes());return signature.sign();}/** 
* 公钥验证签名 
* @param content 字符串 
* @param sign 签名 
* @param pubKey 公钥 
* @return 身份是否真实 
* @throws Exception 
*/publicstaticbooleanverify(String content,byte[] sign,PublicKey pubKey)throwsException{Signature signature =Signature.getInstance(SIGALG);  
    signature.initVerify(pubKey);  
    signature.update(content.getBytes());return signature.verify(sign);}

加密解密

/** 
* RSA公钥加密 
* 
* @param content       加密字符串 
* @param publicKey 公钥 
* @return 密文 
* @throws Exception 加密过程中的异常信息 
*/publicstaticStringencrypt(String content,String publicKey)throwsException{//base64编码的公钥  byte[] decoded =Base64.getMimeDecoder().decode(publicKey);RSAPublicKey pubKey =(RSAPublicKey)KeyFactory.getInstance(KEYALG).generatePublic(newX509EncodedKeySpec(decoded));System.out.println(pubKey.getAlgorithm());//RSA加密  Cipher cipher =Cipher.getInstance(KEYALG);  
    cipher.init(Cipher.ENCRYPT_MODE, pubKey);String outStr =Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes("UTF-8")));return outStr;}/** 
* RSA私钥解密 
* 
* @param content        加密字符串 
* @param privateKey 私钥 
* @return 明文 
* @throws Exception 解密过程中的异常信息 
*/publicstaticStringdecrypt(String content,String privateKey)throwsException{//64位解码加密后的字符串  byte[] inputByte =Base64.getMimeDecoder().decode(content);//        //base64编码的私钥  byte[] decoded =Base64.getMimeDecoder().decode(privateKey);RSAPrivateKey priKey =(RSAPrivateKey)KeyFactory.getInstance("RSA").generatePrivate(newPKCS8EncodedKeySpec(decoded));//RSA解密  Cipher cipher =Cipher.getInstance("RSA");  
    cipher.init(Cipher.DECRYPT_MODE, priKey);String outStr =newString(cipher.doFinal(inputByte));return outStr;}

使用AES加密消息

因为公钥加密的消息认证比较费时间,所以当两个用户建立消息通信时由一方产生会话密钥,使用公钥加密来传送会话密钥并认证身份。身份认证完成后,使用该会话密钥加密消息,其中使用对称加密技术AES加密消息。

消息报文格式如下:
image-20230317212104464
1、 id:报文标识id;

2、 time:报文发送时间

3、 content:报文内容(加密)

4、 type:报文类型:会话密钥消息/公钥消息

5、 sender_name:发送者

6、 receiver_name:接收者

7、 sign:发送者签名。

加密过程如下:

publicfinalclassAESUtils{privatestaticfinalString ALGORITHM ="AES";publicstaticStringgenAesSecret(){try{KeyGenerator kg =KeyGenerator.getInstance("AES");//下面调用方法的参数决定了生成密钥的长度,可以修改为128, 192或256  
            kg.init(256);SecretKey sk = kg.generateKey();byte[] b = sk.getEncoded();String secret =Base64.encodeBase64String(b);return secret;}catch(NoSuchAlgorithmException e){  
            e.printStackTrace();thrownewRuntimeException("没有此算法");}}/** 
     * 根据密钥对指定的明文plainText进行加密. 
     * 
     * @param plainBytes 明文 
     * @param keyBytes   密码 
     * @return 加密后的密文. 
     * @since 0.0.8 
     */publicstaticbyte[]encrypt(byte[] plainBytes,byte[] keyBytes){try{SecretKey secretKey =getSecretKey(keyBytes);Cipher cipher =Cipher.getInstance(ALGORITHM);  
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);return cipher.doFinal(plainBytes);}catch(Exception e){thrownewRuntimeException(e);}}/** 
     * 根据密钥对指定的密文 cipherBytes 进行解密. 
     * 
     * @param cipherBytes 加密密文 
     * @param keyBytes    秘钥 
     * @return 解密后的明文. 
     * @since 0.0.8 
     */publicstaticbyte[]decrypt(byte[] cipherBytes,byte[] keyBytes){try{SecretKey secretKey =getSecretKey(keyBytes);Cipher cipher =Cipher.getInstance(ALGORITHM);  
            cipher.init(Cipher.DECRYPT_MODE, secretKey);return cipher.doFinal(cipherBytes);}catch(Exception e){thrownewRuntimeException(e);}}/** 
     * 获取加密 key 
     * @param keySeed seed 
     * @return 结果 
     * @since 0.0.8 
     */privatestaticSecretKeygetSecretKey(byte[] keySeed){try{// 避免 linux 系统出现随机的问题  SecureRandom secureRandom =SecureRandom.getInstance("SHA1PRNG"); 
            secureRandom.setSeed(keySeed);KeyGenerator generator =KeyGenerator.getInstance("AES");  
            generator.init(secureRandom);return generator.generateKey();}catch(Exception e){thrownewRuntimeException(e);}}}

服务端加密

结合RSA与AES的加密如下:

先用公钥加密RSA发送对称加密使用的会话密钥,然后再用会话密钥进行AES对称加密通信。

// 监听客户端发送消息  
socketIOServer.addEventListener(Constants.EVENT_MESSAGE_TO_SERVER,String.class,(client, data, ackSender)->{String sender_name =getParamsByClient(client,"u_name");ObjectMapper mapper =newObjectMapper();Message message = mapper.readValue(data,Message.class);String receiver_name = message.getReceiver_name();if(message.getType().equals(Constants.MASTER_MESSAGE)){//使用公钥加密传送会话密钥  if(AesKey.equals("")){  
            log.info("用户"+ sender_name +"生成会话密钥");AesKey=AESUtils.genAesSecret();  
            message.setContent(AesKey);  
            log.info("用户"+ sender_name +"使用用户"+ sender_name +"的私钥对会话密钥进行签名");String sign =newString(RSAUtils.sign(message.getContent(),RSAUtils.getPrivateKey()),"ISO-8859-1");  
            message.setSign(sign);String result =RSAUtils.encrypt(message.getContent(), publicKeyStringMap.get(receiver_name));  
            log.info("使用用户"+ receiver_name +"的公钥对会话密钥进行加密:"+ result);  
            message.setContent(result);sendMessageToFriend(message.getReceiver_name(), message);}else{return;}}else{//使用会话密钥发送消息  byte[] bytes =AESUtils.encrypt(message.getContent().getBytes(),AesKey.getBytes());String encrypt =newString(bytes,"ISO-8859-1");  
        log.info("用户"+ sender_name +"使用会话密钥加密消息");  
        message.setContent(encrypt);sendMessageToFriend(message.getReceiver_name(), message);}});//  //GBK,  GB2312,UTF-8等一些编码方式为多字节或者可变长编码,原来的字节数组就被改变了,再转回原来的byte[]数组就会发生错误了。  //ISO-8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中 0~127的字符与ASCII码相同,  // 它是单字节的编码方式,在来回切换时不会出现错误。  // 监听客户端接收消息  
socketIOServer.addEventListener("receive_triger",String.class,(client, data, ackSender)->{ObjectMapper mapper =newObjectMapper();Message message = mapper.readValue(data,Message.class);String sender_name = message.getSender_name();String receiver_name = message.getReceiver_name();if(message.getType().equals(Constants.MASTER_MESSAGE)){  
        log.info("收到来自"+ sender_name +"发送给"+ message.getReceiver_name()+"的消息: "+ message.getContent());String result =RSAUtils.decrypt(message.getContent(),RSAUtils.getKeyPair().get("PR"));  
        log.info("用户"+ receiver_name +"使用用户"+ receiver_name +"的私钥对消息进行解密:");  
        message.setContent(result);  
        log.info("用户"+ receiver_name +"使用用户"+ sender_name +"的公钥对消息进行验证签名");Boolean sign =(RSAUtils.verify(message.getContent(), message.getSign().getBytes("ISO-8859-1"), publicKeyMap.get(sender_name)));if(sign){  
            log.info("签名验证成功!身份无误");}else{thrownewException("签名错误!");}receiveMessageFromFriend(message.getReceiver_name(), message);}else{  
        log.info("收到来自"+ sender_name +"发送给"+ message.getReceiver_name()+"的消息: "+ message.getContent());String text =newString(AESUtils.decrypt(message.getContent().getBytes("ISO-8859-1"),AesKey.getBytes()),"UTF-8");  
        log.info("用户"+ receiver_name +"使用会话密钥进行解密");  
        message.setContent(text);receiveMessageFromFriend(message.getReceiver_name(), message);}});

6 演示

登录

image-20230317212322357

进入主页面

可以看到好友列表

image-20230317212354873

同时获取本地密钥库中的公私钥并将其加入公钥库

image-20230317212408120

选择好友进行私聊

选择好友进行私聊,进入聊天界面。

image-20230317212431510

image-20230317212436916

发送消息

在输入框中输入消息,点击发送,接收者和发送者的聊天框都会出现相应的消息。此消息是经过后端AES对称加密解密得到的。

image-20230317212509053

标签: 安全 web安全 java

本文转载自: https://blog.csdn.net/weixin_47692652/article/details/129628914
版权归原作者 雨落俊泉 所有, 如有侵权,请联系我们删除。

“网络安全实验——安全通信软件safechat的设计”的评论:

还没有评论