待修改:1、运行结果说明 2、格式、3、其他
基本的安全知识概念:
提到java安全,有一个重要的体系叫JCA,java cryptography architecture (java的加密技术架构),包括加密,摘要,签名等等。
还有一种叫JSSE,java secure socket extension ,(java的安全的socket扩展技术) .下面是基本概念的说明。
JCA部分:
对于加密技术,加密算法是公开的,加密时需要密钥,根据密钥情况分为对称和非对称加密。
对称加密?:
密钥一样,也称为密钥加密,加密和解密的密钥必须相同。
只有通信的双方知道密钥。
特点:加密速度快。
常见的对称加密算法:AES 、DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6
非对称加密?:
密钥不一样,也称为公钥加密,加密和解密的密钥不相同,一方持有私钥,其他方都可以知道公钥。
私钥加密,公钥可以解密,公钥加密,私钥也可以解密。
特点:加密速度慢.
常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
public class Security {
static String path = "D://";
static String securityType = "RSA";
public static void main(String[] args) throws Exception {
// RSA 为非对称加密,因此需要公钥和私钥
// 公钥 (或者私钥)加密
Cipher encodeCipher = Cipher.getInstance(securityType);
// 产生公钥和私钥 钥匙对
KeyPairGenerator pairKey = KeyPairGenerator.getInstance(securityType);
KeyPair keyPair = pairKey.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 对数据进行 公钥加密
encodeCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// encodeCipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] encodeData = encodeCipher.doFinal("RSA非对称加密".getBytes("UTF-8"));
// 保存公钥和私钥 ,和加密的数据, 在解密时再读取 公钥或者私钥 和数据。
saveKey(publicKey, "public.key");
saveKey(privateKey, "private.key");
saveData(encodeData, "data.txt");
// 用私钥(或者公钥)解密
Cipher decodeCipher = Cipher.getInstance(securityType);
// 产生 解密的私钥 和 数据
Key decodeKey = getKey("private.key");
// Key decodeKey = getKey("public.key");
byte[] data = getData("data.txt");
// 解密
decodeCipher.init(Cipher.DECRYPT_MODE, decodeKey);
byte[] decodeData = decodeCipher.doFinal(data);
// 打印数据
String decodeStr = new String(decodeData, "UTF-8");
System.out.println(decodeStr);
}
public static void saveData(byte[] data, String fileName) throws Exception {
File f = new File(path + fileName);
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.close();
}
public static byte[] getData(String fileName) throws Exception {
File f = new File(path + fileName);
FileInputStream fos = new FileInputStream(f);
byte[] b = new byte[fos.available()];
fos.read(b);
fos.close();
return b;
}
public static void saveKey(Key key, String fileName) throws Exception {
File f = new File(path + fileName);
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream objectOutput = new ObjectOutputStream(fos);
objectOutput.writeObject(key);
fos.close();
objectOutput.close();
}
public static Key getKey(String fileName) throws Exception {
File f = new File(path + fileName);
FileInputStream fis = new FileInputStream(f);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
Key obj = (Key) objectInputStream.readObject();
fis.close();
objectInputStream.close();
return obj;
}
}
数字摘要?:
用一种算法(例如:MD5、SHA)对数据生成的字符串,是固定长度的,用于数据的完整性校验.
数字摘要可以理解为数字的指纹。
这个数据不论多大生成的都是固定长度的。
import java.security.MessageDigest;
public class DigitalValidate {
public static void main(String[] args) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA");
MessageDigest md5 = MessageDigest.getInstance("MD5");
//digest.update("abc".getBytes());
//byte[] result = digest.digest();
byte[] result = sha.digest("abc".getBytes());
byte[] result2 = md5.digest("abc".getBytes());
//通常看md5 解密结果 时,转为16进制表现形式。
System.out.println("SHA:"+bytesToHexString(result));
System.out.println("MD5:"+bytesToHexString(result2));
//摘要:摘要的产生是基于 数据 + 密码
//SHA:a9993e364706816aba3e25717850c26c9cd0d89d
//MD5:900150983cd24fb0d6963f7d28e17f72
}
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
数字签名?:
数字签名过后就改不了。相当与用汉字写人民币数额时加个“整”字。不能改了。
数字签名的基础是公钥和私钥的非对称加密, 发送者 用私钥 加密 数据的摘要,然后把 数据 和 私钥加密的结果 一同发出去。
接收者使用公钥 解密 内容的摘要,这用来验证这个签名是否是某个人的。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
public class Signature2 {
static String path = "D://";
static String RSA = "RSA";
static String MD5_RSA = "MD5withRSA";
static String SHA_RSA = "SHA1withRSA";
public static void main(String[] args) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = generator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
String data = "testdata:{username:abcd@abcdfa.com,password:abc}";
//数字签名 : 用私钥加密,用公钥解密。
Signature signature = Signature.getInstance(MD5_RSA);
//首先设置私钥
signature.initSign(privateKey);
//然后设置数据
signature.update(data.getBytes());
//最后签名:先md5一下,然后私钥加密一下。
byte[] signedData = signature.sign();
saveData(data.getBytes(), "signature_Data.txt");
saveData(signedData, "signature_signedData.txt");
saveKey(publicKey, "signature_public.key");
//测试,可以修改生成后的签名或者原始数据。这就要求 上面代码运行一次。
System.out.println("---发送公钥 和 签名后的数据 和 原始数据--(用存储代替)------------");
System.out.println("---完毕-下面是验证-----------------------");
System.out.println(varifyData());
}
public static boolean varifyData() throws Exception{
Signature signature = Signature.getInstance(MD5_RSA);
//获取公钥
PublicKey publicKey = (PublicKey) getKey("signature_public.key");
//获取签名(的数据)
byte[] signedData = getData("signature_signedData.txt");
//获取原始数据
byte[] data = getData("signature_Data.txt");
//设置公钥
signature.initVerify(publicKey);
//设置原始的数据
signature.update(data);
// 最后拿着签名去校验
boolean b = signature.verify(signedData);
return b;
}
public static void saveData(byte[] data,String fileName) throws Exception{
File f = new File(path+fileName);
FileOutputStream fos = new FileOutputStream(f);
fos.write(data);
fos.close();
}
public static byte[] getData(String fileName) throws Exception{
File f = new File(path+fileName);
FileInputStream fos = new FileInputStream (f);
byte[] b = new byte[fos.available()];
fos.read(b);
fos.close();
return b;
}
public static void saveKey(Key key,String fileName) throws Exception{
File f = new File(path+fileName);
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream objectOutput = new ObjectOutputStream(fos);
objectOutput.writeObject(key);
fos.close();
objectOutput.close();
}
public static Key getKey(String fileName) throws Exception{
File f = new File(path+fileName);
FileInputStream fis = new FileInputStream(f);
ObjectInputStream objectInputStream = new ObjectInputStream(fis);
Key obj = (Key) objectInputStream.readObject();
fis.close();
objectInputStream.close();
return obj;
}
}
数字证书描述说明:
公钥存放在一个叫 数字证书的 载体 中,这个数字证书上有一个权威机构的签名。这个权威机构负责颁发这种证书。
那么如果要验证一个人的公钥,例如小明,应该是这个逻辑:小明的公钥在一个证书上写着,只要别人拿着这 个证书在这个证书的颁发机构(权威机构)验证一下 ,即可。验证通过表示这就是小明的公钥。
可以这么理解,证书是公钥的一个载体。拿到的证书就拿到了公钥。
如果电脑导入一个证书表示信任这个证书,还可以信任这个证书签发的其他证书。
数字证书可以通过2中方式可以获得,一种是单独的证书文件,一种 是 keystore文件。
关于数字证书的管理,在jdkbin目录下 有个keytool工具。熟悉这个工具即可。
看看KeyStore类的帮助文档,就知道怎样使用KeyStore对象了。
关于证书的说明:
1、生成一个证书: 进入 jdk bin 目录中,命令行执行 keytool -genkeypair
2、注意执行完上面的步骤 就已经有证书了,这个证书放在keystore文件当中.
3、查看证书:keytool -list 或者 详细查看 keytool -list -v
4、从keystore文件中导出证书: keytool -exportcert -alias 证书别名(mykey) -file d:\xxx.cer ,输入证书的密码
即可导出证书。
keytool -exportcert -alias mykey -file d:\xxx.cer
5、一个证书有2种方式放置,一种是存放在 keystore文件当中,一种是证书文件(*.cer格式)。
6、将证书导入keystore文件中:keytool -importcert -alias 导入到证书中的名字(xxx) -file 证书路径 -keystore keystore的路径
例如:keytool -importcert -alias xxx -file D:\a.cer -keystore D:\b.keystore //命令执行后根据提示操作即可。
java 中操作证书:
放在keystore中的证书加载时 用jks表示:
private static void loadCertificateFromStore() throws Exception{
KeyStore keyStore = KeyStore.getInstance("jks");
FileInputStream fis = new FileInputStream("C:\\Documents and Settings\\IBM\\.keystore");
keyStore.load(fis, "123456".toCharArray());
fis.close();
Certificate cert = keyStore.getCertificate("mykey");
System.out.println(cert.toString());
}
证书文件加载时 :是国际标准证书的格式( X.509 )。
private static void loadCertificateFromFile() throws Exception{
CertificateFactory factory = CertificateFactory.getInstance("X.509");
FileInputStream fis = new FileInputStream("C:\\Java\\jdk1.6.0_21\\bin\\zxx1.cer");
Certificate cert = factory.generateCertificate(fis);
fis.close();
X509Certificate x509cert = (X509Certificate)cert;
System.out.println("公钥:" + x509cert.getPublicKey());
System.out.println("签名:" + x509cert.getSignature());
System.out.println("签名算法:" + x509cert.getSigAlgName());
System.out.println("类型:" + x509cert.getType());
System.out.println("证书所有者:" + x509cert.getSubjectDN());
System.out.println("证书发布者:" + x509cert.getIssuerDN());
System.out.println("证书起始有效日期:" + x509cert.getNotBefore());
System.out.println("证书终止有效日期:" + x509cert.getNotAfter());
}
导入证书意义:表示我的电脑系统 信任这个证书 以及信任 这个证书签发的其他证书。
JSSE部分 SSL/TLS 基本概念:
SSL(Secure Socket Layer)是netscape公司设计的主要用于web的安全传输协议。这种协议在WEB上获得了
广泛的应用。IETF(www.ietf.org)将SSL作了标准化,即RFC2246,并将其称为TLS(Transport Layer Security),
从技术上讲,TLS 1.0与SSL 3.0的差别非常微小。
基本原理:先非对称加密传递对称加密所要用的钥匙,然后双方用该钥匙对称加密和解米往来的数据。
工作过程:
1、浏览器向服务器发出请求,询问对方支持的对称加密算法和非对称加密算法;服务器回应自己支持的算法。
2、浏览器选择双方都支持的加密算法,并请求服务器出示自己的证书;服务器回应自己的证书。
3、浏览器随机产生一个用于本次会话的对称加密的钥匙,并使用服务器证书中附带的公钥对该钥匙进行加密后传递给服务器;服务器为本次会话保持该对称加密的钥匙。第三方不知道服务器的私钥,即使截获了数据也无法解密。非对称加密让任何浏览器都可以与服务器进行加密会话。
4、浏览器使用对称加密的钥匙对请求消息加密后传送给服务器,服务器使用该对称加密的钥匙进行解密;服务器使用对称加密的钥匙对响应消息加密后传送给浏览器,浏览器使用该对称加密的钥匙进行解密。第三方不知道对称加密的钥匙,即使截获了数据也无法解密。对称加密提高了加密速度。
要求
服务器端需安装数字证书,用户可能需要确认证书。
会话过程中的加密与解密过程由浏览器与服务器自动完成,对用户完全透明(开发者不用操作这个过程)
用Socket来熟悉一下SSL 通信。
第一步 准备证书:
1:产生证书、keytool -genkeypair 注意名字是域名localhost或者ip。
2:导出证书, (例如导出到D盘:xyz.cer)
3: 熟悉证书操作的类。服务器端的证书由keyManagerFactory类管理,客户端的证书由TrustManagerFactory管理。
第二步: 服务器端:创建服务器端Socket时,应该指定自己的私钥和证书在哪和是什么. 有下面两种方式
public static void main(String[] args) throws Exception { init2(); } public static void init2() throws Exception{ String user_home = System.getProperty("user.home"); /*System.setProperty("javax.net.ssl.keyStore", user_home+"/.keystore"); System.setProperty("javax.net.ssl.keyStorePassword", "666666"); */ System.out.println(user_home); char[] passphrase = "666666".toCharArray(); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(user_home + "/.keystore"), passphrase); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); //如果keystore中只有一个keyEntry,则此处表示keyEntry的密码可以与keystore的密码不同。 kmf.init(ks, passphrase); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init( kmf.getKeyManagers(), null, null); ServerSocketFactory factory = sslContext.getServerSocketFactory(); //SSLServerSocketFactory factory = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SSLServerSocket ss = (SSLServerSocket)factory.createServerSocket(8885); for (int i = 0; i < 8; i++) { final Socket s = ss.accept(); new Thread(new Runnable() { @Override public void run() { OutputStream oStream = null; InputStream inStream = null; try { oStream = s.getOutputStream(); inStream = s.getInputStream(); byte[] buf = new byte[1024]; int len = inStream.read(buf); String s = new String(buf); System.err.println("服务端收到的数据:\r\n"+s); StringBuilder sb = new StringBuilder(); sb.append("HTTP/1.1 200 OK").append("\r\n") .append("Server: bfe/1.0.8.5").append("\r\n") .append("Date: Thu, 24 Sep 2015 08:43:29 GMT").append("\r\n") .append("Content-Type: text/html;charset=utf-8").append("\r\n") .append("Connection: keep-alive").append("\r\n") //.append("Content-Length: ").append("30").append("\r\n") .append("Content-Encoding: gzip").append("\r\n").append("\r\n") .append("server我是服务器返回的!").append("\r\n").append("\r\n"); //向客户端回应数据 oStream.write(sb.toString().getBytes()); } catch (IOException e) { e.printStackTrace(); }finally{ try { oStream.close(); } catch (IOException e) { e.printStackTrace(); } try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } try { s.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start(); } }
写完服务器端后 我们可以用浏览器先访问一下,这时要打开 上面星号处的代码。
注意用https的形式:https://192.168.x.x:8888/
运行如下结果:
第三步: 客户端:创建客户端Socket时,它要验证服务器端出示的证书是否是可信赖的.有两种方式
运行客户端程序进行测试访问,如果服务器出示的证书不是由客户端已经信任的CA签名的,则必须在truststore中导入服务器端的证书。
首先:将信任的证书导入:
keytool -importcert -alias servertoclient -file d:\xyz.cer -keystore ${jdkhome}/jre/lib/security/cacerts
private static void requestSSLTest() throws Exception{
String user_home = System.getProperty("user.home");
System.setProperty("javax.net.ssl.trustStore",user_home+"/.keystore");
System.setProperty("javax.net.ssl.keyStorePassword", "666666");
/*char[] passphrase = "changeit".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("C:\\Program Files\\Java\\jdk1.8.0_25\\jre\\lib\\security\\cacerts"), passphrase);
TrustManagerFactory managerFactory = TrustManagerFactory.getInstance("PKIX");
managerFactory.init(ks);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null,managerFactory.getTrustManagers() , null);
SSLSocketFactory factory = context.getSocketFactory();*/
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
Socket socket = factory.createSocket("192.168.173.1",8885);
InputStream ips = socket.getInputStream() ;
OutputStream ops = socket.getOutputStream();
ops.write("我是客户端发送的".getBytes());
byte[] buf = new byte[1024];
while (true) {
ips.read(buf);
String str = new String(buf);
System.out.println("客户端收到:"+str);
if(str.contains("server")){
break;
}
}
ips.close();
ops.close();
socket.close();
}
public static void main(String[] args) throws Exception {
requestSSLTest();
}
socket模拟运行结果:
以上是通过socket的形式来熟悉SSL 通信。 可以看到最重要的步骤是 证书的部分。
1、首先服务器端要产生一个证书。放在keystore 或者 单独文件(x.509)都可以。
2、服务器端 出示证书
3、客户端验证 信任证书
------------------------------------------------------------------------------------------------Https--------------------------------------------------------------------------------------------------------------
用Https 来模拟请求 ,并返回数据
(本地模拟服务端,就着上面的socket的服务端 ,这里不再重复了。)
private static void https2() throws Exception{
String user_home = System.getProperty("user.home");
System.setProperty("javax.net.ssl.trustStore",user_home+"/.keystore");
System.setProperty("javax.net.ssl.keyStorePassword", "666666");
/*char[] passphrase = "changeit".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("C:\\Java\\jdk1.6.0_21\\jre\\lib\\security\\cacerts"), passphrase);
TrustManagerFactory managerFactory = TrustManagerFactory.getInstance("PKIX");
managerFactory.init(ks);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null,managerFactory.getTrustManagers() , null);
SSLSocketFactory factory = context.getSocketFactory();*/
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
// URL url = new URL("https://www.baidu.com");
URL url = new URL("https://localhost:8885");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(factory);
connection.setDoOutput(true);
OutputStream ops = connection.getOutputStream();
ops.write("post 发送:哈哈哈".getBytes());
InputStream ips = null;
byte[] buf = new byte[1024];
while (true) {
ips = connection.getInputStream() ;
ips.read(buf);
String str = new String(buf,"utf-8");
System.out.println("客户端收到:"+str);
if(str.contains("server") ){
break;
}
}
ips.close();
ops.close();
}
public static void main(String[] args) throws Exception {
https2();
}
https模拟运行结果:
服务端: