java 安全了解

待修改: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("<html>server我是服务器返回的!</html>").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模拟运行结果:

服务端:                  




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值