随笔 - 154  文章 - 60  trackbacks - 0
<2008年1月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

声明:

该blog是为了收集资料,认识朋友,学习、提高技术,所以本blog的内容除非声明,否则一律为转载!!

感谢那些公开自己技术成果的高人们!!!

支持开源,尊重他人的劳动!!

常用链接

留言簿(3)

随笔分类(148)

随笔档案(143)

收藏夹(2)

其他

学习(技术)

观察思考(非技术)

搜索

  •  

最新评论

阅读排行榜

评论排行榜

--------------------------第一个:简单明了-------------------
http://www.ieee.org.cn/dispbbs.asp?boardID=41&ID=13873
代码:
import java.security.*
import java.security.spec.*

class MD5_Test

public final static String MD5(String s)
char hexDigits[] = 
'0''1''2''3''4''5''6''7''8''9''a''b''c''d'
'e''f'}

try 
byte[] strTemp = s.getBytes(); 
MessageDigest mdTemp 
= MessageDigest.getInstance("MD5"); 
mdTemp.update(strTemp); 
byte[] md = mdTemp.digest(); 
int j = md.length; 
char str[] = new char[j * 2]; 
int k = 0
for (int i = 0; i < j; i++
byte byte0 = md[i]; 
str[k
++= hexDigits[byte0 >>> 4 & 0xf]; 
str[k
++= hexDigits[byte0 & 0xf]; 
}
 
return new String(str); 
}
 
catch (Exception e)
return null
}
 
}
 
public static void main(String[] args)
//MD5_Test aa = new MD5_Test(); 

System.out.print(MD5_Test.MD5(
"XX")); 
}


-----------------第二个:说明注释详细---------------
http://www.blogjava.net/haogj/archive/2006/07/04/56604.html

MD5即Message-Digest Algorithm 5(信息-摘要算法5),是一种用于产生数字签名的单项散列算法,在1991年由MIT Laboratory for Computer Science(IT计算机科学实验室)和RSA Data Security Inc(RSA数据安全公司)的Ronald L. Rivest教授开发出来,经由MD2、MD3和MD4发展而来。MD5算法的使用不需要支付任何版权费用。它的作用是让大容量信息在用数字签名软件签私人密匙前被"压缩"成一种保密的格式(将一个任意长度的“字节串”通过一个不可逆的字符串变换算法变换成一个128bit的大整数,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。)
   
   在 Java 中,java.security.MessageDigest 中已经定义了 MD5 的计算,所以我们只需要简单地调用即可得到 MD5 的128 位整数。然后将此 128 位计 16 个字节转换成 16 进制表示即可。

代码:
package com.tsinghua;

/**
 * MD5的算法在RFC1321 中定义
 * 在RFC 1321中,给出了Test suite用来检验你的实现是否正确: 
 * MD5 ("") = d41d8cd98f00b204e9800998ecf8427e 
 * MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661 
 * MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72 
 * MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0 
 * MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b 
 * 
 * 
@author haogj
 *
 * 传入参数:一个字节数组
 * 传出参数:字节数组的 MD5 结果字符串
 
*/

public class MD5 {
 
public static String getMD5(byte[] source) {
  String s 
= null;
  
char hexDigits[] = {       // 用来将字节转换成 16 进制表示的字符
     '0''1''2''3''4''5''6''7''8''9''a''b''c''d',  'e''f'}

   
try
   
{
    java.security.MessageDigest md 
= java.security.MessageDigest.getInstance( "MD5" );
    md.update( source );
    
byte tmp[] = md.digest();          // MD5 的计算结果是一个 128 位的长整数,
                                                
// 用字节表示就是 16 个字节
    char str[] = new char[16 * 2];   // 每个字节用 16 进制表示的话,使用两个字符,
                                                 
// 所以表示成 16 进制需要 32 个字符
    int k = 0;                                // 表示转换结果中对应的字符位置
    for (int i = 0; i < 16; i++{          // 从第一个字节开始,对 MD5 的每一个字节
                                                 
// 转换成 16 进制字符的转换
     byte byte0 = tmp[i];                 // 取第 i 个字节
     str[k++= hexDigits[byte0 >>> 4 & 0xf];  // 取字节中高 4 位的数字转换, 
                                                             
// >>> 为逻辑右移,将符号位一起右移
     str[k++= hexDigits[byte0 & 0xf];            // 取字节中低 4 位的数字转换
    }
 
    s 
= new String(str);                                 // 换后的结果转换为字符串

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

   
return s;
 }

}



测试代码:

import com.tsinghua.*
public class TestMD5
{
 
public static void main( String xu[] )
 
// 计算 "a" 的 MD5 代码,应该为:0cc175b9c0f1b6a831c399e269772661
  System.out.println( MD5.getMD5("a".getBytes()) );
 }

}



-----------第三个:服务周到--------
MD5加密算法严密,安全性较高,广泛应用于系统身份验证、文件完整性验证中。Java API中java.security包中包含MessageDigest类,可以实现MD5算法的简单应用,譬如将指定的普通明文转换成MD5密码,参考源代码如下:

/* *************************************************************
 * Project: bobToolkit <p>
 * Package: bob.sec.MD5 <p>
 * Filename: MD5Util.java<p>
 * Created by BobChen on 2007-3-29 <p>
 * ////////////////////////////////////////////////////////////
 *  Date      Author         Description
 * 2007-3-29    BobChen        init: object MD5Util
 * ***********************************************************/
package bob.sec.MD5;

import java.security.MessageDigest;

public class MD5Util {
    public final static String MD5(String s) {
        char hexDigits[] = { '0', '1', '2', '3', '4',
                             '5', '6', '7', '8', '9',
                             'A', 'B', 'C', 'D', 'E', 'F' };
        try {
            byte[] btInput = s.getBytes();
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        }
        catch (Exception e) {
            // e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        System.out.print(MD5Util.MD5("AusITcim#1485"));
    }

}

---------- 运行 ----------
EF277661A4C821C9007ACB52E1D883EC
输出完成 (耗时 0 秒) - 正常终止
 

 

----------------------JSP页面调用-------------------------------

----------

String u_password=new String(request.getParameter("u_password").getBytes("ISO-8859-1"));

u_password=(new MD5Util().MD5(u_password));


---------

String u_id=new String(request.getParameter("u_id").getBytes("ISO-8859-1"));


String u_password=new String(request.getParameter("u_password").getBytes("ISO-8859-1"));


u_password=(new MD5Util().MD5(u_password));



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1621920

----------第四个:java MD5算法返回数字型字串 --------

常有人问及MD5算法为何有些程序片断返回完全数字型结果而有些返回数字与字母的混合字串。

其实两种返回结果只是因为加密结果的不同显示形式,Blog中已经有.Net的实现,在此附加JAVA实现,供参考。

JAVA的标准类库理论上功能也很强大,但由于虚拟机/运行时的实现太多,加之版本差异,有些代码在不同环境下运行会出现奇怪的异常结果,尤其以涉及字符集的操作为甚。

package com.bee.framework.common;

import java.security.MessageDigest;

public class MD5Encrypt {
  public MD5Encrypt() {
  }

  private final static String[] hexDigits = {
      "0", "1", "2", "3", "4", "5", "6", "7",
      "8", "9", "a", "b", "c", "d", "e", "f"};

  /**
   * 转换字节数组为16进制字串
   * @param b 字节数组
   * @return 16进制字串
   */
  public static String byteArrayToString(byte[] b) {
    StringBuffer resultSb = new StringBuffer();
    for (int i = 0; i < b.length; i++) {
      //resultSb.append(byteToHexString(b[i]));//若使用本函数转换则可得到加密结果的16进制表示,即数字字母混合的形式
      resultSb.append(byteToNumString(b[i]));//使用本函数则返回加密结果的10进制数字字串,即全数字形式
    }
    return resultSb.toString();
  }

  private static String byteToNumString(byte b) {

    int _b = b;
    if (_b < 0) {
      _b = 256 + _b;
    }

    return String.valueOf(_b);
  }

  private static String byteToHexString(byte b) {
    int n = b;
    if (n < 0) {
      n = 256 + n;
    }
    int d1 = n / 16;
    int d2 = n % 16;
    return hexDigits[d1] + hexDigits[d2];
  }

  public static String MD5Encode(String origin) {
    String resultString = null;

    try {
      resultString = new String(origin);
      MessageDigest md = MessageDigest.getInstance("MD5");
      resultString =
byteArrayToString(md.digest(resultString.getBytes()));
    }
    catch (Exception ex) {

    }
    return resultString;
  }

  public static void main(String[] args) {
    MD5Encrypt md5encrypt = new MD5Encrypt();
    System.out.println(MD5Encode("10000000"));
  }
}



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=470562

----------第五个:理论---------

MD5的Java Bean实现


2001 年 3 月 01 日

编者的话:

虽然 MD5 签名算法在 jdk 中早已实现(如 MessageDigest类),但作者从 MD5 的原理分析讲述 MD5 具体算法的 Java实现并给出一个完整的示例程序,我想这对我们的读者来说还是会有很多帮助的。

MD5简介

MD5的全称是Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。

Message-Digest泛指字节串(Message)的Hash变换,就是把一个任意长度的字节串变换成一定长的大整数。请注意我使用了“字节串”而不是“字符串”这个词,是因为这种变换只与字节的值有关,与字符集或编码方式无关。

MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。

MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。

MD5还广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的,用户Login的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,而系统并不“知道”用户的密码是什么。

一些黑客破获这种密码的方法是一种被称为“跑字典”的方法。有两种方法得到字典,一种是日常搜集的用做密码的字符串表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。

即使假设密码的最大长度为8,同时密码只能是字母和数字,共26+26+10=62个字符,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这个字典就需要TB级的磁盘组,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。

在很多电子商务和社区应用中,管理用户的Account是一种最常用的基本功能,尽管很多Application Server提供了这些基本组件,但很多应用开发者为了管理的更大的灵活性还是喜欢采用关系数据库来管理用户,懒惰的做法是用户的密码往往使用明文或简单的变换后直接保存在数据库中,因此这些用户的密码对软件开发者或系统管理员来说可以说毫无保密可言,本文的目的是介绍MD5的Java Bean的实现,同时给出用MD5来处理用户的Account密码的例子,这种方法使得管理员和程序设计者都无法看到用户的密码,尽管他们可以初始化它们。但重要的一点是对于用户密码设置习惯的保护。

有兴趣的读者可以从这里取得MD5也就是RFC 1321的文本。http://www.ietf.org/rfc/rfc1321.txt





回页首


实现策略

MD5的算法在RFC1321中实际上已经提供了C的实现,我们其实马上就能想到,至少有两种用Java实现它的方法,第一种是,用Java语言重新写整个算法,或者再说简单点就是把C程序改写成Java程序。第二种是,用JNI(Java Native Interface)来实现,核心算法仍然用这个C程序,用Java类给它包个壳。

但我个人认为,JNI应该是Java为了解决某类问题时的没有办法的办法(比如与操作系统或I/O设备密切相关的应用),同时为了提供和其它语言的互操作性的一个手段。使用JNI带来的最大问题是引入了平台的依赖性,打破了SUN所鼓吹的“一次编写到处运行”的Java好处。因此,我决定采取第一种方法,一来和大家一起尝试一下“一次编写到处运行”的好处,二来检验一下Java 2现在对于比较密集的计算的效率问题。





回页首


实现过程

限于这篇文章的篇幅,同时也为了更多的读者能够真正专注于问题本身,我不想就某一种Java集成开发环境来介绍这个Java Bean的制作过程,介绍一个方法时我发现步骤和命令很清晰,我相信有任何一种Java集成环境三天以上经验的读者都会知道如何把这些代码在集成环境中编译和运行。用集成环境讲述问题往往需要配很多屏幕截图,这也是我一直对集成环境很头疼的原因。我使用了一个普通的文本编辑器,同时使用了Sun公司标准的JDK 1.3.0 for Windows NT。

其实把C转换成Java对于一个有一定C语言基础的程序员并不困难,这两个语言的基本语法几乎完全一致.我大概花了一个小时的时间完成了代码的转换工作,我主要作了下面几件事:

  1. 把必须使用的一些#define的宏定义变成Class中的final static,这样保证在一个进程空间中的多个Instance共享这些数据
  2. 删去了一些无用的#if define,因为我只关心MD5,这个推荐的C实现同时实现了MD2 MD3和 MD4,而且有些#if define还和C不同编译器有关
  3. 将一些计算宏转换成final static 成员函数。
  4. 所有的变量命名与原来C实现中保持一致,在大小写上作一些符合Java习惯的变化,计算过程中的C函数变成了private方法(成员函数)。
  5. 关键变量的位长调整
  6. 定义了类和方法

需要注意的是,很多早期的C编译器的int类型是16 bit的,MD5使用了unsigned long int,并认为它是32bit的无符号整数。而在Java中int是32 bit的,long是64 bit的。在MD5的C实现中,使用了大量的位操作。这里需要指出的一点是,尽管Java提供了位操作,由于Java没有unsigned类型,对于右移位操作多提供了一个无符号右移:>>>,等价于C中的 >> 对于unsigned 数的处理。

因为Java不提供无符号数的运算,两个大int数相加就会溢出得到一个负数或异常,因此我将一些关键变量在Java中改成了long类型(64bit)。我个人认为这比自己去重新定义一组无符号数的类同时重载那些运算符要方便,同时效率高很多并且代码也易读,OO(Object Oriented)的滥用反而会导致效率低下。

限于篇幅,这里不再给出原始的C代码,有兴趣对照的读者朋友可以去看RFC 1321。 MD5.java源代码





回页首


测试

在RFC 1321中,给出了Test suite用来检验你的实现是否正确:

MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
            MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
            MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
            MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0
            MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
            ……
            

这些输出结果的含义是指:空字符串””的MD5值是d41d8cd98f00b204e9800998ecf8427e,字符串”a”的MD5值是
0cc175b9c0f1b6a831c399e269772661……
编译并运行我们的程序:

            javac –Cd . MD5.java
            java beartool.MD5
            

为了将来不与别人的同名程序冲突,我在我的程序的第一行使用了package beartool;

因此编译命令 javac �Cd . MD5.java 命令在我们的工作目录下自动建立了一个 beartool 目录,目录下放着编译成功的 MD5.class

我们将得到和Test suite同样的结果。当然还可以继续测试你感兴趣的其它MD5变换,例如:

            java beartool.MD5 1234
            

将给出1234的MD5值。

可能是我的计算机知识是从Apple II和Z80单板机开始的,我对大写十六进制代码有偏好,如果您想使用小写的Digest String只需要把byteHEX函数中的A、B、C、D、E、F改成a、b、 c、d、e、f就可以了。

MD5据称是一种比较耗时的计算,我们的Java版MD5一闪就算出来了,没遇到什么障碍,而且用肉眼感觉不出来Java版的MD5比C版的慢。

为了测试它的兼容性,我把这个MD5.class文件拷贝到我的另一台Linux+IBM JDK 1.3的机器上,执行后得到同样结果,确实是“一次编写到处运行了”。





回页首


Java Bean简述

现在,我们已经完成并简单测试了这个Java Class,我们文章的标题是做一个Java Bean。

其实普通的Java Bean很简单,并不是什么全新的或伟大的概念,就是一个Java的Class,尽管 Sun规定了一些需要实现的方法,但并不是强制的。而EJB(Enterprise Java Bean)无非规定了一些必须实现(非常类似于响应事件)的方法,这些方法是供EJB Container使用(调用)的。

在一个Java Application或Applet里使用这个bean非常简单,最简单的方法是你要使用这个类的源码工作目录下建一个beartool目录,把这个class文件拷贝进去,然后在你的程序中import beartool.MD5就可以了。最后打包成.jar或.war是保持这个相对的目录关系就行了。

Java还有一个小小的好处是你并不需要摘除我们的MD5类中那个main方法,它已经是一个可以工作的Java Bean了。Java有一个非常大的优点是她允许很方便地让多种运行形式在同一组代码中共存,比如,你可以写一个类,它即是一个控制台Application和GUI Application,同时又是一个Applet,同时还是一个Java Bean,这对于测试、维护和发布程序提供了极大的方便,这里的测试方法main还可以放到一个内部类中。

这里讲述了把测试和示例代码放在一个内部静态类的好处,是一种不错的工程化技巧和途径。





回页首


把Java Bean装到JSP里

正如我们在本文开头讲述的那样,我们对这个MD5 Bean的应用是基于一个用户管理,这里我们假设了一个虚拟社区的用户login过程,用户的信息保存在数据库的个名为users的表中。这个表有两个字段和我们的这个例子有关,userid :char(20)和pwdmd5 :char(32),userid是这个表的Primary Key,pwdmd5保存密码的MD5串,MD5值是一个128bit的大整数,表示成16进制的ASCII需要32个字符。

这里给出两个文件, login.html是用来接受用户输入的form, login.jsp 用来模拟使用MD5 Bean的login过程。

为了使我们的测试环境简单起见,我们在JSP中使用了JDK内置的JDBC-ODBC Bridge Driver,community是ODBC的DSN的名字,如果你使用其它的JDBC Driver,替换掉login.jsp中的
Connection con= DriverManager.getConnection("jdbc:odbc:community", "", "");

即可。

login.jsp的工作原理很简单,通过post接收用户输入的UserID和Password,然后将Password变换成MD5串,然后在users表中寻找UserID和pwdmd5,因为UserID是users表的Primary Key,如果变换后的pwdmd5与表中的记录不符,那么SQL查询会得到一个空的结果集。

这里需要简单介绍的是,使用这个Bean只需要在你的JSP应用程序的WEB-INF/classes下建立一个beartool目录,然后将MD5.class拷贝到那个目录下就可以了。如果你使用一些集成开发环境,请参考它们的deploy工具的说明。在JSP使用一个java Bean关键的一句声明是程序中的第2行:

<jsp:useBean id='oMD5' scope='request' class='beartool.MD5'/>

这是所有JSP规范要求JSP容器开发者必须提供的标准Tag。

id=实际上是指示JSP Container创建Bean的实例时用的实例变量名。在后面的<%和%>之间的Java程序中,你可以引用它。在程序中可以看到,通过 pwdmd5=oMD5.getMD5ofStr (password) 引用了我们的MD5 Java Bean提供的唯一一个公共方法: getMD5ofStr。

Java Application Server执行.JSP的过程是先把它预编译成.java(那些Tag在预编译时会成为java语句),然后再编译成.class。这些都是系统自动完成和维护的,那个.class也称为Servlet。当然,如果你愿意,你也可以帮助Java Application Server去干本该它干的事情,自己直接去写Servlet,但用Servlet去输出HTML那简直是回到了用C写CGI程序的恶梦时代。

如果你的输出是一个复杂的表格,比较方便的方法我想还是用一个你所熟悉的HTML编辑器编写一个“模板”,然后在把JSP代码“嵌入”进去。尽管这种JSP代码被有些专家指责为“空心粉”,它的确有个缺点是代码比较难管理和重复使用,但是程序设计永远需要的就是这样的权衡。我个人认为,对于中、小型项目,比较理想的结构是把数据表示(或不严格地称作WEB界面相关)的部分用JSP写,和界面不相关的放在Bean里面,一般情况下是不需要直接写Servlet的。

如果你觉得这种方法不是非常的OO(Object Oriented),你可以继承(extends)它一把,再写一个bean把用户管理的功能包进去。





回页首


到底能不能兼容?

我测试了三种Java应用服务器环境,Resin 1.2.3、Sun J2EE 1.2、IBM WebSphere 3.5,所幸的是这个Java Bean都没有任何问题,原因其实是因为它仅仅是个计算程序,不涉及操作系统,I/O设备。其实用其它语言也能简单地实现它的兼容性的,Java的唯一优点是,你只需提供一个形态的运行码就可以了。请注意“形态”二字,现在很多计算结构和操作系统除了语言本身之外都定义了大量的代码形态,很简单的一段C语言核心代码,转换成不同形态要考虑很多问题,使用很多工具,同时受很多限制,有时候学习一种新的“形态”所花费的精力可能比解决问题本身还多。比如光Windows就有EXE、Service、的普通DLL、COM DLL以前还有OCX等等等等,在Unix上虽说要简单一些,但要也要提供一个.h定义一大堆宏,还要考虑不同平台编译器版本的位长度问题。我想这是Java对我来说的一个非常重要的魅力吧。

login.html代码:
<html>
<head>
<title>Login</title>
</head>
<body>
<form method="POST" action="login.jsp">
  
<p>UserID:<input type="text" name="UserID" size="20">
  Password:
<input type="password" name="Password" size="20"></p>
  
<p><input type="submit" value="Login" name="Login"></p>
</form>
</body>
</html>



login.jsp代码:
<%@ page language='java' %>
<jsp:useBean id='oMD5' scope='request' class='beartool.MD5'/>
 
<%@ page import='java.util.*'%>
<%@ page import='java.sql.*'%>
<html>
<body>
<pre>
<%
  
String userid = request.getParameter("UserID");  //接受UserID
  
String password = request.getParameter("Password"); //接受Password
  
String pwdmd5 = oMD5.getMD5ofStr(password);  //变换密码
  PrintWriter rp 
= response.getWriter();
  Class.forName(
"sun.jdbc.odbc.JdbcOdbcDriver");
  Connection con 
= DriverManager.getConnection("jdbc:odbc:community""""");
  Statement stmt 
= con.createStatement();                           
  ResultSet rs 
= stmt.executeQuery("select * from users where userID ='"
+userid+"' and pwdmd5= '" + pwdmd5+"'" );
  
if (rs.next()) 
    {
      rp.print(
"Login OK");
   }
  
else
    {
      rp.print(
"Login Fail");
    }
  stmt.close();
  con.close();
%>



posted on 2008-01-30 15:21 lk 阅读(723) 评论(0)  编辑  收藏 所属分类: j2se

只有注册用户登录后才能发表评论。


网站导航: