0
回答
厚积薄发,丰富的公用类库积累,助你高效进行系统开发(8)----非对称加密、BASE64加密、MD5等常用加密处理

俗话说,一个好汉十个帮,众人拾柴火焰高等都说明一个道理,有更多的资源,更丰富的积累,都是助你走向成功,走向顶峰的推动力。

本篇继续继续整理优化已有的共用类库,并继续发表随笔介绍公用类库的接口方法以及详细使用操作,力求给自己继续优化,积攒更丰富的公用类库资源,加深了解的同时,也给大家展现公用类库好的方面。

本篇的公用类库的介绍主题是加密解密的部分,加解密算法包括DES加解密、Base64加解密、

AES RijndaelManaged加解密、MD5加密、以及RSA非对称加密等操作。如果你对前面的类库介绍文章有兴趣,可以参考下面的连接了解。 

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(7)

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(6)

厚积薄发,丰富的公用类库积累,助你高效进行系统开发(5)


1、非对称加密、解密、验证辅助类 RSASecurityHelper
 

本人开发过很多共享软件,在共享软件注册方面积累了一些自己的经验,其中采用非对称加密方式实现注册码验证的操作,就是其中一个比较重要的步骤,由于其逻辑不可逆的特点,采用非对称加密方式,较一般的对称加密方式,能够隐藏授权的逻辑,因此具有更好的效果。 这个非对称的解密解密、验证操作,是我所有共享软件里面用到的授权操作,很早很多朋友就这个问题问过我很多次,现在奉献给大家注册码实现的思路及操作接口,希望大家能够在享受代码带来的便利外,也可以提高自己对知识产权的保护。

一般来说,非对称加密方式,结合代码混淆和强名称验证,是比较好的实现注册授权机制的功能。 

实现效果 
1)本辅助类主要是用来方便实现非对称加密、解密、验证等相关操作。 
2)非对称加密有RSA、椭圆曲线等,最流行的是RSA。所谓的非对称,是因为它有两个密钥,一个称为公钥,一个称为私钥。公钥是可以对外发布的,而私钥是自己保存的。用公钥加密,必须用私钥解密,反之,用私钥加密必须用公钥解密。所以,我们可以在软件中保存公钥的内容,这个是可以公开的;私钥我们可以自己留着。别人拿不到私钥,根本计算不出注册码来。 
3)使用辅助类RSASecurityHelper实现注册机制如下:
a. 注册码产生过程:
注册信息(包括用户名等信息)---> RSA私钥加密 ---> 注册码。
b. 软件验证注册码过程:
注册码 ---> RSA公钥验证 ---> 验证成功或失败。


  
实现代码 
1)辅助类提供的方法接口如下所示:
///   <summary>     
///  非对称加密生成的私钥和公钥     
///   </summary>     
///   <param name="privateKey"> 私钥 </param>     
///   <param name="publicKey"> 公钥 </param>     
public  static  void GenerateRSAKey( out  string privateKey,  out  string publicKey)   
  
#region 非对称数据加密(公钥加密)    
   
///   <summary>     
///  非对称加密字符串数据,返回加密后的数据    
///   </summary>     
///   <param name="publicKey"> 公钥 </param>     
///   <param name="originalString"> 待加密的字符串 </param>     
///   <returns> 加密后的数据 </returns>     
public  static  string RSAEncrypt( string publicKey,  string originalString)    
   
///   <summary>     
///  非对称加密字节数组,返回加密后的数据    
///   </summary>     
///   <param name="publicKey"> 公钥 </param>     
///   <param name="originalBytes"> 待加密的字节数组 </param>     
///   <returns> 返回加密后的数据 </returns>     
public  static  string RSAEncrypt( string publicKey,  byte[] originalBytes)   
  
#endregion   
  
#region 非对称解密(私钥解密)    
   
///   <summary>     
///  非对称解密字符串,返回解密后的数据    
///   </summary>     
///   <param name="privateKey"> 私钥 </param>     
///   <param name="encryptedString"> 待解密数据 </param>     
///   <returns> 返回解密后的数据 </returns>     
public  static  string RSADecrypt( string privateKey,  string encryptedString)    
   
///   <summary>     
///  非对称解密字节数组,返回解密后的数据    
///   </summary>     
///   <param name="privateKey"> 私钥 </param>     
///   <param name="encryptedBytes"> 待解密数据 </param>     
///   <returns></returns>     
public  static  string RSADecrypt( string privateKey,  byte[] encryptedBytes)   
  
#endregion   
  
#region 非对称加密签名、验证    
   
///   <summary>     
///  使用非对称加密签名数据    
///   </summary>     
///   <param name="privateKey"> 私钥 </param>     
///   <param name="originalString"> 待加密的字符串 </param>     
///   <returns> 加密后的数据 </returns>     
public  static  string RSAEncrypSignature( string privateKey,  string originalString)    
          
///   <summary>     
///  对私钥加密签名的字符串,使用公钥对其进行验证    
///   </summary>     
///   <param name="originalString"> 未加密的文本,如机器码 </param>     
///   <param name="encrytedString"> 加密后的文本,如注册序列号 </param>     
///   <returns> 如果验证成功返回True,否则为False </returns>     
public  static  bool Validate( string originalString,  string encrytedString)    
   
///   <summary>     
///  对私钥加密的字符串,使用公钥对其进行验证    
///   </summary>     
///   <param name="originalString"> 未加密的文本,如机器码 </param>     
///   <param name="encrytedString"> 加密后的文本,如注册序列号 </param>     
///   <param name="publicKey"> 非对称加密的公钥 </param>     
///   <returns> 如果验证成功返回True,否则为False </returns>     
public  static  bool Validate( string originalString,  string encrytedString,  string publicKey)   
  

#endregion   

2) 辅助类RSASecurityHelper的使用例子代码如下所示 
// 生成加解密私钥、公钥    
string publicKey =  "";    
string privateKey =  "";    
RSASecurityHelper.GenerateRSAKey( out privateKey,  out publicKey);    
   
string originalString =  " testdata ";    
string encryptedString = RSASecurityHelper.RSAEncrypt(publicKey, originalString);    
string originalString2 = RSASecurityHelper.RSADecrypt(privateKey, encryptedString);     
if (originalString == originalString2)    
{    
    MessageUtil.ShowTips( " 解密完全正确 ");    
}    
else   
{    
    MessageUtil.ShowWarning( " 解密失败 ");    
}    
   
string regcode = RSASecurityHelper.RSAEncrypSignature(privateKey, originalString);    
bool validated = RSASecurityHelper.Validate(originalString, regcode, publicKey);    

MessageUtil.ShowTips( validated ? "验证成功" : "验证失败");     


3)实际项目使用的注册验证代码如下所示。

///   <summary>     
///  每次程序运行时候,检查用户是否注册    
///   </summary>     
///   <returns> 如果用户已经注册, 那么返回True, 否则为False </returns>     
public  bool CheckRegister()    
{    
     //  先获取用户的注册码进行比较    
     string serialNumber =  string.Empty;  // 注册码    
    RegistryKey reg = Registry.CurrentUser.OpenSubKey(UIConstants.SoftwareRegistryKey,  true);    
     if ( null != reg)    
    {    
        serialNumber = ( string)reg.GetValue( " SerialNumber ");    
        Portal.gc.bRegisted = Portal.gc.Register(serialNumber);    
    }    
   
     return Portal.gc.bRegisted;    
}    
   
///   <summary>     
///  调用非对称加密方式对序列号进行验证    
///   </summary>     
///   <param name="serialNumber"> 正确的序列号 </param>     
///   <returns> 如果成功返回True,否则为False </returns>     
public  bool Register(String serialNumber)    
{    
     string hardNumber = HardwareInfoHelper.GetCPUId();    
     return RSASecurityHelper.Validate(hardNumber, serialNumber);    
}    
   
public  string GetHardNumber()    
{    
     return HardwareInfoHelper.GetCPUId(); 

}   

 

2、 DES对称加解密、AES RijndaelManaged加解密、Base64加密解密、MD5加密等操作辅助类 EncodeHelper 

实现效果 
 1)本辅助类主要是用来方便实现DES对称加解密、AES RijndaelManaged加解密、Base64加密解密、MD5加密等操作。 
2)本辅助类实现多种加解密处理,可以实现字符串、文件、文件流等加密处理。该加密辅助类汇集各种加密处理逻辑,是一个非常常用、方便应对各种加密处理的辅助类。
 
实现代码 
1)辅助类提供的方法接口如下所示:
///   <summary>     
///  使用默认加密    
///   </summary>     
///   <param name="strText"></param>     
///   <returns></returns>     
public  static  string DesEncrypt( string strText)    
   
///   <summary>     
///  使用默认解密    
///   </summary>     
///   <param name="strText"> 解密字符串 </param>     
///   <returns></returns>     
public  static  string DesDecrypt( string strText)    
   
///   <summary>      
///  加密字符串,注意strEncrKey的长度为8位    
///   </summary>      
///   <param name="strText"> 待加密字符串 </param>      
///   <param name="strEncrKey"> 加密键 </param>      
///   <returns></returns>      
public  static  string DesEncrypt( string strText,  string strEncrKey)    
   
///   <summary>      
///  解密字符串,注意strEncrKey的长度为8位    
///   </summary>      
///   <param name="strText"> 待解密的字符串 </param>      
///   <param name="sDecrKey"> 解密键 </param>      
///   <returns> 解密后的字符串 </returns>      
public  static  string DesDecrypt( string strText,  string sDecrKey)    
   
///   <summary>      
///  加密数据文件,注意strEncrKey的长度为8位    
///   </summary>      
///   <param name="m_InFilePath"> 待加密的文件路径 </param>      
///   <param name="m_OutFilePath"> 输出文件路径 </param>      
///   <param name="strEncrKey"> 加密键 </param>      
public  static  void DesEncrypt( string m_InFilePath,  string m_OutFilePath,  string strEncrKey)    
   
///   <summary>      
///  解密数据文件,注意strEncrKey的长度为8位    
///   </summary>      
///   <param name="m_InFilePath"> 待解密的文件路径 </param>      
///   <param name="m_OutFilePath"> 输出路径 </param>      
///   <param name="sDecrKey"> 解密键 </param>      
public  static  void DesDecrypt( string m_InFilePath,  string m_OutFilePath,  string sDecrKey)   
       
#endregion   
  
#region 对称加密算法AES RijndaelManaged加密解密    
   
///   <summary>     
///  对称加密算法AES RijndaelManaged加密(RijndaelManaged(AES)算法是块式加密算法)    
///   </summary>     
///   <param name="encryptString"> 待加密字符串 </param>     
///   <returns> 加密结果字符串 </returns>     
public  static  string AES_Encrypt( string encryptString)    
   
///   <summary>     
///  对称加密算法AES RijndaelManaged加密(RijndaelManaged(AES)算法是块式加密算法)    
///   </summary>     
///   <param name="encryptString"> 待加密字符串 </param>     
///   <param name="encryptKey"> 加密密钥,须半角字符 </param>     
///   <returns> 加密结果字符串 </returns>     
public  static  string AES_Encrypt( string encryptString,  string encryptKey)    
   
///   <summary>     
///  对称加密算法AES RijndaelManaged解密字符串    
///   </summary>     
///   <param name="decryptString"> 待解密的字符串 </param>     
///   <returns> 解密成功返回解密后的字符串,失败返源串 </returns>     
public  static  string AES_Decrypt( string decryptString)    
   
///   <summary>     
///  对称加密算法AES RijndaelManaged解密字符串    
///   </summary>     
///   <param name="decryptString"> 待解密的字符串 </param>     
///   <param name="decryptKey"> 解密密钥,和加密密钥相同 </param>     
///   <returns> 解密成功返回解密后的字符串,失败返回空 </returns>     
public  static  string AES_Decrypt( string decryptString,  string decryptKey)    
   
   
///   <summary>     
///  加密文件流    
///   </summary>     
///   <param name="fs"> 文件流 </param>     
///   <returns></returns>     
public  static CryptoStream AES_EncryptStrream(FileStream fs,  string decryptKey)    
   
///   <summary>     
///  解密文件流    
///   </summary>     
///   <param name="fs"> 文件流 </param>     
///   <returns></returns>     
public  static CryptoStream AES_DecryptStream(FileStream fs,  string decryptKey)    
   
///   <summary>     
///  对指定文件加密    
///   </summary>     
///   <param name="InputFile"> 输入文件 </param>     
///   <param name="OutputFile"> 输出文件 </param>     
///   <returns></returns>     
public  static  bool AES_EncryptFile( string InputFile,  string OutputFile)    
   
///   <summary>     
///  对指定的文件解压缩    
///   </summary>     
///   <param name="InputFile"> 输入文件 </param>     
///   <param name="OutputFile"> 输出文件 </param>     
///   <returns></returns>     
public  static  bool AES_DecryptFile( string InputFile,  string OutputFile)   
  
#endregion   
  
#region Base64加密解密    
///   <summary>     
///  Base64是一種使用64基的位置計數法。它使用2的最大次方來代表僅可列印的ASCII 字元。    
///  這使它可用來作為電子郵件的傳輸編碼。在Base64中的變數使用字元A-Z、a-z和0-9 ,    
///  這樣共有62個字元,用來作為開始的64個數字,最後兩個用來作為數字的符號在不同的    
///  系統中而不同。    
///  Base64加密    
///   </summary>     
///   <param name="str"> Base64方式加密字符串 </param>     
///   <returns></returns>     
public  static  string Base64Encrypt( string str)    
   
///   <summary>     
///  Base64解密字符串    
///   </summary>     
///   <param name="str"> 待解密的字符串 </param>     
///   <returns></returns>     
public  static  string Base64Decrypt( string str)   
  
#endregion   
  
#region MD5加密    
   
///   <summary>      
///  使用MD5加密字符串    
///   </summary>      
///   <param name="strText"> 待加密的字符串 </param>      
///   <returns> MD5加密后的字符串 </returns>      
public  static  string MD5Encrypt( string strText)    
   
///   <summary>     
///  使用MD5加密的Hash表    
///   </summary>     
///   <param name="input"> 待加密字符串 </param>     
///   <returns></returns>     
public  static  string MD5EncryptHash(String input)    
   
///   <summary>     
///  使用Md5加密为16进制字符串    
///   </summary>     
///   <param name="input"> 待加密字符串 </param>     
///   <returns></returns>     
public  static  string MD5EncryptHashHex(String input)    
   
///   <summary>     
///  MD5 三次加密算法.计算过程: (QQ使用)    
///  1. 验证码转为大写    
///  2. 将密码使用这个方法进行三次加密后,与验证码进行叠加    
///  3. 然后将叠加后的内容再次MD5一下,得到最终验证码的值    
///   </summary>     
///   <param name="s"></param>     
///   <returns></returns>     
public  static  string EncyptMD5_3_16( string s)   
  
#endregion    
   
///   <summary>     
///  SHA256函数    
///   </summary>     
///   <param name="str"> 原始字符串 </param>     
///   <returns> SHA256结果(返回长度为44字节的字符串) </returns>     
public  static  string SHA256( string str)    
   
///   <summary>     
///  加密字符串(使用MD5+Base64混合加密)    
///   </summary>     
///   <param name="input"> 待加密的字符串 </param>     
///   <returns></returns>     
public  static  string EncryptString( string input)    
   
///   <summary>     
///  解密加过密的字符串(使用MD5+Base64混合加密)    
///   </summary>     
///   <param name="input"> 待解密的字符串 </param>     
///   <param name="throwException"> 解密失败是否抛异常 </param>     
///   <returns></returns>     
public  static  string DecryptString( string input,  bool throwException) 

 

2) 辅助类EncodeHelper的使用例子代码如下所示 
///   <summary>     
///  对字符串加密    
///   </summary>     
///   <returns></returns>     
private  string EncodePassword( string passwordText)    
{    
     return EncodeHelper.MD5Encrypt(passwordText);    

}  

 

private  void btnEncrypt_Click( object sender, EventArgs e)    
{    
     string original =  " 测试加密字符串 ";    
    Console.WriteLine( " original: " + original);    
   
     string encrypt = EncodeHelper.SHA256(original);    
    Console.WriteLine( " EncodeHelper.SHA256 " + encrypt);    
   
     // DES加解密    
    encrypt = EncodeHelper.DesEncrypt(original);    
    Console.WriteLine( " EncodeHelper.DesEncrypt: " + encrypt);    
   
     string decrypt = EncodeHelper.DesDecrypt(encrypt);    
    Console.WriteLine( " EncodeHelper.DesDecrypt: " + decrypt);    
   
     // MD5加密    
    encrypt = EncodeHelper.MD5Encrypt(original);    
    Console.WriteLine( " EncodeHelper.MD5Encrypt: " + encrypt);    
    encrypt = EncodeHelper.MD5EncryptHash(original);    
    Console.WriteLine( " EncodeHelper.MD5EncryptHash: " + encrypt);    
    encrypt = EncodeHelper.MD5EncryptHashHex(original);    
    Console.WriteLine( " EncodeHelper.MD5EncryptHashHex " + encrypt);    
    encrypt = EncodeHelper.EncyptMD5_3_16(original);    
    Console.WriteLine( " EncodeHelper.EncyptMD5_3_16: " + encrypt);    
   
     // Base64加解密    
    encrypt = EncodeHelper.Base64Encrypt(original);    
    Console.WriteLine( " EncodeHelper.Base64Encrypt " + encrypt);    
    decrypt = EncodeHelper.Base64Encrypt(encrypt);    
    Console.WriteLine( " EncodeHelper.Base64Encrypt " + decrypt);    
   
    encrypt = EncodeHelper.AES_Encrypt(original);    
    Console.WriteLine( " EncodeHelper.AES_Encrypt " + encrypt);    
    decrypt = EncodeHelper.AES_Decrypt(encrypt);    

    Console.WriteLine("EncodeHelper.AES_Decrypt" + decrypt);   

 

 

3、 MD5各种长度加密字符、验证MD5等操作辅助类 MD5Util 

实现效果 
 1)本辅助类主要是用来方便实现MD5各种长度加密字符、验证MD5等操作。 
 2)MD5即Message-Digest Algorithm 5(信息-摘要算法 5),用于确保信息传输完整一致。是计算机广泛使用的散列算法之一(又译摘要算法、哈希算法)。 
 3)MD5已经广泛使用在为文件传输提供一定的可靠性方面。例如,服务器预先提供一个MD5校验和,用户下载完文件以后,用MD5算法计算下载文件的MD5校验和,然后通过检查这两个校验和是否一致,就能判断下载的文件是否出错
 
实现代码 
1)辅助类提供的方法接口如下所示:
///   <summary>     
///  获得32位的MD5加密    
///   </summary>     
public  static  string GetMD5_32( string input)    
   
///   <summary>     
///  获得16位的MD5加密    
///   </summary>     
public  static  string GetMD5_16( string input)    
        
///   <summary>     
///  获得8位的MD5加密    
///   </summary>     
public  static  string GetMD5_8( string input)    
   
///   <summary>     
///  获得4位的MD5加密    
///   </summary>     
public  static  string GetMD5_4( string input)    
   
///   <summary>     
///  添加MD5的前缀,便于检查有无篡改    
///   </summary>     
public  static  string AddMD5Profix( string input)    
   
///   <summary>     
///  移除MD5的前缀    
///   </summary>     
public  static  string RemoveMD5Profix( string input)    
   
///   <summary>     
///  验证MD5前缀处理的字符串有无被篡改    
///   </summary>     
public  static  bool ValidateValue( string input)   
  
#region MD5签名验证    
        
///   <summary>     
///  对给定文件路径的文件加上标签    
///   </summary>     
///   <param name="path"> 要加密的文件的路径 </param>     
///   <returns> 标签的值 </returns>     
public  static  bool AddMD5( string path)    
   
///   <summary>     
///  对给定路径的文件进行验证,如果一致返回True,否则返回False    
///   </summary>     
///   <param name="path"></param>     
///   <returns> 是否加了标签或是否标签值与内容值一致 </returns>     

public static bool CheckMD5(string path)    

2) 辅助类

MD5Util 的使用例子代码如下所示 

 

// 为文件增加MD5编码标签,然后验证是否被修改    
string file =  @" c:\test.xls ";    
bool flag2 = MD5Util.AddMD5(file);    
Console.WriteLine(flag2);    
   
// 对给定路径的文件进行验证,如果一致返回True,否则返回False    
bool flag3 = MD5Util.CheckMD5(file);    

Console.WriteLine(flag3);  

 
4、基于Base64的加密编码辅助类 Base64Util
实现效果 
 1)本辅助类主要是用来方便实现基于Base64的加密编码。 
 2)Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。Base64编码一般在电子邮件中可以查看到,打开一封Email,查看其原始信息(您可以通过收取、导出该邮件用文本编辑器查看)。加密是肯定的,但是加密的目的不是让用户发送非常安全的Email。这种加密方式主要就是“防君子不防小人”。即达到一眼望去完全看不出内容即可。 
 3)采用Base64编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
 
实现代码 
1)辅助类提供的方法接口如下所示:
///   <summary>     
///  使用默认的密码表加密字符串    
///   </summary>     
///   <param name="input"> 待加密字符串 </param>     
///   <returns></returns>     
public  static  string Encrypt( string input)    
   
///   <summary>     
///  使用默认的密码表解密字符串    
///   </summary>     
///   <param name="input"> 待解密字符串 </param>     
///   <returns></returns>     
public  static  string Decrypt( string input)    
   
///   <summary>     
///  获取具有标准的Base64密码表的加密类    
///   </summary>     
///   <returns></returns>     

public static Base64Util GetStandardBase64()    

 
2) 辅助类Base64Util的使用例子代码如下所示 
private  void btnBase64_Click( object sender, EventArgs e)    
{    
     string original =  " 这是一个测试的Base64加密字符串 ";    
     string encrypt = Base64Util.Encrypt(original);    
    Console.WriteLine(encrypt); // 输出内容:6L*Z5pi_5LiA5Liq5rWL6K*V55qEQmFzZTY05Yqg5b*G5b2X56ym5Liy    
   
     string decrypt = Base64Util.Decrypt(encrypt);    
    Debug.Assert(original == decrypt); // 验证相等    

 


5、QQ密码加密操作辅助类 QQEncryptUtil
实现效果 
 1)本辅助类主要是用来方便实现QQ密码加密操作。 
 2)在QQ的很多模拟网页采集数据,需要输入用户账号、密码,其中密码是需要进行加密操作的,一般使用js脚本实现,这里把它转化为C#的代码操作,其实也就是把密码和验证码通过3次MD5加密实现的。 
 
实现代码 
1)辅助类提供的方法接口如下所示:
///   <summary>     
///  QQ根据密码及验证码对数据进行加密    
///   </summary>     
///   <param name="password"> 原始密码 </param>     
///   <param name="verifyCode"> 验证码 </param>     
///   <returns></returns>     
public static string EncodePasswordWithVerifyCode(string password, string verifyCode)   
 
2) 辅助类QQEncryptUtil的使用例子代码如下所示 
private  bool CheckLogin()    
{    
    HttpHelper httpHelper =  new HttpHelper();    
     string refUrl =  " http://ui.ptlogin2.qq.com/cgi-bin/login?appid=3000801&s_url=http%3A%2F%2Fqun.qq.com%2Fgod%2Fsucc.htm&f_url=loginerroralert&lang=2052&bgcolor=ffffff&style=1&low_login=1&link_target=blank&target=self&hide_title_bar=1&dummy=1 ";    
   
     string verifyCode =  this.txtVerify.Text;    
     string postData =  string.Format( " u={0}&p={1}&verifycode={2}&aid=3000801&u1=http%3A%2F%2Fqun.qq.com%2Fgod%2Fsucc.htm&h=1&ptredirect=0&ptlang=2052&from_ui=1&dumy=&fp=loginerroralert ",    
         this.txtUsername.Text, QQEncryptUtil.EncodePasswordWithVerifyCode( this.txtPassword.Text, verifyCode), verifyCode);    
   
     string result = httpHelper.GetHtml( " http://ptlogin2.qq.com/login? " + postData, Portal.gc.cookieQun, refUrl);    
     string errorTxt = result;    
     bool isLogin = result.Contains( " 登录成功! ");    
     return isLogin;    
}   

CHM帮助文档持续更新中,统一下载地址是: http://www.iqidi.com/download/commonshelp.rar 

最新公用类库DLL+XML注释文件下载地址是:http://files.cnblogs.com/wuhuacong/WHC.OrderWater.Commons.rar 


原文链接:http://www.cnblogs.com/wuhuacong/archive/2011/11/28/2266694.html
<无标签>
举报
长平狐
发帖于5年前 0回/121阅
顶部