• 基于自签名的X.509数字证书生成及验证


    基于自签名的 X.509 数字证书生成及验证

     

    数字证书用于标志网络用户身份,在 Web 应用中,数字证书的应用十分广泛,如:安全电子邮件、访问安全站点、安全电子事务处理和安全电子交易等。

    数字证书的格式一般采用 X.509 国际标准。目前,数字证书认证中心主要签发安全电子邮件证书、个人和企业身份证书、服务器证书以及代码签名证书等几种类型证书。

     

    数字证书由证书机构签发,证书机构通常需经权威认证机构注册认证。在企业应用中,也常用企业自身作为发证机构(未经过认证)签发数字证书,证书的使用范围也常是企业内部,这样的证书就是所谓的“自签名”的。

     

    数字证书采用公钥密码体制,每个用户拥有一把仅为本人所掌握的私有密钥 ( 私钥 ) ,用它进行解密和签名 ; 同时拥有一把公共密钥 ( 公钥 ) 并可以对外公开,用于加密和验证签名。

     

    下面介绍我们在项目中常用的自签名证书的生成及验证方法。为简单起见,我们假设所有网站用户使用同一个数字证书 clientCA

     

    一、 服务器端

     

    登录远程服务器,在服务器端生成证书并提供下载。服务器上应当安装 jdk1.5 以上,因为我们需要使用 jdk 自带的 keytool 工具, keytool 工具位于 jdk 安装目录的 bin 目录下。

    1、  生成密钥数据库及根证书

    如果是第 1 次使用数字证书,那么很可能服务器上还没有密钥数据库。我们可以使用下列命令成密钥数据库( keystore )。

    keytool -genkey -dname "CN= 发证人姓名 ,OU= 发证人所属部门 ,O= 发证人所属公司 ,L= 昆明市 ,ST= 云南省 ,C= 中国 " -alias IPCCCA -keyalg RSA -keysize 1024 -keystore IPCCCA -keypass 根证书密码 -storepass 库密码

     

    运行脚本后,会在当前用户主目录(如: C:/Documents and Settings/Administrator 目录, window 系统)下生成密钥数据库文件 IPCCCA 。注意,需要设置两个密码,一个是根证书密码 keypass ,一个是库密码 storepass ,如果你不是很确定二者间的区别,最好两个都设置成一样。

     

    2、  生成自签名证书

    运行下列命令,生成一个自签名证书:

    keytool -genkey -dname "CN= 发证人姓名 ,OU= 发证人所属部门 ,O= 发证人所属公司 ,L= 昆明市 ,ST= 云南省 ,C= 中国 " -alias clientCA -keyalg RSA -keysize 1024 -keystore IPCCCA -keypass 123456 -storepass 库密码 -validity 1

    这个证书是一个自签名证书,该证书的别名为 clientCA ,存储在前面生成的那个密钥库文件 (IPCCCA) 中。这需要提供访问该库的库密码 (storepass) ,必须跟第 1 步中的一样。 Kepass 是该证书的公钥,验证证书时需要提供该密钥。

    并且为了便于测试,我们把证书有效期设置为 1 天。这样每过一天,用户必须重新下载证书。

    3、  查看密钥库

    你可以用下列命令查看生成的两个密钥:

    keytool -list -keystore IPCCCA –storepass  库密码

    结果会列出两个密钥,类似如下:

    您的 keystore 包含 2 输入

     

    clientca, 2011-3-30, keyEntry,

    认证指纹 (MD5) 10:B8:51:54:7B:1C:60:7C:89:E7:B6:8E:71:E5:E1:E7

    ipccca, 2011-3-30, keyEntry,

    认证指纹 (MD5) C3:E3:7D:7C:9B:AA:05:84:92:AF:93:18:42:D2:1C:07

     

    4、  提供证书下载

    我们可以在服务器上放一个 servlet ,以提供自签名证书的下载:

    private static final long serialVersionUID = 1L;

    // 有效期天数

    private static final int Max_Days = 1;

    // keystore 密码

    private static final char[] password = "ipcc@95598".toCharArray();

    // keystore 文件路径

    private static final String keystoreFilename = "C://Documents and Settings//Administrator//IPCCCA";

    // 证书文件名

    private static final String certFilename="client.cer";

    //  证书别名

    private static final String alias = "clientCA";

    private KeyStore keystore;  

        private String sigAlgrithm;

    // 读取 keystore

    private KeyStore loadKeystore(String keystorepath) {

    KeyStore ks = null;

    try {

    FileInputStream fIn = new FileInputStream(keystorepath);

    ks = KeyStore.getInstance("JKS");

    ks.load(fIn, password);

    fIn.close();

    return ks;

    } catch (Exception e) {

    System.out.println(e.getMessage());

    }

    return ks;

    }

     

    // 获得 CertInfo

    private X509CertInfo getCertInfo(Certificate c, String alias) {

    X509CertInfo certInfo = null;

    try {

    // 从待签发的证书中提取证书信息  

    byte[] encod2 = c.getEncoded();// 获取 证书内容(经过编码的字节)

    X509CertImpl cimp2 = new X509CertImpl(encod2);// 创建 X509CertImpl

    sigAlgrithm=cimp2.getSigAlgName();

    // 获取 X509CertInfo 对象

    certInfo = (X509CertInfo) cimp2.get(X509CertImpl.NAME

    + "." + X509CertImpl.INFO);

     

    } catch (Exception e) {

    System.out.println(e.getMessage());

    }

    return certInfo;

    }

     

    // 修改有效期

    private void updateValidity(X509CertInfo cinfo, int days) {

    // 获取当前时间

    Date d1 = new Date();

    // 有效期为当前日期后延 n

    Date d2 = new Date(d1.getTime() + days * 24 * 60 * 60 * 1000L);

    // 创建有效期对象

    CertificateValidity cv = new CertificateValidity(d1, d2);

    try {

    cinfo.set(X509CertInfo.VALIDITY, cv);// 设置有效期

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

     

    //  存储证书

    private void saveCert(KeyStore ks, char[] storepass, String alias,

    PrivateKey pKey, char[] keypass, X509CertInfo cinfo,String algrithm) {

    try {

    X509CertImpl cert = new X509CertImpl(cinfo);// 新建证书

    cert.sign(pKey, algrithm); // 使用 CA 私钥对其签名

    // 获取别名对应条目的证书链

    Certificate[] chain = new Certificate[] { cert };

    // 向密钥库中添加条目 , 使用已存在别名将覆盖已存在条目

    ks.setKeyEntry(alias, pKey, keypass, chain);

    // keystore 存储至文件

    FileOutputStream fOut = new FileOutputStream(keystoreFilename);

    keystore.store(fOut, password);

    fOut.close();

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    // 导出证书

    private void exportCert(KeyStore ks,String alias,HttpServletResponse response){

    try{

        Certificate cert = keystore.getCertificate(alias);

        // 得到证书内容(以编码过的格式)

        byte[] buf = cert.getEncoded();

    // 写证书文件

        response.setContentType("application/x-download");  

        response.addHeader("Content-Disposition", "attachment;filename=" 

                + certFilename);  

        OutputStream out = response.getOutputStream();

        out.write(buf);

        out.close();

    }catch(Exception e){

    e.printStackTrace();

    }

    }

    /**

         * @see HttpServlet#HttpServlet()

         */

        public GetNewCert() {

            super();

            // TODO Auto-generated constructor stub

        }

     

    /**

      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)

      */

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    try{

    keystore = loadKeystore(keystoreFilename); // 读取 keystore

    Certificate c = keystore.getCertificate(alias);// 读取证书

    X509CertInfo cinfo = getCertInfo(c, alias);// 获得证书的 CertInfo

    updateValidity(cinfo, Max_Days);// 修改证书有效期

     

    // 从密钥库中读取 CA 的私钥

    PrivateKey pKey = (PrivateKey) keystore.getKey(alias, "123456"

    .toCharArray());

     

    // keystore 存储至 keystore 文件

    saveCert(keystore, password, alias, pKey, "123456".toCharArray(),cinfo,sigAlgrithm);

    exportCert(keystore,alias,response);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

     

    /**

      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)

      */

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    doGet(request,response);

    }

     

    }

    这个 servlet 的作用是:

    当用户请求该 serlvet ,从密钥库中提取 clientCA 证书,将证书有效期修改为当前日期到下一天。

    也就是说,当用户客户端请求该 servlet ,即可获得一个新的证书,这个证书的有效期已经向后延了一天。

    我们的目的是,用户每次登录后,检查用户下载的证书,如证书已过了有效期,则请求此 servlet 即可获得一个有效的新证书。

     

    二、 客户端

    客户端可能是任何设备,包括 pc 、移动终端。我们假设客户端是基于 Android1.6 以上的移动终端,则以下是 java 客户端的证书验证类 MyCertificate

    public class MyCertificate {

    private static String tag="MyCertificate";

    public static Certificate readCert(File file){

    Certificate c=null;

    try{

    CertificateFactory cf = CertificateFactory.getInstance("X.509");

    FileInputStream in1 = new FileInputStream(file);

    c = cf.generateCertificate(in1);

    in1.close();

    } catch (Exception e) {

    Log.e(tag, e.toString());

    }

    return c;

    }

    // 验证证书的有效性

    public static boolean verifyCert(Certificate c){

    PublicKey pbk=c.getPublicKey();

    try{

    c.verify(pbk);

    return true;

    }catch(Exception e){

    Log.e(tag,"Certificate is invalid");

    }

    return false;

    }

    // 验证证书有效期

    public static int verifyCertValidity(Date date,Certificate c){

    int i=0;

    X509Certificate t=(X509Certificate)c;

    try {// 有效

    t.checkValidity(date);

    } catch (CertificateExpiredException e) {// 过期

    Log.e(tag,"Certificate Expired");

    i=-1;

    } catch (CertificateNotYetValidException e) {// 尚未生效

    Log.e(tag,"Certificate Too early");

    i=-2;

    }

    return i;

    }

    public static boolean verify(Context ctx){

    Activity act=(Activity)ctx;

    boolean b=false;

    // 检查证书文件是否存在

    File file=new File(Environment.getExternalStorageDirectory()+act.getString(R.string.CERT_DIR)+act.getString(R.string.CERT_FILE));

    if(!file.exists()){

    act.showDialog(1);

    }else{

    Date d=new Date();// 取当前时间

    Certificate c=MyCertificate.readCert(file);// 读取证书文件

    // 校验证书有效性

    if(!MyCertificate.verifyCert(c)){

    act.showDialog(0);// 无效证书

    }else{

    // 校验证书有效期

    int i=MyCertificate.verifyCertValidity(d,c);

    switch(i){

    case 0:// 有效

    b=true;

    break;

    case -1:// 过期

    act.showDialog(2);

    break;

    case -2:// 未生效

    act.showDialog(3);

    break;

    }

    }

    }

    return b;

    }

    }

    在相关 activity 中可以这样使用它:

    private void login(String acc, String pass) {

    String url = this.getString(R.string.PORT_LOGIN_URL);

    url = String.format(url, acc, pass);

    // Log.i(tag,url);

    MainLoginHandler handler = new MainLoginHandler();

    modules = SaxHelper.getModules(url, handler);

    // Log.i(tag,systems.toString());

    Log.i("modules:", "" + modules);

    if (modules != null) {

    String status = (String) modules.get("loginstatus");

    if ("true".equals(status)) {// 登录成功

    if (!verifyCert()) {

    return;

    }

    Bundle bundle = new Bundle();

    bundle.putSerializable("data",

    (Serializable) modules.get("modules"));

    gotoActivity(main.class, bundle);

    } else {

    Toast.makeText(getBaseContext(), " 用户名或密码错误! ",

    Toast.LENGTH_SHORT).show();

    }

    }

    }

    private boolean verifyCert() {

    return MyCertificate.verify(this);

    }

    // 创建 activity 托管对话框

    protected Dialog onCreateDialog(int id) {

    Log.e("::::", "showdialog!");

    String msg = "";

    switch (id) {

    case 1:

    msg = " 证书未下载!请点击“是”以下载证书。 ";

    break;

    case 2:

    msg = " 证书已过期!请点击“是”重新下载证书。 ";

    break;

    case 3:

    msg = " 证书尚未生效!请等证书生效后再重新登录。 ";

    // 对于未生效的证书,无需重新下载,等证书生效即可

    return new AlertDialog.Builder(this)

    .setMessage(msg)

    .setNegativeButton(" ",

    new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog,

    int which) {

    dialog.dismiss();// removeDialog(0); 移除对话框

    }

    }).create();

    case 4:

    return new AlertDialog.Builder(this)

    .setMessage(" 位置源未设置!是否现住设置位置源? ")

    .setPositiveButton(" ",

    new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog,

    int which) {

    // 转至 GPS 设置界面

    Intent intent = new Intent(

    Settings.ACTION_SECURITY_SETTINGS);

    startActivityForResult(intent, 0);

    }

    })

    .setNegativeButton(" ",

    new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog,

    int which) {

    dialog.dismiss();// removeDialog(0); 移除对话框

    }

    }).create();

    default:

    msg = " 无效的证书!请点击“是”重新下载证书。 ";

    }

    return new AlertDialog.Builder(this).setMessage(msg)

    .setPositiveButton(" ", new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {

    // 开始下载证书

    downloadCert();

    }

    })

    .setNegativeButton(" ", new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {

    dialog.dismiss();// removeDialog(0); 移除对话框

    }

    }).create();

    }

    红色加粗部分的代码调用了 MyCertificate.verify() onCreateDialog 方法则通过对话框方式返回证书校验的结果。

  • 相关阅读:
    Python-面向对象(一)-Day7
    Python-模块使用-Day6
    Python-迭代器&生成器&装饰器&软件目录结构规范-Day5
    Python-函数-Day4
    Python-字典、集合、字符编码、文件操作整理-Day3
    Python-字符串及列表操作-Day2
    Python-基础学习-Day1
    解决安装Weblogic domain卡住问题(Primeton BPS)
    怎样在旅途中拍出好看的照片
    Weblogic启动成功,控制台打不开
  • 原文地址:https://www.cnblogs.com/encounter/p/2188492.html
Copyright © 2020-2023  润新知