数字证书简介及Java编码实现

1.数字证书简介

数字证书具备常规加密解密必要的信息,包含签名算法,可用于网络数据加密解密交互,标识网络用户(计算机)身份。数字证书为发布公钥提供了一种简便的途径,其数字证书则成为加密算法以及公钥的载体依靠数字证书,我们可以构建一个简单的加密网络应用平台。

数字证书类似于个人身份证,由数字证书颁发认证机构(Certificate Authority, CA)签发。只有经过CA签发的证书在网络中才具备可认证性。CA颁发给自己的证书叫根证书。

VeriSign, GeoTrust和Thawte是国际权威数字证书颁发认证机构的三巨头。其中应用最广泛的是VeriSign签发的电子商务用数字证书。

最为常用的非对称加密算法是RSA,与之配套的签名算法是SHA1withRSA,最常用的消息摘要算法是SHA1.

 

除了RSA,还可以使用DSA算法。只是使用DSA算法无法完成加密解密实现,即这样的证书不包括加密解密功能。

数字证书有多种文件编码格式,主要包含CER编码,DER编码等。

CER(Canonical Encoding Rules, 规范编码格式),DER(Distinguished Encoding Rules 卓越编码格式),两者的区别是前者是变长模式,后者是定长模式。

所有证书都符合公钥基础设施(PKI, Public Key Infrastructure)制定的ITU-T X509国际标准(X.509标准)。

2.模型分析

在实际应用中,很多数字证书都属于自签名证书,即证书申请者为自己的证书签名。这类证书通常应用于软件厂商内部发放的产品中,或约定使用该证书的数据交互双方。数字证书完全充当加密算法的载体,为必要数据做加密解密和签名验签等操作。在我司的开发过程中,数字证书更多是用来做加密和解密。

1)证书签发

2)加密交互,图略。

当客户端获取到服务器下发的数字证书后,就可以进行加密交互了。具体做法是:

客户端使用公钥,加密后发送给服务端,服务端用私钥进行解密验证。

服务端使用私钥进行加密和数字签名。

3. KeyTool 管理证书

KeyTool与本地密钥库相关联,将私钥存于密钥库,公钥则以数字证书输出。KeyTool位于JDK目录下的bin目录中,需要通过命令行进行相应的操作。

1)构建自签名证书

申请数字证书之前,需要在密钥库中以别名的方式生成本地数字证书,建立相应的加密算法,密钥,有效期等信息。

keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 3600 -alias myCertificate -keystore myKeystore.keystore

各参数含义如下:

-genkeypair  表示生成密钥对

-keyalg    指定密钥算法,这里是RSA

-keysize    指定密钥长度,默认1024,这里指定2048

-sigal     指定签名算法,这里是SHA1withRSA

-validity    指定有效期,单位为天

-alias     指定别名

-keystore    指定密钥库存储位置

这里我输入参数Changeme123作为密钥库的密码,也可通过参数-storepass指定密码。可以用-dname "CN=xxx...."这样的形式,避免更多交互。

注意:一个keystore应该是可以存储多套<私钥-数字证书>的信息,通过别名来区分。通过实践,调用上述命令两次(别名不同),生成同一个keystore,用不同别名进行加密解密和签名验签,没有任何问题。

更多命令可参考:http://blog.chinaunix.net/uid-17102734-id-2830223.html 

经过上述操作后,密钥库中已经创建了数字证书。虽然这时的数字证书并没有经过CA认证,但并不影响我们使用。我们仍可将证书导出,发送给合作伙伴进行加密交互。

keytool -exportcert -alias myCertificate -keystore myKeystore.keystore -file myCer.cer -rfc

各参数含义如下:

-exportcert  表示证书导出操作

-alias     指定别名

-keystore   指定密钥库文件

-file      指定导出证书的文件路径

-rfc      指定以Base64编码格式输出

打印证书

keytool -printcert -file myCer.cer

2)构建CA签发证书

如果要获取CA机构谁的数字证书,需要将数字证书签发申请(CSR)导出,经由CA机构认证并颁发,将认证后的证书导入本地密钥库和信息库。

keytool -certreq -alias myCertificate -keystore myKeystore.keystore -file myCsr.csr -v

各参数含义如下:

-certreq    表示数字证书申请操作

-alias      指定别名

-keystore    指定密钥库文件路径

-file      指定导出申请的路径

-v       详细信息

获得签发的数字证书后,需要将其导入信任库。

keytool -importcert -trustcacerts -alias myCertificate -file myCer.cer -keystore myKeystore.keystore

参数不作详细讲解,如果是原来的证书文件,那么会报错:

查看证书

keytool -list -alias myCertificate -keystore myKeystore.keystore

 

经过上述的所有操作后,可以得到下面几个文件

4. 证书使用

终于到了激动人心的时刻,可以用代码通过keystore进行加解密操作了!

Java 6提供了完善的数字证书管理实现,我们几乎无需关注,仅通过操作密钥库和数字证书就可完成相应的加密解密和签名验签过程。

密钥库管理私钥,数字证书管理公钥,公钥和私钥分属消息传递双方,进行加密消息传递。

考虑一个场景。

A机器某模块需要将数据导出到一个文件中,将文件发送到B机器,由B将数据导入。

在这个场景中,A就相当于服务端,需要将证书给B,同时用私钥加密数据,生成签名,导出到文件中。

B相当于客户端,用收到的数字证书进行解密和验签。

  1 package jdbc.pro.lin;
  2 
  3 import java.io.FileInputStream;
  4 import java.io.FileNotFoundException;
  5 import java.io.IOException;
  6 import java.io.InputStream;
  7 import java.security.InvalidKeyException;
  8 import java.security.KeyStore;
  9 import java.security.KeyStoreException;
 10 import java.security.NoSuchAlgorithmException;
 11 import java.security.PrivateKey;
 12 import java.security.PublicKey;
 13 import java.security.Signature;
 14 import java.security.SignatureException;
 15 import java.security.UnrecoverableKeyException;
 16 import java.security.cert.Certificate;
 17 import java.security.cert.CertificateException;
 18 import java.security.cert.CertificateFactory;
 19 import java.security.cert.X509Certificate;
 20 
 21 import javax.crypto.BadPaddingException;
 22 import javax.crypto.Cipher;
 23 import javax.crypto.IllegalBlockSizeException;
 24 import javax.crypto.NoSuchPaddingException;
 25 
 26 public class MyCertifacate {
 27     private static final String STORE_PASS = "Changeme123";
 28     private static final String ALIAS = "myCertificate";
 29     private static final String KEYSTORE_PATH = "D:\\JavaDemo\\Certifacate\\myKeystore.keystore";
 30     private static final String CERT_PATH = "D:\\JavaDemo\\Certifacate\\myCer.cer";
 31     private static final String PLAIN_TEXT = "MANUTD is the most greatest club in the world.";
 32     /** JDK6只支持X.509标准的证书 */
 33     private static final String CERT_TYPE = "X.509";
 34 
 35     public static void main(String[] args) throws IOException {
 36         /**
 37          * 假设现在有这样一个场景 。A机器上的数据,需要加密导出,然后将导出文件放到B机器上导入。 在这个场景中,A相当于服务器,B相当于客户端
 38          */
 39 
 40         /** A */
 41         KeyStore keyStore = getKeyStore(STORE_PASS, KEYSTORE_PATH);
 42         PrivateKey privateKey = getPrivateKey(keyStore, ALIAS, STORE_PASS);
 43         X509Certificate certificate = getCertificateByKeystore(keyStore, ALIAS);
 44 
 45         /** 加密和签名 */
 46         byte[] encodedText = encode(PLAIN_TEXT.getBytes(), privateKey);
 47         byte[] signature = sign(certificate, privateKey, PLAIN_TEXT.getBytes());
 48 
 49         /** 现在B收到了A的密文和签名,以及A的可信任证书 */
 50         X509Certificate receivedCertificate = getCertificateByCertPath(
 51                 CERT_PATH, CERT_TYPE);
 52         PublicKey publicKey = getPublicKey(receivedCertificate);
 53         byte[] decodedText = decode(encodedText, publicKey);
 54         System.out.println("Decoded Text : " + new String(decodedText));
 55         System.out.println("Signature is : "
 56                 + verify(receivedCertificate, decodedText, signature));
 57     }
 58 
 59     /**
 60      * 加载密钥库,与Properties文件的加载类似,都是使用load方法
 61      * 
 62      * @throws IOException
 63      */
 64     public static KeyStore getKeyStore(String storepass, String keystorePath)
 65             throws IOException {
 66         InputStream inputStream = null;
 67         try {
 68             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
 69             inputStream = new FileInputStream(keystorePath);
 70             keyStore.load(inputStream, storepass.toCharArray());
 71             return keyStore;
 72         } catch (KeyStoreException | NoSuchAlgorithmException
 73                 | CertificateException | IOException e) {
 74             // TODO Auto-generated catch block
 75             e.printStackTrace();
 76         } finally {
 77             if (null != inputStream) {
 78                 inputStream.close();
 79             }
 80         }
 81         return null;
 82     }
 83 
 84     /**
 85      * 获取私钥
 86      * 
 87      * @param keyStore
 88      * @param alias
 89      * @param password
 90      * @return
 91      */
 92     public static PrivateKey getPrivateKey(KeyStore keyStore, String alias,
 93             String password) {
 94         try {
 95             return (PrivateKey) keyStore.getKey(alias, password.toCharArray());
 96         } catch (UnrecoverableKeyException | KeyStoreException
 97                 | NoSuchAlgorithmException e) {
 98             // TODO Auto-generated catch block
 99             e.printStackTrace();
100         }
101         return null;
102     }
103 
104     /**
105      * 获取公钥
106      * 
107      * @param certificate
108      * @return
109      */
110     public static PublicKey getPublicKey(Certificate certificate) {
111         return certificate.getPublicKey();
112     }
113 
114     /**
115      * 通过密钥库获取数字证书,不需要密码,因为获取到Keystore实例
116      * 
117      * @param keyStore
118      * @param alias
119      * @return
120      */
121     public static X509Certificate getCertificateByKeystore(KeyStore keyStore,
122             String alias) {
123         try {
124             return (X509Certificate) keyStore.getCertificate(alias);
125         } catch (KeyStoreException e) {
126             // TODO Auto-generated catch block
127             e.printStackTrace();
128         }
129         return null;
130     }
131 
132     /**
133      * 通过证书路径生成证书,与加载密钥库差不多,都要用到流。
134      * 
135      * @param path
136      * @param certType
137      * @return
138      * @throws IOException
139      */
140     public static X509Certificate getCertificateByCertPath(String path,
141             String certType) throws IOException {
142         InputStream inputStream = null;
143         try {
144             // 实例化证书工厂
145             CertificateFactory factory = CertificateFactory
146                     .getInstance(certType);
147             // 取得证书文件流
148             inputStream = new FileInputStream(path);
149             // 生成证书
150             Certificate certificate = factory.generateCertificate(inputStream);
151 
152             return (X509Certificate) certificate;
153         } catch (CertificateException | IOException e) {
154             // TODO Auto-generated catch block
155             e.printStackTrace();
156         } finally {
157             if (null != inputStream) {
158                 inputStream.close();
159             }
160         }
161         return null;
162 
163     }
164 
165     /**
166      * 从证书中获取加密算法,进行签名
167      * 
168      * @param certificate
169      * @param privateKey
170      * @param plainText
171      * @return
172      */
173     public static byte[] sign(X509Certificate certificate,
174             PrivateKey privateKey, byte[] plainText) {
175         /** 如果要从密钥库获取签名算法的名称,只能将其强制转换成X509标准,JDK 6只支持X.509类型的证书 */
176         try {
177             Signature signature = Signature.getInstance(certificate
178                     .getSigAlgName());
179             signature.initSign(privateKey);
180             signature.update(plainText);
181             return signature.sign();
182         } catch (NoSuchAlgorithmException | InvalidKeyException
183                 | SignatureException e) {
184             // TODO Auto-generated catch block
185             e.printStackTrace();
186         }
187 
188         return null;
189     }
190 
191     /**
192      * 验签,公钥包含在证书里面
193      * 
194      * @param certificate
195      * @param decodedText
196      * @param receivedignature
197      * @return
198      */
199     public static boolean verify(X509Certificate certificate,
200             byte[] decodedText, final byte[] receivedignature) {
201         try {
202             Signature signature = Signature.getInstance(certificate
203                     .getSigAlgName());
204             /** 注意这里用到的是证书,实际上用到的也是证书里面的公钥 */
205             signature.initVerify(certificate);
206             signature.update(decodedText);
207             return signature.verify(receivedignature);
208         } catch (NoSuchAlgorithmException | InvalidKeyException
209                 | SignatureException e) {
210             // TODO Auto-generated catch block
211             e.printStackTrace();
212         }
213         return false;
214     }
215 
216     /**
217      * 加密。注意密钥是可以获取到它适用的算法的。
218      * 
219      * @param plainText
220      * @param privateKey
221      * @return
222      */
223     public static byte[] encode(byte[] plainText, PrivateKey privateKey) {
224         try {
225             Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
226             cipher.init(Cipher.ENCRYPT_MODE, privateKey);
227             return cipher.doFinal(plainText);
228         } catch (NoSuchAlgorithmException | NoSuchPaddingException
229                 | InvalidKeyException | IllegalBlockSizeException
230                 | BadPaddingException e) {
231             // TODO Auto-generated catch block
232             e.printStackTrace();
233         }
234 
235         return null;
236 
237     }
238 
239     /**
240      * 解密,注意密钥是可以获取它适用的算法的。
241      * 
242      * @param encodedText
243      * @param publicKey
244      * @return
245      */
246     public static byte[] decode(byte[] encodedText, PublicKey publicKey) {
247         try {
248             Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
249             cipher.init(Cipher.DECRYPT_MODE, publicKey);
250             return cipher.doFinal(encodedText);
251         } catch (NoSuchAlgorithmException | NoSuchPaddingException
252                 | InvalidKeyException | IllegalBlockSizeException
253                 | BadPaddingException e) {
254             // TODO Auto-generated catch block
255             e.printStackTrace();
256         }
257 
258         return null;
259     }
260 }
View Code

 

posted @ 2015-11-26 00:02  kingsleylam  阅读(22894)  评论(0编辑  收藏  举报