Torrent.cheers();自定义密码长度的一个文件加密类!!

废话去代码文件里面的注释吧~

直接上图上代码,有图有码有真相



package org.bruce.file.handle.handler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import org.apache.commons.io.IOUtils;
import org.bruce.file.handle.config.BYSingle;
import org.bruce.file.handle.util.BY;
import org.bruce.file.handle.util.FileLists;
import org.bruce.file.handle.util.FileNameFilter;

/**
 * @author Bruce Yang
 * 由于 RSA,DES 的 key 都有着固定的长度,若将密钥保存为文件倒是合适,
 * 但有时候却给一些情况带来不便,如:我不想随时拿着一个盛装着密钥的U盘
 * 而且,如果用U盘来装密钥,u盘一旦丢了,那就完蛋了~
 * 如果能够为加密文件设置一个自定义的密码,那么就没什么问题了,只要你不把密码忘了便行~
 * 自定义密码长度的改造 des 加密算法~
 * 
 * 另外,还想到其他几种自定义密码长度加密文件的方法,如:
 * 1。由密码字符串获取字节数组,用这个字节序列对文件数据的0101取反
 * 据介绍,DES算法加密的明文常有被破解的消息传出,
 * 试想本例的 DES 算法采用了 8 个字符长度的密码对数据进行加密,多少是比不上 rsa 加密算法,
 * 据我所至,rsa的密钥长度是 1024 bit 或 2048 bit等,长度比 des 对称算法所用的密钥长度长得多了,
 * 破解的难度自然不小,试想 DES 如果采用了那么长的密钥,安全性也不会低的~
 * 事实上,对文件数据进行加密,加密文件的安全性肯定是不能和web服务器数据库里面存放的数据相提并论的,
 * 试想如果一个具备程序编写能力和对加解密比较熟悉的人获得了你加密过的文件,
 * 他可以肆无忌惮的对你的文件进行各种各样的,不分白昼的解密尝试。
 * 和 web 服务器数据库的安全性是完全无法作对比的。
 * web服务器不高兴了,你针对某个账号做了仅仅几次的密码猜解,便会针对你的ip不让你再做多的尝试。。
 * 那么,即便密码是简单的213三个数字,也不要想有多的想法。
 * 当然,你也可以不停的换 ip 做猜解。我对这个不是很熟悉,不做多的评论。。。
 * 密码简单也并非不安全,下面还有其他几个案例:
 * 1。如银行取款机的6位密码,你又能有多少想法(有摄像头呢,你套密码就不怕被盯上?)?
 * 2。如网站会员登陆时的验证码机制;
 * 3。如淘宝的手机验证码验证机制,等等~
 * 一句话,现在的人们都学精了啊,要早出生那么十几年,猜解暴破什么的,那就容易的多了~ ^ ^
 * 花了不少时间研究这个主要是因为还没发现有一款像 windows 里面的 WinRAR 那样的软件,
 * 如果有的话,我也不用费这么多劲儿了~
 * 
 * 不过,对于一般人而言,即使是对普通的数据文件(图片,声音,视频,文档等)的2进制数据稍作修改,
 * 如将文件的文件头做下处理,让相关程序在打开文件的时候无法处理此文件,
 * 便能够过滤掉相当一部分人,让他们放弃获知文件真实内容的想法。
 * 不过,对于别有居心的人,我还是提倡尽量将加密做到底!
 * 其实,如果不是为了方便我记忆密码,采用8个字符的DES算法对文件进行加密,我个人还是觉得不那么安全的。
 * 在我的印象里面,des貌似是将 文件分割成一段一段的,然后用密码对这一段一段的内容做加密处理
 * 。。。。。思路有点儿乱了,下次有空在继续研究
 * 
 * 可改进的地方:
 * 1。写个UI壳子,这个东东基本上就可以拿来用了,到时候把种子,账号密码,私密照片什么的内容
 * 通过这个东西来处理一下,基本上就能称得上有点儿安全内涵了,不错不错...
 * 2。comment里面只能放ascii字符,到时候可以将中文用BAse64处理一下,就可以支持中文注释了,
 * 到时候也可以在GUI里面弄一个加密文件的缩略信息查看功能,
 * 要求能比较高效的拿出description部分的文件描述数据~
 * 3。zip归档~
 * 4。..暂时还没多的想法
 */
public class EncryptZipper extends Handler {
	
	/** DES对称加密所用到的密码~ */
	public static String PWD = "hello,java!";
	/** 用于存放被加密过后的文件的中转目录~ */
	public static String ENCRYPTED_DIR = "/Users/user/MyData/MyPersonal/encrypted";
	/** 用于存放解密出来的文件的中转目录~ */
	public static String DECRYPTED_DIR = "/Users/user/MyData/MyPersonal/decrypted";
	
	/** 待加密文件所处的目录,或者是待加密文件的路径~ */
	public static String RAW_FILES_DIR = "/Users/user/Desktop/test";
	
	// 筛选特定格式的文件路径~
	public static String[] SUFFIXS = {
		".des",
	};
	
	public enum Mode {
		encrypt,
		decrypt,
	};
	
//	public static Mode MODE = Mode.encrypt;
	public static Mode MODE = Mode.decrypt;
	
	public EncryptZipper() {
		/** 因为没有对源文件做内容更新,所以,完全没有必要做备份~ */
		BYSingle.BAKUP_OR_NOT = false;
		
		/** 测试 ant 的 deprecation 选项~ */
//		@SuppressWarnings("deprecation")
//		String s = new String(new byte[]{1,2,3}, 33);
	}
	
	/**
	 * 用例,使用的时候将 main(String args) 修改为 (String[] args)~
	 * 主要是怕手快不小心运行了这个类里面的入口
	 * (我真实的程序入口在另外一个地方,那里在之前会做一些安全检查操作)~
	 * 我发现我真有点儿怂了,上次乱搞磁盘被格了200多个G,
	 * 前几天搞个整站下载器,不小心删掉了我csdn博客40篇博文,
	 * 在我的辛勤恢复之下,还是丢了几篇文章(CSDN每日限制发布20篇博文)~
	 * 后面想了下,我当时的session没有关闭,整站下载器跑起来以后对我博客里面的各种连接那是一顿乱点啊,
	 * 置顶,删除什么的操作,整站下载器那是毫不犹豫地一往无前,结果我便悲催了...一把辛酸泪~
	 * 扯远了,拉回。。
	 * 修改 MODE 设置当前是加密还是解密
	 * 修改 RAW_FILES_DIR, DECRYPTED_DIR,ENCRYPTED_DIR 这几个目录确定相关文件所在的路径~
	 * 密码可以随便修改(小于我所规定的 32 个 ascii 字符即可,
	 * 实际上只有前8个字符会参与到 des 加解密,不过我有做简单的处理,还是设了一层屏障)~
	 * 还有,代码里面用到了一些我自定义的工具类:
	 * 1。BYSingle 仅仅是对加解密文件所处的路径做设置
	 * 2。BY 也只是让打印写起来更简短一点,如
	 * BY.log() == System.out.println()
	 * 事实上我还是觉得 sysout 写起来比较有感觉~
	 * 3。FileListsUtil 是一个遍历文件夹下子文件以获取所有子单文件路径的辅助类
	 * 4。FileNameFilter 是一个筛选或保留特定格式文件路径的辅助类
	 * 。。。。。
	 * 上次不经意间看到一个份对文件进行 3 重 DES 加密的代码,
	 * 效果比较蛋疼,密码是随机生成的 48 个 16 进制数
	 * 加密的时候48哥16进制数被分成 3 组,每组 16 个 16 进制数,8 个字符
	 * 文件 -> 0~15_0x数.DES加密 -> 16~31_0x数.DES加密 -> 32~47_0x数.DES加密 -> 文件
	 * 蛋疼,一个 6.6MB 的 PSD 文件我等了好一会儿(我IntelE6600CPU,3.06G主频,8G内存),我想说:
	 * 安全是算得上相当安全了,但是这个效率实在是伤不起啊~
	 * 其实我觉得我个人更倾向于比较投机的文件加密方式,如:
	 * 对文件头,文件尾的几个字节做做取反操作等..
	 * 那样毕竟要高效一点儿嘛,也能拦住不少小白了,那便是我的需求~
	 * last,很简单的一个加密实现,周末愉快~
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		/** 1。设置密码以及加解密文件存放的中转目录~ */
		EncryptZipper.PWD = "Less than 32 characters";
		EncryptZipper.ENCRYPTED_DIR = "/Users/user/MyData/MyPersonal/encrypted";
		EncryptZipper.DECRYPTED_DIR = "/Users/user/MyData/MyPersonal/decrypted";
		
		/** 2。1。加密配置~ */
//		EncryptZipper.MODE = Mode.encrypt;
//		EncryptZipper.RAW_FILES_DIR = "/Users/user/Desktop/test";
		
		/** 2。2。解密配置~ */
		EncryptZipper.MODE = Mode.decrypt;
		EncryptZipper.RAW_FILES_DIR = "/Users/user/MyData/MyPersonal/encrypted";
		
		/** 3。新建对象,执行操作(依据上面所做的配置对相关目录下的所有或者特定文件做处理)~ */
		EncryptZipper ez = new EncryptZipper();
		ez.handle(RAW_FILES_DIR);
	}
	
	@Override
	public boolean handle(String sourcePath) {
		// 1。准备工作,建立几个主要目录(如果他们不存在)~
		if(!prepare()) {
			return false;
		}
		
		// 2。收集需要进行处理的文件列表~
		List<String> pathList = new ArrayList<String>();
		if(new File(sourcePath).isDirectory()) {
			FileLists.listFilesOnly(sourcePath, pathList);
		} else {
			pathList.add(sourcePath);
		}
		
		if(MODE == Mode.decrypt) {
			FileNameFilter.reserve(pathList, SUFFIXS);
		}
		
		// 3。实际处理~
		for(String path : pathList) {
			File child = new File(path);
			
			/** 是否打印出处理文件的路径~ */
			if(BYSingle.LOG_HANDLED_FILE_NAME_OR_NOT) {
				BY.log(child.getName());
			}
			
			try {
				boolean success = false;
				if(MODE == Mode.encrypt) {
					success = encryptFile(child);
				} else {
					success = decryptFile(child);
				}
				if(!success) {
					System.err.println("在加密(解密) " + child.getAbsolutePath() + " 的时候出现问题!");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		BY.byLog("已将 " + pathList.size() + " 个文件加密(解密)到目标文件!!");
		return true;
	}
	
	
	/**
	 * 对文件的数据字节数组进行 8 个字符密钥的 DES 加密~
	 * @param f
	 * @return
	 * @throws Exception
	 */
	public static boolean encryptFile(File f) throws Exception {
		byte[] bytesData = IOUtils.toByteArray(new FileInputStream(f));
		byte[] bytesDesc = getFileDescription(f).toByteArray();
		
		/** 将文件描述字节数组与文件数据字节数组拼接起来~ */
		byte[] bytesDataCombined = combineTwoByteArray(bytesDesc, bytesData);
		
		/** 是英文混数字的,不用管编码什么的(发现我还是有点儿不明白)~ */
		byte[] bytesKey = getBytesKey(PWD.getBytes());
		
		byte[] bytesDataEncrypted = doFinalWithDES(bytesDataCombined, bytesKey, Cipher.ENCRYPT_MODE);
		
		String prefix = f.getName().substring(0, f.getName().lastIndexOf('.'));
		File encryptedFile = new File(ENCRYPTED_DIR + File.separator + prefix + ".des");
		
		IOUtils.write(bytesDataEncrypted, new FileOutputStream(encryptedFile));
		return true;
	}
	
	/**
	 * 解密被加密过后的文件~
	 * @param f
	 * @return
	 * @throws Exception
	 */
	public static boolean decryptFile(File f) throws Exception {
		byte[] bytesData = IOUtils.toByteArray(new FileInputStream(f));
		byte[] bytesKey = getBytesKey(PWD.getBytes());
		
		byte[] bytesDataDecrypted = doFinalWithDES(bytesData, bytesKey, Cipher.DECRYPT_MODE);
		int len = bytesDataDecrypted.length;
		int descLen = Description.DESC_AREA_SIZE;
		
		Description desc = Description.descriptionFromBytes(bytesDataDecrypted);
		if(!desc.getPassword().equals(PWD)) {
			System.err.println("密码不正确,退出~");
			/**
			 * 实际上走到这里已经很玄乎了,能走到这里说明输入密码的前 8 位和正确密码是能够相匹配的,
			 * 否则是无法突破 DES 加密这一关的,但是我在 desc 对象里面仍然保留了最后一道防线。
			 * 虽然 DES 加密仅用到了密码的前 8 位,但是如果 8 位以后的字符和 desc 里面保留的密码对不上,
			 * 也不会让套密码的人得到正确解密后的数据文件~
			 */
			return false;
		}
		
		System.out.println(desc.toString());
		
		File decryptedFile = new File(DECRYPTED_DIR + File.separator
				+ desc.getFileName() + '.' + desc.getFileFormat());
		
		FileOutputStream fos = new FileOutputStream(decryptedFile);
		fos.write(bytesDataDecrypted, descLen, len - descLen);
		fos.flush();
		fos.close();
		return true;
	}
	
	// String.getBytes():
	// Encodes this String into a sequence of bytes using 
	// the platform's default charset, storing the result into a new byte array.
	
	/**
	 * 获取文件的描述对象
	 * (描述对象中包含了文件的一部分属性值:
	 * 文件名,文件后缀名,文件长度,加密文件所用密码,数字摘要以及对压缩文件的注释)~
	 * @param f
	 * @return
	 */
	public static Description getFileDescription(File f) {
		String fName = f.getName();
		String prefix = fName.substring(0, fName.lastIndexOf('.'));
		String suffix = fName.substring(fName.lastIndexOf('.') + 1);
		
		Description desc = new Description();
		desc.setFileName(prefix);
		desc.setFileFormat(suffix);
		desc.setFileLength(String.valueOf(f.length()));
		desc.setPassword(PWD);
		desc.setMd5Digest("1231231231231234");	// md5 摘要的长度为 128 bit, 16 byte~
		
		// 下面是一个长度超过512字节的字符串(86 * 11 = 946),测试一下长度超过规定长度自动被砍断的功能
		desc.setComment("My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!" +
				"My name is Yang3wei, i love java the most!!My name is Yang3wei, i love java the most!!");
		return desc;
	}
	
	
	/**
	 * 从指定字符串生成密钥,密钥所需的字节数组长度为8位 不足8位时后面补0,超出8位只取前8位
	 * @param arrBTmp 构成该字符串的字节数组
	 * @return 生成的密钥
	 * @throws Exception
	 */
	private static byte[] getBytesKey(byte[] bytesKeyRaw) throws Exception {
		// 创建一个空的8位字节数组(默认值为0)
		byte[] bytesKey = new byte[8];
		// 将原始字节数组转换为8位
		for (int i = 0; i < bytesKeyRaw.length && i < bytesKey.length; i++) {
			bytesKey[i] = bytesKeyRaw[i];
		}
		return bytesKey;
	}
	
	
	/**
	 * 用 DES方法加密(或解密)输入的字节。bytKey 须为 8 字节长,是加密(或解密)的密码~
	 */
	private static byte[] doFinalWithDES(byte[] bytesData, byte[] bytesKey, int desMode) throws Exception {
		SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
		DESKeySpec desKS = new DESKeySpec(bytesKey);
		SecretKey sk = skf.generateSecret(desKS);
		
		Cipher cip = Cipher.getInstance("DES");
		cip.init(desMode, sk);
		return cip.doFinal(bytesData);
	}
	
	/**
	 * 准备工作,建立几个主要目录(如果他们不存在)~
	 * @return
	 */
	private static boolean prepare() {
		// 1。检查 BASE_PATH 文件夹是否存在?如果不存在,创建为文件夹~
		File baseDir = new File("/Users/user/MyData/MyPersonal");
		if(!baseDir.exists()) {
			boolean success = baseDir.mkdir();
			if(!success) {
				System.out.println("EncryptZipper 创建 BASE_PATH 文件夹的时候失败~");
				return false;
			}
			File encryptedDir = new File(ENCRYPTED_DIR);
			if(!encryptedDir.exists()) {
				boolean success1 = encryptedDir.mkdir();
				if(!success1) {
					System.out.println("EncryptZipper 创建 CHILD_ENCRYPTED 文件夹的时候失败~");
					return false;
				}
			}
			File decryptedDir = new File(DECRYPTED_DIR);
			if(!decryptedDir.exists()) {
				boolean success2 = decryptedDir.mkdir();
				if(!success2) {
					System.out.println("EncryptZipper 创建 CHILD_DECRYPTED 文件夹的时候失败~");
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * 需要保留一些额外的数据,如文件的后缀格式,还有完整的密码
	 * 由于des加解密仅仅用到了8字节的密码,所以如果存在额外的密码,须进一步在额外数据里面得到验证,
	 * 验证通过的话在将加密文件还原为可用的数据文件
	 */
	public static byte[] combineTwoByteArray(byte[] bytesDesc, byte[] bytesData) {	// 拼接两个字节数组~
		int oldLen = bytesData.length;
		int newLen = Description.DESC_AREA_SIZE + oldLen;
		byte[] bytesDataCombined = new byte[newLen];
		for(int i = 0; i < Description.DESC_AREA_SIZE; ++ i) {
			bytesDataCombined[i] = bytesDesc[i];
		}
		for(int i = Description.DESC_AREA_SIZE; i < newLen; ++ i) {
			bytesDataCombined[i] = bytesData[i - Description.DESC_AREA_SIZE];
		}
		return bytesDataCombined;
	}
}

/**
 * @author BruceYang
 * 前 512 byte 用于各项属性,
 * 前 512 byte 又被分成 16 份,每份 32 byte~
 * 每项属性占用 32 个字节 大小的存储区域~
 * 如果属性的长度超过了本身所能容纳的长度,砍断。
 * 另外,每项属性以换行符 '\n' 做终止标识~
 * 
 * 后 512 byte 用于大段文本注释(支持中文注释,base64编码解码)
 * 加密文件属性描述类~
 */
class Description {
	/** constructor~ */
	public Description() {
		
	}
	
	@Override
	public String toString() {
		// 该方法主要用于调试~
		StringBuffer sb = new StringBuffer();
		sb.append("fileName=" + fileName).append('\n')
			.append("fileFormat=" + fileFormat).append('\n')
			.append("fileLength=" + fileLength).append('\n')
			.append("password=" + password).append('\n')
			.append("md5Digest=" + md5Digest).append('\n')
			.append("comment=" + comment).append('\n');
		return sb.toString();
	}
	
	/** 提供一个 1KB(1024Byte)的数据头,用以保存文件格式,完整密码等数据~ */
	protected static final int DESC_AREA_SIZE = 1024;
	protected static final int PROPERTY_LENGTH = 32;
	protected static final int COMMENT_LENGTH = 512;
	
	/** properties(暂且就弄这5个属性,描述必须为ascii)~ */
	private String fileName;	// offset = 0~31
	private String fileFormat;	// offset = 32~63
	private String fileLength;	// offset = 64~95
	private String password;	// offset = 96~127
	private String md5Digest;	// offset = 128~159
	/** 下面这个是注释~ */
	private String comment;		// offset = 512~1023
	
	protected byte[] toByteArray() {
		byte[] dest = new byte[DESC_AREA_SIZE];
		encodeProperty2ByteArray(fileName, dest, 0, PROPERTY_LENGTH);
		encodeProperty2ByteArray(fileFormat, dest, 32, PROPERTY_LENGTH);
		encodeProperty2ByteArray(fileLength, dest, 64, PROPERTY_LENGTH);
		encodeProperty2ByteArray(password, dest, 96, PROPERTY_LENGTH);
		encodeProperty2ByteArray(md5Digest, dest, 128, PROPERTY_LENGTH);
		encodeProperty2ByteArray(comment, dest, 512, COMMENT_LENGTH);
		return dest;
	}
	
	private void encodeProperty2ByteArray(String property, byte[] dest, int offset, int length) {
		byte[] bytesProp = property.getBytes();
		int len = bytesProp.length;
		if(len >= length) {
			for(int i = 0; i < length; ++ i) {
				dest[offset + i] = bytesProp[i];
			}
		} else {
			for(int i = 0; i < length; ++ i) {
				if(i < len) {
					dest[offset + i] = bytesProp[i];
				} else {
					dest[offset + i] = (byte)0x20;
				}
			}
		}
	}
	
	protected static Description descriptionFromBytes(byte[] totalBytes) {
		Description desc = new Description();
		
		desc.setFileName(new String(totalBytes, 0, PROPERTY_LENGTH).trim());
		desc.setFileFormat(new String(totalBytes, 32, PROPERTY_LENGTH).trim());
		desc.setFileLength(new String(totalBytes, 64, PROPERTY_LENGTH).trim());
		desc.setPassword(new String(totalBytes, 96, PROPERTY_LENGTH).trim());
		desc.setMd5Digest(new String(totalBytes, 128, PROPERTY_LENGTH).trim());
		desc.setComment(new String(totalBytes, 512, COMMENT_LENGTH).trim());
		return desc;
	}
	
	/** getters && setters~ */
	public String getFileName() {
		return fileName;
	}
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	public String getFileFormat() {
		return fileFormat;
	}
	public void setFileFormat(String fileFormat) {
		this.fileFormat = fileFormat;
	}
	public String getFileLength() {
		return fileLength;
	}
	public void setFileLength(String fileLength) {
		this.fileLength = fileLength;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getMd5Digest() {
		return md5Digest;
	}
	public void setMd5Digest(String md5Digest) {
		this.md5Digest = md5Digest;
	}
	public String getComment() {
		return comment;
	}
	public void setComment(String comment) {
		this.comment = comment;
	}
}


posted on 2012-03-24 01:05  yang3wei  阅读(258)  评论(0编辑  收藏  举报