密码和Java中的加解密之对称加密(一)

3 篇文章 0 订阅
2 篇文章 0 订阅
对称加密
也就是秘密密钥加密。对称加密和解密使用同一个密钥。
加密时,我们用这个密钥初始化密码算法,密码算法对经过它的数据进行加密。
解密过程与加密类似,密码算法用同一个密钥初始化,密码算法对经过它的数据进行解密。
加密的强度依赖于密钥的长度。一般的对称密钥长度在40-128位之间,有些算法可能更长。
建议使用128位以上的对称加密算法,如:DESede(TripleDES)、Blowfish、AES
对称加密比不对称加密(如公钥加密)要快的多,比较适合于大量数据加/解密的情况下(如:文件加密)。

javax.crypto.Cipher
例:Cipher cip = Cipher.getInstance("DESede/CBC/PKCS5Padding");
参数是算法,共分为三段('/'分隔)

第一段是算法名字


第二段是算法使用的模式
模式定了Cipher如何应用加密算法。
改变模式可以允许一个块加密程序变为流加密程充。
分组加密每次加密一个数据组,这个组的位数可以是随意的,一般是64或128位。
流加密每次可以加密或解密一个字节的数据,使其更适合网络通信等。
常用的模式:
ECB(电码本):最简单的模式。同样的明文总是加密成相同的密文分组。

CBC(密码分组链接):同样的明文分组不一定加密成同样的密文块。
CBC使用前一个分组的信息加密当前的分组,这个方法存在的问题是相同的信息仍旧加密成相同的密文,因为所有的分组是同时变成密文分组。
为了解决这个问题需要一个IV(初始化向量),IV仅仅是一个初始化加密程序的随机数,它无需秘密保存,但对每一个信息来说它都是不同的。
通过这种方式,既使有两条相同的信息,只要它们的IV不同,那么它们加密后的密文也是不同的。
所以一个初始化向量就象是口令加密中的盐。

CFB(密码反馈):工作方式与CBC类似,但它可执行更小的数据块,典型的有8位。也需要一个IV。

OFB(输出反馈):在传输中能给数据提供更好的保护,防止数据丢失,其他与CFB类似。
密文中一位出错,也只造成明文中的一位出错。其他方式会造成整个块丢失。也需要一个IV。


第三段是填充符
大多数算法支持两种填充符:
1)No padding 既没有填充,这就要求加密以一个完整的块结束,而没有多余的数据。
2)RKCS#5 padding 工作方式为:一个未被填满的字节被赋予一个指定的数据,这个数值的大小就是块未被写满的字节数。
例如,一个8字节的块,其中仅有3个字节写满了数据,那么就需要对5个节字填充,而这5个字节都被赋予数值'5',这是因为有5个字节需要填充。

如果数据长度正好为8字节的倍数,那么就需要在数据结尾处加一个完整的填序块(既8个为'8'的字节)。解密后,这个填充块必须被移除。


密钥打包和解包
一种是用JCE提供的打包和解包功能,另一种是拿到Key.getEncoded()做处理。

例:

package test1;

import java.security.Key;
import java.security.SecureRandom;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

/**
 * 
 * @author shanl
 *
 */
public class Test10 {
	public static void main(String[] args){
		t2();
//		t1();
	}
	
	/**
	 * 密钥使用Key.getEncoded()
	 */
	static void t3(){
		try {
			KeyGenerator keyGener = KeyGenerator.getInstance("DESede", "SunJCE");
			Key key = keyGener.generateKey();
			byte[] keyBys = key.getEncoded();
			//用PBE对keyBys既可
		} catch (Exception e) {		
			e.printStackTrace();
		}
	}
	
	/**
	 * 密钥使用JCE打包/解包
	 */
	static void t2(){
		try {
			char[] passwd = "123456@".toCharArray();
			PBEKeySpec keySpec = new PBEKeySpec(passwd);
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede","SunJCE");
			Key passwdKey = keyFactory.generateSecret(keySpec);
			int iterations = 100;
			byte[] salt = new byte[8];
			Random rand = new Random();
			rand.nextBytes(salt);
			PBEParameterSpec paramSpec = new PBEParameterSpec(salt, iterations);
			
			KeyGenerator keyGener = KeyGenerator.getInstance("DESede", "SunJCE");
			Key key = keyGener.generateKey();
			byte[] bys = key.getEncoded();
			System.out.println("密钥:");
			showBys(bys);
						
			Cipher cip = Cipher.getInstance("PBEWithSHA1AndDESede","SunJCE");
			cip.init(Cipher.WRAP_MODE, passwdKey, paramSpec);
			byte[] encryptKeyBys = cip.wrap(key);
			System.out.println("加密后:");
			showBys(encryptKeyBys);
			
			cip.init(Cipher.UNWRAP_MODE, passwdKey, paramSpec);
			key = cip.unwrap(encryptKeyBys, "DESede", Cipher.SECRET_KEY);
			bys = key.getEncoded();
			System.out.println("解密后:");
			showBys(bys);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 对称密钥加/解密测试
	 */
	static void t1(){
		try {
			//SecureRandom初始化很慢,尽量使它只初始化一次
			SecureRandom sr = new SecureRandom();
			
			KeyGenerator keyGener = KeyGenerator.getInstance("DESede", "SunJCE");
			keyGener.init(168);
			Key key = keyGener.generateKey();
			
			Cipher cip = Cipher.getInstance("DESede", "SunJCE");
			cip.init(Cipher.ENCRYPT_MODE, key, sr);
			
			byte[] srcBys = "TripleDES (分组密码)有时也被称为DESede,密钥长度168位(112位有效)".getBytes("UTF-8");
			System.out.println("明文:");
			showBys(srcBys);
			
			//可以多次update()
//			cip.update(srcBys);
			//如果只有一个byte组,可以直接doFinal(srcBys);
			byte[] cipBys = cip.doFinal(srcBys);
			System.out.println("加密后:");
			showBys(cipBys);
						
			cip.init(Cipher.DECRYPT_MODE, key, sr);
//			cip.update(cipBys);
			byte[] bys = cip.doFinal(cipBys);
			System.out.println("解密后:");
			showBys(bys);			
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	static void showBys(byte[] bys){
		for(byte b:bys){
			System.out.print(b+" ");
		}
		
		System.out.println("\n");
	}
}




基于口令的加密(PBE)
PBE使用一个口令作为密钥,这样是非常便利的,但口令加密一般不如TripleDES及Blowfish等二进制密钥安全。

口令加密使用的是杂凑加密和普通对称加密的组合。口令由消息摘要算法(如MD5和SHA)进行杂凑,杂凑的结果为对称算法构造一个数组密钥。
口令加密存的隐患之一是可能要创建一个预编译的口令列表,杂凑列表中的口令,产生一套密钥用于加密数据。
攻击者可以使用所有可能的密钥进行查找,并很快就能查到哪一个密钥是所用的加密密钥。

1.1 盐
在对数据杂凑前添加一些随机数,可以增加创建口令时可用的密钥数。
盐存在于加密的数据中,当一块新的数据被加密后,就会产生个新的盐。这意味着同样的短语加密后都会产生不同的值,甚至在口令相同的情况下也是如此。
解密时盐必顺从加密的数据中提取,并与口令组合产生解密密钥。

1.2 重复计算
重复计算是为了增加攻击者测试口令时间。
如果重复计算1000次,就需要杂凑口令字1000字,做1000次比计算一次更为复杂,攻击者要想攻击口令加密不得不花费1000倍或者更多的计算机资源。

1.3 常见的PBE算法
算法/密钥长度/默认密钥长度:

PBEWithMD5AndDES/56/56 (由于DES密钥太短,尽量不要用它)
PBEWithMD5AndTripleDES/112,168/168 (SunJCE有问题,需要下载新包)
PBEWithSHA1AndDESede/112,168/168
PBEWithSHA1AndRC2_40/40 to 1024/128

工作模式:CBC
填充方式:PKCS5Padding 

PBE例:

package test1;

import java.security.Key;
import java.security.SecureRandom;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

/**
 * PBE工具
 * @author shanl
 *
 */
public class PBETool {
	/**加密*/
	public static final int ENCRYPT_MODE = Cipher.ENCRYPT_MODE;	
	/**解密*/
	public static final int DECRYPT_MODE = Cipher.DECRYPT_MODE;
	
	static{
		//SecureRandom初始化比较慢,所以最好不要多次初始化
		sr = new SecureRandom();
	}
	
	protected static final SecureRandom sr;
	protected String algorithm = "PBEWithMD5AndDES";
	protected String mode = null;
	protected String padding = null;
	protected String provider = "SunJCE";
	protected int iterations = 1000;
	protected Key key = null;	
	protected byte[] salt = null;
	private int processMode = -1;
	
	/**
	 * 创建一个盐
	 */
	protected void initSalt(){
		this.salt = new byte[8];
		Random rand = new Random();		 
		rand.nextBytes(this.salt);
	}
	
	/**
	 * 用密码创建Key
	 * @param passwd
	 * @throws Exception 
	 */
	protected void initKey(char[] passwd)throws Exception{
		PBEKeySpec keySpec = new PBEKeySpec(passwd);
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm,provider);
		this.key = keyFactory.generateSecret(keySpec);
		
	}
	
	/** 
	 * 创建新的盐完成初始化
	 * @param processMode 处理模式
	 * @param passwd 密码
	 */
	public void init(int processMode,char[] passwd){
		try{
			this.processMode = processMode;
			initSalt();
			initKey(passwd);
		}catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}
	
	/**
	 * 使用一个已存在的盐完成初始化
	 * @param processMode 处理模式
	 * @param passwd 密码
	 * @param salt 盐
	 */
	public void init(int processMode,char[] passwd, byte[] salt){
		try{
			try{
				this.processMode = processMode; 
				setSalt(salt);
				initKey(passwd);
			}catch(Exception ex){
				throw new RuntimeException(ex);
			}
		}catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}
	
	/**
	 * 处理
	 * @param data
	 * @return
	 */
	public byte[] doProcess(byte[] data){
		try{
			String _algorithm = algorithm;			
			_algorithm = (null!=mode && null!=padding)? _algorithm+"/"+mode+"/"+padding: _algorithm;
			
			PBEParameterSpec paramSpec = new PBEParameterSpec(salt, iterations);		
			Cipher cipher = Cipher.getInstance(_algorithm);
			cipher.init(processMode, this.key, paramSpec, sr);
			return cipher.doFinal(data);
		}catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}
	
	protected void setSalt(byte[] salt){
		this.salt = salt;
	}
	
	public byte[] getSalt(){
		return this.salt;
	}
	
	public void setAlgorithm(String algorithm) {
		this.algorithm = algorithm;
	}

	public void setMode(String mode) {
		this.mode = mode;
	}

	public void setPadding(String padding) {
		this.padding = padding;
	}

	public void setProvider(String provider) {
		this.provider = provider;
	}

	public void setIterations(int iterations) {
		this.iterations = iterations;
	}
}

测试类:

package test1;

public class PBEToolTest1 {
	public static void main(String[] args){
//		t3();
		t2();
//		t1();
	}
	
	/**
	 * 使用其他算
	 */
	static void t3(){
		PBETool pbe = new PBETool();
		
		char[] passwd = "123456@".toCharArray();
		byte[] src = "abc123@#%".getBytes();		
		
//		pbe.setAlgorithm("PBEWithMD5AndDES");
//		pbe.setAlgorithm("PBEWithMD5AndTripleDES"); //SunJCE的PBEWithMD5AndTripleDES有问题,需要下新包
		pbe.setAlgorithm("PBEWithSHA1AndDESede"); //这个比PBEWithMD5AndDES更安全
//		pbe.setAlgorithm("PBEWithSHA1AndRC2_40");
		pbe.setProvider("SunJCE");
		pbe.setMode("CBC");
//		pbe.setPadding("PKCS5Padding");
		
		showBys(src);
		pbe.init(PBETool.ENCRYPT_MODE, passwd);
		byte[] salt = pbe.getSalt();			
		byte[] bys = pbe.doProcess(src);
		showBys(bys);
		
		pbe.init(PBETool.DECRYPT_MODE, passwd, salt);			
		bys = pbe.doProcess(bys);
		showBys(bys);
	}
	
	/**
	 * 多次加/解密测试
	 */
	static void t2(){
		long l = System.currentTimeMillis();
		
		//第一次执行 比较慢,后面很快
		for(int i=0;i<10;i++){
			t1();
		}		
		
		System.out.println("耗时(毫秒):"+(System.currentTimeMillis()-l));
	}
	
	/**
	 * 加/解密测试
	 */
	static void t1(){
		PBETool pbe = new PBETool();
		
		char[] passwd = "123456@".toCharArray();
		byte[] src = "abc123@#%".getBytes();		
		
		pbe.setMode("CBC");
//		pbe.setPadding("PKCS5Padding");
		
		showBys(src);
		pbe.init(PBETool.ENCRYPT_MODE, passwd);
		byte[] salt = pbe.getSalt();			
		byte[] bys = pbe.doProcess(src);
		showBys(bys);
		
		pbe.init(PBETool.DECRYPT_MODE, passwd, salt);			
		bys = pbe.doProcess(bys);
		showBys(bys);
	}
	
	static void showBys(byte[] bys){
		for(byte b:bys){
			System.out.print(b+" ");
		}
		
		System.out.println("\n");
	}
}


/**
	 * 加/解密用的口令
	 */
	static final char[] secretPasswd = ".Hj0*6%1$k".toCharArray();
	
	static final byte[] secretSalt = {53,16,-2,92,-31,106,13,40};
	
	static final int secretIterations = 13;
	

	/**
	 * base64解密<br/>
	 * 需要jdk的rt.jar支持
	 * @param data
	 * @return
	 */
	public static 
	byte[] base64Decode(String data){		
		try {
			BASE64Decoder b64Decoder = new BASE64Decoder();
			return b64Decoder.decodeBuffer(data);
		} catch (IOException e) {		
			throw new RuntimeException(e);
		}
	}

	/**
	 * base64方式加密<br/>
	 * 需要jdk的rt.jar支持
	 * @param data
	 * @return
	 */
	public static 
	String base64Encode(byte[] data){
		try {
			BASE64Encoder b64Encoder = new BASE64Encoder();
			return b64Encoder.encode(data);
		} catch (Exception e) {		
			throw new RuntimeException(e);
		}
	}	
	
	/**
	 * 对base64处理过的数据解密为原文
	 * @param data
	 * @return
	 */
	public static 
	String decrypt(String data){
		String res = null;
		try {			
			byte[] tmp = base64Decode(data);
			byte[] dd = decrypt(tmp);
			res = new String(dd, "UTF-8");
		} catch (Exception e) {		
			e.printStackTrace();
		}
		return res;
	}

	/**
	 * 基于口令的解密方法
	 * @param data
	 * @return
	 */
	public static 
	byte[] decrypt(byte[] data){
		byte[] res = null;
		
		try{
			PBEKeySpec keySpec = new PBEKeySpec(secretPasswd);  
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede","SunJCE");  
            Key passwdKey = keyFactory.generateSecret(keySpec);            
            PBEParameterSpec paramSpec = new PBEParameterSpec(secretSalt, secretIterations);

            Cipher cip = Cipher.getInstance("PBEWithSHA1AndDESede", "SunJCE");
            cip.init(Cipher.DECRYPT_MODE, passwdKey, paramSpec);
            //数据加密
            res = cip.doFinal(data);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		
		return res;
	}
	
	/**
	 * 对原文进行加密,加密的数据为base64格式.
	 * @param src
	 * @return
	 */
	public static 
	String encrypt(String src){
		String res = null;
		try {			
			byte[] ed = encrypt(src.getBytes("UTF-8"));			
			res = base64Encode(ed);
		} catch (Exception e) {		
			e.printStackTrace();
		}
		return res;
	}
	
	/**
	 * 基于口令的加密方法
	 * @param src
	 * @return
	 */
	public static 
	byte[] encrypt(byte[] src){
		byte[] res = null;
		
		try{		
            PBEKeySpec keySpec = new PBEKeySpec(secretPasswd);  
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede","SunJCE");  
            Key passwdKey = keyFactory.generateSecret(keySpec);            
            PBEParameterSpec paramSpec = new PBEParameterSpec(secretSalt, secretIterations);

            Cipher cip = Cipher.getInstance("PBEWithSHA1AndDESede", "SunJCE");
            cip.init(Cipher.ENCRYPT_MODE, passwdKey, paramSpec);
            //数据加密
            res = cip.doFinal(src);
		}catch(Exception ex){
			ex.printStackTrace();
		}
		
		return res;
	}	




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值