java-通过异或(xor)实现快速加解密的方法
一、背景
首次接触异或加解密最早是在宽带不普及的时候,当时用的是某个在线音乐软件看MV,上网不易想着把MV下载下来,日后离线看。
可是在缓存目录把视频复制到其它目录后,发现不使用他们的播放器还无法播放。
查了下当时有对各家视频的转码程序,找个个试了下,直接转码成功。
当时也比较好奇,所以查了下相关资料。发现其实逻辑挺简单就是使用了一个固定值(暂定37吧),对流文件的每个字节执行“异或”运算。
对明文第一次与37异或后的值就是密文了,对密文再次与37异或,就能把它变回明文。
当时也没有深究原因,能用就完了呗!最近遇到个功能需要实现对大文件的快速加解密,脑海里突然蹦出来了这个算法。
二、异或定义
- 运算规则
- 如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
- 更容易记忆的规则(半加运算)
- 异或也叫半加运算,其运算法则相当于不带进位的二进制加法:
- 二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位。
- 所以异或常被认作不进位加法。
异或不常用,几天不不碎碎念几遍规则,有个2周就把它忘干净了 _!!!。
三、异或运算法则
- 归零律:a^a=0
- 恒等律:a^0=a
- 交换律: a^b = b^a
- 结合律:abc = a(bc) = (ab)c
- 自反律:aba = b
java中异或运算符为 ^
四、异或加密原理
- 异或自反律:aba = b
看完异或运算法则之后就能明白,加解密用的是异或的自反律,这是一个神奇的性质。 - 例如:不引入第三变量,交换两个数值?
public static void main(String[] args) {
int a = 88;
int b = 99;
System.out.println(MessageFormat.format("交换前\ta={0}\tb={1}",a,b));
a = a^b;
b = a^b;
a = a^b;
System.out.println(MessageFormat.format("交换后\ta={0}\tb={1}",a,b));
}
输出:
交换前 a=88 b=99
交换后 a=99 b=88
- 内容加密
在异或自反律(aba = b)中,如果把a看做加密的盐值,b看做明文,使用盐值a对明文b连续执行两次“异或”操作即可得到原来的明文b内容。通过该定律我们就可以实现对二进制文件的简单加解密。
系统中存在的物理文件,都是由很多个byte组成的,我们只需按位对物理文件进行读写加密就可以实现加解密。
盐值的长短决定了密文的破解难易。
比如可以按1位去异或,也可以按4位去异或,等等!
五、加密算法实现
- 读取使用InputStream类
- 写入使用RandomAccessFile类
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输出流不同的是,RandomAccessFile支持"随机访问"的方式,程序可以直接跳转到文件的任意地方来读写数据。
刚开始使用的是OutputStream类写内容,发现调用write方法后,文件内容变小了_!!!
- 代码实现
public static void main(String[] args) throws Exception {
//你好世界!hello,world!
String fileName = "E:\\downloads\\text.txt";
byte salt = 10;
int readLength = 128; //读取长度
byte[] cipherBytes = new byte[readLength];
//读流
try(InputStream fr = new FileInputStream(fileName)){
byte[] buffer = new byte[readLength];
fr.read(buffer,0,buffer.length);
//按位执行异或
for(int i=0;i<buffer.length;i++){
byte b = buffer[i];
b ^= salt;
//byte a = b ^ salt; 不能用这种方法,因为异或后的值是int类型
cipherBytes[i]=b;
}
}
File file = new File(fileName);
System.out.println("执行前:file.len="+file.length());
//写流
try(RandomAccessFile src = new RandomAccessFile(file,"rws")) {
src.write(cipherBytes,0,cipherBytes.length);
}
file = new File(fileName);
System.out.println("执行后:file.len="+file.length());
}
- text.txt内容
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
你好世界!hello,world!
总结
首先说RandomAccessFile是个很强大的类,读写文件不丢内容。不知道为啥OutputStream设计成要么覆盖要么追加的模式!
再说文件加密,一般文件都为二进制内容(除了些文本类的文件)如exe、xls、dll等,不需要对所有内容进行加密,只需要对部分执行加密即可,改变了部分内容也就相当于破坏了原有结构,也算是达到了加密效果。
而且修改内容越少,文件被损毁的风险也就越小。比如有个1G的文件,写一半断电了,这个文件几乎就废了解密不了了。
如果只修改前128位内容,修改一次性执行完成,风险就稍微小一些。