最近初步接触了下Java加密和数字签名的相关内容,我学习的过程大概分五步:
1)消息摘要
2)私钥加密
3)公钥加密
4)数字签名
5)数字证书
6)keystore提取私钥和证书
1)消息摘要
1. package
2.
3. import
4.
5. /**
6. * 消息摘要是一种与消息认证码结合使用以确保消息完整性的技术
7. * 目前广泛使用的算法有MD4、MD5、SHA-1
8. * 在java中进行消息摘要很简单, java.security.MessageDigest提供了一个简易的操作方法,如下
9. * 注意:消息摘要是单向的
10. */
11. public class
12. public static void main(String[] args) throws
13.
14. "asdf"
15. "摘要前:"
16.
17. //初始信息要转换成字节流的形式
18. byte [] plainText = beforeDegist.getBytes( "UTF8"
19.
20. //使用getInstance("算法")来获得消息摘要,这里使用SHA-1的160位算法
21. // MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
22. "MD5"
23.
24. // System.out.println("\n" + messageDigest.getProvider().getInfo());
25.
26. //开始使用算法
27. messageDigest.update(plainText);
28.
29. //输出算法运算结果
30. new String(messageDigest.digest(), "UTF8"
31. "摘要后:"
32. }
33. }
2)私钥加密
1. package
2.
3. import
4. import
5. import
6.
7. /**
8. * 此例子是对一个字符串信息,用一个私钥(key)加密,然后在用该私钥解密,验证是否一致
9. * 私钥加密,是对称加密
10. * 使用对称算法。比如:A用一个密钥对一个文件加密,而B读取这个文件的话,则需要和A一样的密钥,双方共享一
11. * 个私钥(而在web环境下,私钥在传递时容易被侦听)
12. *
13. * 附:主要对称算法有:DES(实际密钥只用到56 位)
14. * AES(支持三种密钥长度:128、192、256位),通常首先128位,其他的还有DESede等
15. */
16. public class
17. public static void main(String[] args) throws
18.
19. "asdf"
20. byte [] plainText = before.getBytes( "UTF8"
21.
22. //1步**********************************************************************
23. "Start generate AES key."
24. //得到一个使用AES算法的KeyGenerator的实例
25. "AES"
26. //定义密钥长度128位
27. 128
28. //通过KeyGenerator产生一个key(密钥算法刚才已定义,为AES)
29. Key key = keyGen.generateKey();
30. "Finish generating AES key."
31.
32.
33. //2步**********************************************************************
34. //获得一个私钥加密类Cipher,定义Cipher的基本信息:ECB是加密方式,PKCS5Padding是填充方法
35. "AES/ECB/PKCS5Padding"
36. // System.out.println("\n" + cipher.getProvider().getInfo());
37.
38.
39. //3步**********************************************************************
40. //使用私钥加密
41. "\n用私钥加密..."
42. //把刚才生成的key当作参数,初始化使用刚才获得的私钥加密类,Cipher.ENCRYPT_MODE意思是加密
43. cipher.init(Cipher.ENCRYPT_MODE, key);
44.
45. //私钥加密类Cipher进行加密,加密后返回一个字节流byte[]
46. byte
47.
48. //以UTF8格式把字节流转化为String
49. new String(cipherText, "UTF8"
50. "用私钥加密完成:"
51.
52.
53. //4步**********************************************************************
54. //使用私钥对刚才加密的信息进行解密,看看是否一致,Cipher.DECRYPT_MODE意思是解密钥
55. "\n用私钥解密..."
56. cipher.init(Cipher.DECRYPT_MODE, key);
57.
58. //对刚才私钥加密的字节流进行解密,解密后返回一个字节流byte[]
59. byte
60.
61. new String(newPlainText, "UTF8"
62. "用私钥解密完成:"
63. }
64. }
1. package
2.
3. import
4. import
5. import
6.
7. /**
8. * 此例子是一个公钥加密例子,Cipher类使用KeyPairGenerator(顾名思义:一对钥匙生成器)生成的公钥和私钥
9. *
10. * 公钥加密也叫不对称加密,不对称算法使用一对密钥对,一个公钥,一个私钥,使用公钥加密的数据,只有私钥能
11. * 解开(可用于加密);同时,使用私钥加密的数据,只有公钥能解开(签名)。但是速度很慢(比私钥加密慢100到
12. * 1000倍),公钥的主要算法有RSA,还包括Blowfish,Diffie-Helman 等
13. */
14. public class
15. public static void main(String[] args) throws
16.
17. "asdf"
18. byte [] plainText = before.getBytes( "UTF8"
19.
20. //产生一个RSA密钥生成器KeyPairGenerator(顾名思义:一对钥匙生成器)
21. "RSA"
22. //定义密钥长度1024位
23. 1024
24. //通过KeyPairGenerator产生密钥,注意:这里的key是一对钥匙!!
25. KeyPair key = keyGen.generateKeyPair();
26.
27. //获得一个RSA的Cipher类,使用公钥加密
28. "RSA/ECB/PKCS1Padding"
29. // System.out.println("\n" + cipher.getProvider().getInfo());
30.
31. "\n用公钥加密..."
32. //Cipher.ENCRYPT_MODE意思是加密,从一对钥匙中得到公钥 key.getPublic()
33. cipher.init(Cipher.ENCRYPT_MODE, key.getPublic());
34. //用公钥进行加密,返回一个字节流
35. byte
36. //以UTF8格式把字节流转化为String
37. new String(cipherText, "UTF8"
38. "用公钥加密完成:"
39.
40.
41. //使用私钥解密
42. "\n用私钥解密..."
43. //Cipher.DECRYPT_MODE意思是解密,从一对钥匙中得到私钥 key.getPrivate()
44. cipher.init(Cipher.DECRYPT_MODE, key.getPrivate());
45. //用私钥进行解密,返回一个字节流
46. byte
47.
48. new String(newPlainText, "UTF8"
49. "用私钥解密完成:"
50. }
51. }
4)数字签名
1. package
2.
3. import
4. import
5. import
6. import
7.
8. /**
9. * 此例子是数字签名的例子,使用RSA私钥对消息摘要(这里指的是原始数据)进行签名,然后使用公钥验证签名
10. *
11. * A通过使用B的公钥加密数据后发给B,B利用B的私钥解密就得到了需要的数据(进过B公钥加密的数据只有B的私钥能够
12. * 解开,C没有B的私钥,所以C解不开,但C可以使用B的公钥加密一份数据发给B,这样一来,问题来了,B收到的数据到
13. * 底是A发过来的还是C发过来的呢)
14. * 由于私钥是唯一的,那么A就可以利用A自己的私钥进行加密,然后B再利用A的公钥来解密,就可以确定:一定是A的消
15. * 息,数字签名的原理就基于此
16. *
17. * 总结:A想将目标数据传给B,此时A需要准备1和2两部分
18. * 1:A使用B的公钥将原始信息加密,以起到保密作用(只有B的私钥能解开,其他人使用其他钥匙都解不开,当然就保密咯)
19. * 2:A使用A的私钥将原始信息的摘要进行签名,以起到接收方B确定是A发过来的作用(A用A的私钥对目标数据的摘要进行签
20. * 名,然后传给B,同时,C用C的私钥对任意信息进行签名也传给B,B想接受的是A的数据(比如说一个转帐请求),于是B
21. * 就通过A的公钥对接受到的两个信息进行解密,解开的就是A(A的公钥能且只能解开A的私钥加密的数据))
22. */
23. public class
24. public static void main(String[] args) throws
25.
26. "asdf"
27. byte [] plainText = before.getBytes( "UTF8"
28.
29. //形成RSA公钥对
30. "RSA"
31. 1024
32. KeyPair key = keyGen.generateKeyPair();
33.
34. //使用私钥签名**********************************************************
35. "SHA1WithRSA"
36. //sig对象得到私钥
37. //签名对象得到原始数据
38. //sig对象得到原始数据(现实中用的是原始数据的摘要,摘要的是单向的,即摘要算法后无法解密)
39. byte [] signature = sig.sign(); //sig对象用私钥对原始数据进行签名,签名后得到签名signature
40. // System.out.println(sig.getProvider().getInfo());
41. new String(signature, "UTF8"
42. "\n用私钥签名后:"
43.
44. //使用公钥验证**********************************************************
45. //sig对象得到公钥
46. //签名对象得到原始信息
47. //sig对象得到原始数据(现实中是摘要)
48. try
49. if (sig.verify(signature)) { //sig对象用公钥解密签名signature得到原始数据(即摘要),一致则true
50. "签名验证正确!!"
51. else
52. "签名验证失败!!"
53. }
54. catch
55. "签名验证失败!!"
56. }
57. }
58. }
5)数字证书
下面的代码是第五部分:数字证书
比起前四部分,这部分就稍微麻烦点了,我想我有必要给刚刚接触数字证书的朋友们,把在本地跑通下面代码的前提说一下:
1此例是对“数字证书”文件的操作,所以,你先要在本地建立一个证书库
2建立证书库(密钥库)
cmd中输入:C:/>keytool -genkey -alias TestCertification -keyalg RSA -keysize 1024 -keystore BocsoftKeyLib -validity 365
意思是:在c盘目录下创建一个证书,指定证书库为BocsoftKeyLib,创建别名为TestCertification的一条证书,它指定用 RSA 算法生成,且指定密钥长度为1024,证书有效期为1年
建立库的过程中会询问问题,详见第5条
3将证书导出到证书文件TC.cer
在cmd中输入:C:/>keytool -export -alias TestCertification -file TC.cer -keystore BocsoftKeyLib
意思是:将把证书库BocsoftKeyLib中的别名为TestCertification的证书导出到TC.cer证书文件中,它包含证书主体的信息及证书的公钥,不包括私钥,可以公开
4以上3步就基本上完成了证书的操作,下面操作是可选的
导出的证书文件无法用文本编辑器正确显示,可以输入如下命令,然后在以记事本形式打开TC.cer就能看了,看看传说中的证书里到底写的什么鬼东西~~~
C:\>keytool -export -alias TestCertification -file TC.cer -keystore BocsoftKeyLib -storepass keystore -rfc
5在创建证书库时,系统会询问如下问题:
输入keystore密码: keystore(你证书库的密码)
您的名字与姓氏是什么?
[Unknown]: miaozhuang
您的组织单位名称是什么?
[Unknown]: csii
您的组织名称是什么?
[Unknown]: csii
您所在的城市或区域名称是什么?
[Unknown]: tianjin
您所在的州或省份名称是什么?
[Unknown]: tianjin
该单位的两字母国家代码是什么
[Unknown]: ch
CN=miaozhuang, OU=csii, O=csii, L=tianjin, ST=tianjin, C=ch 正确吗?
[否]:
上面的信息都是随便输入的,无关大局
此时输入: y 回车即可(别像我一样,输个yes……傻死了)
1. package
2.
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12.
13. /**
14. * 此例是对“数字证书”文件的操作
15. * java平台(在机器上安装jdk)为你提供了密钥库(证书库),cmd下提供了keytool命令就可以创建证书库
16. *
17. * 在运行此例前:
18. * 在c盘目录下创建一个证书,指定证书库为BocsoftKeyLib,创建别名为TestCertification的一条证书,它指定用
19. * RSA 算法生成,且指定密钥长度为1024,证书有效期为1年
20. * 导出证书文件为TC.cer已存于本地磁盘C:/
21. * 密码是keystore
22. */
23.
24. public class
25.
26. public static void
27. try
28. //前提:将证书库中的一条证书导出到证书文件(我写的例子里证书文件叫TC.cer)
29. //从证书文件TC.cer里读取证书信息
30. /*CertificateFactory cf = CertificateFactory.getInstance("X.509");
31. FileInputStream in = new FileInputStream("C:/TC.cer");
32. //将文件以文件流的形式读入证书类Certificate中
33. Certificate c = cf.generateCertificate(in);
34. System.err.println("转换成String后的证书信息:"+c.toString());*/
35.
36.
37. //或者不用上面代码的方法,直接从证书库中读取证书信息,和上面的结果一摸一样
38. "keystore"
39. new FileInputStream( "C:/BocsoftKeyLib"
40. "JKS"
41. ks.load(in2,pass.toCharArray());
42. "TestCertification" ; //alias为条目的别名
43. Certificate c=ks.getCertificate(alias);
44. "转换成String后的证书信息:"
45.
46.
47. //获取获取X509Certificate类型的对象,这是证书类获取Certificate的子类,实现了更多方法
48. X509Certificate t=(X509Certificate)c;
49. //从信息中提取需要信息
50. "版本号:"
51. "序列号:" +t.getSerialNumber().toString( 16
52. "主体名:"
53. "签发者:"
54. "有效期:"
55. "签名算法:"
56. byte [] sig=t.getSignature(); //签名值
57. PublicKey pk = t.getPublicKey();
58. byte
59. "公钥:"
60. for ( int i= 0
61. ","
62. }
63. System.err.println();
64.
65.
66. //证书的日期有效性检查,颁发的证书都有一个有效性的日期区间
67. new
68. t.checkValidity(TimeNow);
69. "证书的日期有效性检查:有效的证书日期!"
70.
71.
72. //验证证书签名的有效性,通过数字证书认证中心(CA)机构颁布给客户的CA证书,比如:caroot.crt文件
73. //我手里没有CA颁给我的证书,所以下面代码执行不了
74. /*FileInputStream in3=new FileInputStream("caroot.crt");
75. //获取CA证书
76. Certificate cac = cf.generateCertificate(in3);
77. //获取CA的公钥
78. PublicKey pbk=cac.getPublicKey();
79. //c为本地证书,也就是待检验的证书,用CA的公钥校验数字证书c的有效性
80. c.verify(pbk);*/
81.
82.
83. catch (CertificateExpiredException e){ //证书的日期有效性检查:过期
84. "证书的日期有效性检查:过期"
85. catch (CertificateNotYetValidException e){ //证书的日期有效性检查:尚未生效
86. "证书的日期有效性检查:尚未生效"
87. catch
88. ce.printStackTrace();
89. catch
90. fe.printStackTrace();
91. /*catch (IOException ioe){
92.
93. } catch (KeyStoreException kse){
94.
95. }*/ catch
96. e.printStackTrace();
97. }
98.
99. }
100. }
6)keystore提取私钥和证书
keytool -genkey -alias test -keyalg RSA -keystore c:/key.store 生成keyStore
RSA是一个既能用于数据加密也能用于数字签名的算法。
DSA(Digital Signature Algorithm,数字签名算法,用作数字签名标准的一部分),它是另一种公开密钥算法,它不能用作加密,只用作数字签名。DSA使用公开密钥,为接受者验证数据的完整性和数据发送者的身份。
提取证书:通过keytool命令我们可以很轻松的提取证书.证书包括主体信息,公钥.
keytool -export -alias 别名 -keystore 文件名 -file 证书名称
但是我们无法通过KEYTOOL工具来提取私钥的..我们只能通过java的KeyStore类getEntry() 或者getKey()来提取私钥.
String keystoreFilename = "C:/keystore/server.keystore";
char[] password = "123456".toCharArray();
String alias = "Server";
KeyStore ks = KeyStore.getInstance("JKS");
java.io.FileInputStream fis = new java.io.FileInputStream(keystoreFilename);
// 从指定的输入流中加载此 KeyStore
ks.load(fis, password);
// keystore 中的每一项都用“别名”字符串标识。
// 使用指定保护参数获取指定别名的 keystore Entry。
方法1、
// KeyStore.PrivateKeyEntry 保存 PrivateKey 和相应证书链的 KeyStore 项。
KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(alias,
new KeyStore.PasswordProtection(password));
System.out.println(pkEntry.getPrivateKey());
// 返回与给定别名相关联的密钥
方法2、
PrivateKey key = (PrivateKey) ks.getKey(alias, password);
怎么来验证提取的私钥是否正确呢?(因为公钥私钥必须成对出现,我们可以通过证书提取去公钥,然后用公钥加密,使用刚刚获得的私钥解密)
提取证书的方法:
keytool -export -alias 别名 -keystore 文件名 -file 证书名称
//通过证书,获取公钥
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream("C:\\server\\server.cer");
//生成一个证书对象并使用从输入流 inStream 中读取的数据对它进行初始化。
Certificate c = cf.generateCertificate(in);
PublicKey publicKey = c.getPublicKey();
//通过下面这段代码提取的私钥是否正确
String before = "asdf";
byte[] plainText = before.getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 用公钥进行加密,返回一个字节流
byte[] cipherText = cipher.doFinal(plainText);
cipher.init(Cipher.DECRYPT_MODE, myPrivateKey);
// 用私钥进行解密,返回一个字节流
byte[] newPlainText = cipher.doFinal(cipherText);
System.out.println(new String(newPlainText, "UTF-8"));