zoukankan      html  css  js  c++  java
  • 用Java读取使用证书[转]

    证书(Certificate,也称public-key certificate)是用某种签名算法对某些内容(比如公钥)进行数字签名后得到的、可以用来当成信任关系中介的数字凭证。证书发行机构通过发行证书告知证书使用者或实体其公钥(public-key)以及其它一些辅助信息。证书在电子商务安全交易中有着广泛的应用,证书发行机构也称CA(Certificate Authority)。


    应用证书
    证书在公钥加密应用中的作用是保证公钥在某些可信的机构发布,其在协议SSL、电子交易协议SET等方面有重要的应用。图1显示了一个最简单的证书应用方法:

          图1 证书应用方法
    证书的应用步骤是:
    (1) A把自己的公钥PKA送到CA(Certificate Authority);
    (2) CA用自己的私钥和A的公钥生成A的证书,证书内包括CA的数字签名。签名对象包括需要在证书中说明的内容,比如A的公钥、时间戳、序列号等,为了简化这里不妨假设证书中只有三项内容:A的公钥PKA、时间戳TIME1、序列号IDA。那么CA发送给A的简单证书凭证可表达为:CertA=Eca[TIME1,IDA,PKA];
    (3) B同样把自己的公钥PKB送到CA;
    (4) B得到CA发布的证书CertB;
    (5) A告知B证书CertA;
    (6) B告知A证书CertB。
    A、B各自得到对方证书后,利用从CA得到的公钥(在CA的自签证书中)验证彼此对方的证书是否有效,如果有效,那么就得到了彼此的公钥。利用对方的公钥,可以加密数据,也可以用来验证对方的数字签名。
    本文为了方便说明,并没有使用从CA获得的证书,而是通信双方各自产生自签证书,也就是说图1的A和B并没有经过CA,不过前提是A和B之间是互相拥有对方的证书。
    证书的内容和意义如表1所示(这里以通用X .509证书格式为例)。
                  表1 证书内容和意义

    证书内容 意义
    Version 告诉这个X.509证书是哪个版本的,目前有v1、V2、v3
    Serial Number 由证书分发机构设置证书的序列号
    Signature Algorithm Identifier 证书采用什么样的签名算法
    Issuer Name 证书发行者名,也就是给这个证书签名的机构名
    Validity Period 证书有效时间范围
    Subject Name 被证书发行机构签名后的公钥拥有者或实体的名字,采用X.500协议,在Internet上的标志是惟一的。例如:CN=Java,OU=Infosec,O=Infosec Lab,C=CN表示一个subject name。

    对证书的详细定义及其应用相关的各种协议,这里不加详细说明,详细细节请查看RFC2450、RFC2510、RFC2511、RFC2527、RFC2528、RFC2559、RFC2560、RFC2585、RFC2587等文档。

    生成自签证书
    个人或机构可以从信任的证书分发机构申请得到证书,比如说,可以从http://ca.pku.edu.cn 得到一个属于个人的证书。这里可以利用J2SDK的安全工具keytool手工产生自签证书,所谓自签证书是指证书中的“Subject Name”和“Issuer Name”相同的证书。 

    下面产生一个自签证书。安装完J2SDK(这里用的是J2SDK1.4)后,在J2SDK安装目录的bin目录下,有一个keytool的可执行程序。利用keytool产生自签证书的步骤如下:
    第一步,用-genkey命令选项,产生公私密钥对。在控制台界面输入:keytool -genkey -alias testkeypair -keyalg RSA -keysize 1024 -sigalg MD5withRSA。这里的-alias表示使用这对公私密钥产生新的keystore入口的别名(keystore是用来存放管理密钥对和证书链的,缺省位置是在使用者主目录下,以.keystore为名的隐藏文件,当然也可指定某个路径存放.keystore文件);-keyalg是产生公私钥对所用的算法,这里是RSA;-keysize定义密钥的长度;-sigalg是签名算法,选择MD5withRSA,即用RSA签名,然后用MD5哈希算法摘要。接下来,系统会提示进行一些输入: 

    输入keystore密码:  abc123
    您的名字与姓氏是什么?  
      [Unknown]:  Li
    您的组织单位名称是什么?  
      [Unknown]:  InfosecLab
    您的组织名称是什么?  
      [Unknown]:  InfosecLab Group
    您所在的城市或区域名称是什么?  
      [Unknown]:  Beijing
    您所在的州或省份名称是什么?  
      [Unknown]:  Beijing
    该单位的两字母国家代码是什么  
      [Unknown]:  CNCN=Li, OU=InfosecLab, O=InfosecLab Group, L=Beijing, ST=Beijing, C=CN 正确吗?
    [否]:  y
    输入<testkeypair>的主密码 (如果和 keystore 密码相同,按回车):

    第二步,产生自签证书,输入以下命令:

    keytool -selfcert -alias testkeypair -dname "CN=Li, OU=InfosecLab, O=InfosecLab 
    Group, L=Beijing, ST=Beijing, C=CN" 
    输入keystore密码:  abc123

    第三步,导出自签证书,由上面两步产生的证书,已经存放在以“testkeypair”为别名的keystore入口了,如果使用其文件,必须导出证书。输入:

    keytool -export -rfc -alias testkeypair -file mycert.crt  
    输入keystore密码:  abc123
    保存在文件中的认证 <mycert.crt>

    这样,就得到了一个自签的证书mycert.crt。注意,选项rfc是把证书输出为RFC1421定义的、用Base64最终编码的格式。

    读取证书
    Java为安全应用提供了丰富的API,J2SDK1.4 的JSSE (JavaTM Secure Socket Extension) 包括javax.security.certificate包,并且提供对证书的操作方法。而对证书的读操作,只用java.security.cert. CertificateFactory和java.security.cert.X509Certificate就可以了。下面是读取证书内容的部分代码:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.table.*;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.io.*;
    public class CARead extends JPanel {
     private String CA_Name; 
     private String CA_ItemData[][] = new String[9][2];
     private String[] columnNames = {"证书字段标记","内容" };
     public CARead(String CertName) {
      CA_Name=CertName;
      /* 三个Panel用来显示证书内容*/
      JTabbedPane tabbedPane = new JTabbedPane();
      JPanel panelNormal = new JPanel();
      tabbedPane.addTab("普通信息", panelNormal);   
      JPanel panelAll=new JPanel();
      panelAll.setLayout(new BorderLayout());
      tabbedPane.addTab("所有信息",panelAll);
      JPanel panelBase64=new JPanel();
      panelBase64.setLayout(new BorderLayout());
      tabbedPane.addTab("Base64编码信息",panelBase64);
      /* 读取证书常规信息 */
      Read_Normal(panelNormal);
      /* 读取证书文件字符串表示内容 */
      Read_Bin(panelAll);
      /* 读取证原始Base64编码形式的证书文件 */
      Read_Raw(panelBase64);
      tabbedPane.setSelectedIndex(0);
      setLayout(new GridLayout(1, 1)); 
      add(tabbedPane);
     }
      /*以下是定义的Read_Normal(),Read_Bin(),Read_Raw()以及main() 
      这里省略...   */  
    }


    定义证书信息的读取函数如下:

    private int Read_Normal(JPanel panel){
     String Field;
     try{
      CertificateFactory certificate_factory=CertificateFactory.getInstance("X.509");
      FileInputStream file_inputstream=new FileInputStream(CA_Name);
      X509Certificate 
    x509certificate=(X509Certificate)certificate_factory.generateCertificate
    (file_inputstream);
      Field=x509certificate.getType();
      CA_ItemData[0][0]="类型";
      CA_ItemData[0][1]=Field;
      Field=Integer.toString(x509certificate.getVersion());
      CA_ItemData[1][0]="版本";
      CA_ItemData[1][1]=Field;	
      Field=x509certificate.getSubjectDN().getName();
      CA_ItemData[2][0]="标题";
      CA_ItemData[2][1]=Field;
      /* 以下类似,这里省略 
      Field=x509certificate.getNotBefore().toString();得到开始有效日期
      Field=x509certificate. getNotAfter().toString();得到截止日期
      Field=x509certificate.getSerialNumber().toString(16);得到序列号
      Field=x509certificate.getIssuerDN().getName();得到发行者名
      Field=x509certificate.getSigAlgName();得到签名算法
      Field=x509certificate.getPublicKey().getAlgorithm();得到公钥算法 */
      file_inputstream.close();
      final JTable table = new JTable(CA_ItemData, columnNames);
      TableColumn tc=null;
      tc = table.getColumnModel().getColumn(1);
      tc.setPreferredWidth(600); 
      panel.add(table);
     }catch(Exception exception){
      exception.printStackTrace();
      return -1;
     }
     return 0;
    }

     如果以字符串形式读取证书,加入下面Read_Bin这个函数。其中CertificateFactory.generateCertificate() 这个函数可以从证书标准编码(RFC1421定义)中解出可读信息。Read_Bin函数代码如下:  

    private int Read_Bin(JPanel panel){
     try{
      FileInputStream file_inputstream=new FileInputStream(CA_Name);
      DataInputStream data_inputstream=new DataInputStream(file_inputstream);
      CertificateFactory certificatefactory=CertificateFactory.getInstance("X.509");
      byte[] bytes=new byte[data_inputstream.available()];
      data_inputstream.readFully(bytes);
      ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
      JEditorPane Cert_EditorPane;
      Cert_EditorPane=new JEditorPane();
      while(bais.available()>0){
      X509Certificate 
    Cert=(X509Certificate)certificatefactory.generateCertificate(bais);
      Cert_EditorPane.setText(Cert_EditorPane.getText()+Cert.toString());
     }
     Cert_EditorPane.disable();
     JScrollPane edit_scroll=new JScrollPane(Cert_EditorPane);
     panel.add(edit_scroll);
     file_inputstream.close();
     data_inputstream.close();
     }catch( Exception exception){
      exception.printStackTrace();
      return -1;
     }
     return 0;	
    }
    

    如果要得到原始证书编码后的信息,则可用如下代码:

    private int Read_Raw(JPanel panel){
     try{		
      JEditorPane Cert_EditorPane=new JEditorPane();
      String CertText=null;
      File inputFile = new File(CA_Name);
      FileReader in = new FileReader(inputFile);
      char[] buf=new char[2000];
      int len=in.read(buf,0,2000);
      for(int i=1;i<len;i++) 
      {   
       CertText=CertText+buf[i];
      }
      in.close();
      Cert_EditorPane.setText(CertText);
      Cert_EditorPane.disable();
      JScrollPane edit_scroll=new JScrollPane(Cert_EditorPane);
      panel.add(edit_scroll);
     }catch( Exception exception){
      exception.printStackTrace();
      return -1;
     }
     return 0;	
    }

    最后用这个小程序看一看刚才生成的证书mycert.crt内容,把文件名写入main()中:

    public static void main(String[] args) {
     JFrame frame = new JFrame("证书阅读器");
     frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
     });
     frame.getContentPane().add(new CARead("mycert.crt"),BorderLayout.CENTER);
     frame.setSize(700, 425);
     frame.setVisible(true);
    }


    证书mycert.crt的内容显示如图2所示,所有信息和Base64的显示内容,这里不再列举。

              图2 证书mycert.crt的内容显示

    现在已经读取了证书的一些内容,那么怎样使用证书呢?我们可以假设A和B要共享一个绝密的文件F,B信任并拥有A的证书,也就是说B拥有A的公钥。那么A通过A和B共知的加密算法(对称密钥算法,比如DES算法)先加密文件F,然后对加密后的F进行签名和散列摘要(比如MD5算法,目的是保证文件的完整性),然后把F发送到B。B收到文件后,先用A的证书中的公钥验证签名,然后再用通过共知的加密算法解密,就可以得到原文件了。这里使用的数字签名,可以保证B得到的文件,就是A的,A不能否认其不拥有文件F,因为只有A拥有可以让A的公钥验证其签名的私钥,同时这里使用DES算法加密,使得文件有保密性。

    使用DES算法的加密解密函数类似,这里不对加密算法做进一步讨论,详细请看J2SDK的JSE部分内容,加密签名、解密验证文件结构见图3。

        图3 加密签名、解密验证文件结构图
    加密函数中的desKeyData存放DES加密密钥,如果要在程序中指定,可以设置为: 

    static byte[] desKeyData = { (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08 };
    

    加密函数写成:

    public static void crypt(byte[] cipherText,String outFileName){		
     try{
      DESKeySpec desKeySpec = new DESKeySpec(desKeyData);
      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
      SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
      Cipher cdes = Cipher.getInstance("DES");
      cdes.init(Cipher.ENCRYPT_MODE, secretKey);
      byte[] ct = cdes.doFinal(cipherText);
      try{
       FileOutputStream out=new FileOutputStream(outFileName);
       out.write(ct);
       out.close();
      }catch(IOException e){
       e.printStackTrace();
      }
     }catch (Exception e){
      e.printStackTrace();
     }
    }

    其中ct就是加密后的内容,outFileName保存加密后文件的文件名。把cdes.init(Cipher.ENCRYPT_MODE, secretKey)换成cdes.init(Cipher.DECRYPT_MODE, secretKey)就是解密文件了。
    文件加密后就要对文件签名,保证A发送到B的文件不可伪造。下面是用存放在.keystore中的私钥进行签名的函数,签名使用的摘要算法是MD5。其中sigText是被签名内容的输入数组,outFileName是保存签名后输出文件的名称,KeyPassword是读取Keystore使用的密码,KeyStorePath是存放.keystore文件的路径,函数代码如下:

    public static void sig(byte[] sigText, String outFileName,String 
    KeyPassword,String KeyStorePath){
     char[] kpass;
     int i;
     try{
      KeyStore ks = KeyStore.getInstance("JKS");
      FileInputStream ksfis = new FileInputStream(KeyStorePath); 
      BufferedInputStream ksbufin = new BufferedInputStream(ksfis);  
      kpass=new char[KeyPassword.length()];
      for(i=0;i<KeyPassword.length();i++)
       kpass[i]=KeyPassword.charAt(i);
      ks.load(ksbufin, kpass);
      PrivateKey priv = (PrivateKey) ks.getKey(KeystoreAlias,kpass );
      Signature rsa=Signature.getInstance("MD5withRSA");  
      rsa.initSign(priv);
      rsa.update(sigText);
      byte[] sig=rsa.sign();
      System.out.println("sig is done");
      try{
       FileOutputStream out=new FileOutputStream(outFileName);
       out.write(sig);
       out.close();
      }catch(IOException e){
       e.printStackTrace();
      }    
     }catch(Exception e){
      e.printStackTrace();
     }
    }

    验证签名需要存放签名文件和被签名的文件以及证书,其中,updateData存放被签名文件的内容,sigedText存放得到的签名内容,CertName是证书名。验证签名代码如下:

    public static void veriSig(byte[] updateData, byte[] sigedText){
        try{  
            CertificateFactory 
    certificatefactory=CertificateFactory.getInstance("X.509");
    		FileInputStream fin=new FileInputStream(CertName);
    		X509Certificate 
    certificate=(X509Certificate)certificatefactory.generateCertificate(fin);
    	    PublicKey pub = certificate.getPublicKey();
    	    Signature rsa=Signature.getInstance("MD5withRSA");
            rsa.initVerify(pub);
            rsa.update(updateData);
            boolean verifies=rsa.verify(sigedText);
            System.out.println("verified "+verifies);
            if(verifies){
                   System.out.println("Verify is done!");
              }else{
                   System.out.println("verify is not successful");
            }	    
    	  }catch(Exception e){    
                e.printStackTrace();	           	
    	 }
    }

    可以用keytool产生两个自签的签名证书,或者到某个CA去申请两个证书。用Java编写加密和验证程序,上述例子只是一个非常简单的证书应用,实际协议对证书的使用(比如SSL)要比这个复杂多了。

    转自:http://tech.ccidnet.com/art/294/20030108/36014_3.html

  • 相关阅读:
    linux查看CPU和内存信息
    linux yum命令详解
    查看文件中关键字前后几行的内容
    vue.js+web storm安装及第一个vue.js
    android GPS: code should explicitly check to see if permission is available
    ASP.NET MVC Identity 使用自己的SQL Server数据库
    阿里云服务器,tomcat启动,一直卡在At least one JAR was scanned for TLDs yet contained no TLDs就不动了
    ASP.NET MVC4 MVC 当前上下文中不存在名称“Scripts”
    python 将windows字体中的汉字生成图片的方法
    Java android DES+Base64加密解密
  • 原文地址:https://www.cnblogs.com/wen12128/p/1847995.html
Copyright © 2011-2022 走看看