背景:领导用python写了个AES加密,加密后的结果写在文件中。让我写个AES解密,可以解密他的文件。
作为一个代码的搬运工,这种问题难不倒我,百度一下我就知道(不要鄙视我不用google)。
看到结果我傻眼了:搜索到的结果基本都是对着写python加解密和java加解密,这些内容虽然也让我获益匪浅,但距离实现我的目标还有一定的差距。
最终费了九牛二虎之力,发挥想象和感觉,终于在上百次的重启中找到可行的代码。
如果观看文章的你,也和我有类似的情况,希望我的文章可以给你些思路。由于加解密的设置不同结果也是完全不同的,所以简单的ctrl c,ctrl v可能没有用。
AES是什么我就不多说了,我也不懂,不能瞎说。但要进行AES加解密是要设置一些前提的,我碰到的是
- key和iv都是确定的
- 加解密的方式为CFB(网上最多的是CBC)
- 字符串编码方式为gb2312(大家用的最多是utf-8)
本次主要记录的问题:
- python和java的数据类型不一样的,所以python加密的内容,如果只看内容,java读取的文件是乱码的。但计算机的底层都是一致的,所以用二进制数组解析文件才是对的。
- CFB方式加密需要设置分段的长度(segment_size),python默认是8,而jvm默认是128,所以不能用默认,由于我是后写的,所以我就按python的改成8
- 将文件上传类型MultipartFile类型转换成File类型
如果这些你都知道,那你基本不用看了,没啥新的了,对我最有用的文章有以下几篇,感谢这些文章的作者
https://segmentfault.com/q/1010000017486879
https://stackoverflow.com/questions/40004858/encrypt-in-python-and-decrypt-in-java-with-aes-cfb
https://blog.csdn.net/weixin_39800144/article/details/80225990
https://blog.csdn.net/qq_39699665/article/details/83081431
好了进入正题,其实python加解密和java加解密,网上都是现成的。网上的我们领导给了我python解密的代码,让我对着写java代码,代码如下
secret_key = "1234567890987654"
iv_param = '1234567890123456'
with open('D:/test.txt','rb') as file_object:
contents = file_object.read()
print(contents)
aes2 = AES.new(secret_key.encode("gb2312"), AES.MODE_CFB, iv_param.encode("gb2312"))
plain_data2 = aes2.decrypt(contents)
print(plain_data2.decode('gb2312'))
代码中contents的类型是bytes,打印的结果类似于
b’\xa7!\xbfc\x9d\xe4\xd9\xef\x11po1\xb6\x87\xee\x1f\xfd.\xa7\xa3\xa1m$\x1e}<5=Y\xd5b\xf7\x94+Q\x93\xb2\x(我只截取了一半)
妈呀看不懂,但是没关系,只要java也能将这个文件读成这样不就行了吗,于是我找了各种流都不成功,只能读出一堆乱码的文本,这里的错误在于我以为是文本的内容编码后变成了样,所以一直在用各种流去读取文件内容,但事实上是文本本身在计算机以这种类型存储(就这个意思,可能表述不准确)
解决方案,就是用java将文件读取成二进制的byte数组,具体代码
String file = "D:\\test.txt";
File myFile = new File(file);
byte[] mybetys = BinUtil.getFileToByte(myFile);
这里涉及一个bin的工具类,就是我上文给出的第三个链接,我引用的方法的具体实现如下
public static byte[] getFileToByte(File file) {
byte[] by = new byte[(int) file.length()];
try {
InputStream is = new FileInputStream(file);
ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
byte[] bb = new byte[2048];
int ch;
ch = is.read(bb);
while (ch != -1) {
bytestream.write(bb, 0, ch);
ch = is.read(bb);
}
by = bytestream.toByteArray();
} catch (Exception ex) {
throw new RuntimeException("transform file into bin Array 出错",ex);
}
return by;
}
强调一下,不是读取文件内容,而是文件本身,所以不是用什么字符流字节流,而是FileInputStream(我流学的不太好,理解比较片面,领会精神)
文件以正确的方式读取了,解密应该小菜一碟了,网上到处都是解密的代码,但还是上文说的问题
The Cipher Feedback (CFB) mode of operation is a family of modes. It is parametrized by the segment size (or register size). PyCrypto has a default segment size of 8 bit and Java (actually OpenJDK) has a default segment size the same as the block size (128 bit for AES).
If you want CFB-128 in pycrypto, you can use AES.new(key, AES.MODE_CFB, iv, segment_size=128). If you want CFB-8 in Java, you can use Cipher.getInstance(“AES/CFB8/NoPadding”);
Python 的 PyCrypto 模块默认使用的 segment_size 是 8, Java 则默认采用segment_size为128.
所以要使 Python 获的和 Java 一样的加密结果, 必须让 Python 和 Java 使用相同的segment_size. 假设我们让 Python 适应 Java, 使用 segment_size=128
这两段话的意思差不多,其实英文写的更明白一下,就是python默认segment_size为8,JAVA(实际上是OpenJDK)的默认segment_size是128(这个分段的尺寸必须是8的倍数)
如果你想改python,你就这样设置
AES.new(key, AES.MODE_CFB, iv, segment_size=128)
如果你想改java,你就这样设置,这个设置我找了很久,基本上没有提到的
Cipher.getInstance("AES/CFB8/NoPadding")
那后续的就很简单了,我把代码贴出来,供大家参考
首先是main函数
public static void main(String[] args)throws Exception{
String file = "D:\\test.txt";
File myFile = new File(file);
byte[] mybetys = BinUtil.getFileToByte(myFile);
String secret_key = "1234567890987654"
String iv_param = '1234567890123456'
String res = aesDecrypt(secret_key,iv_param,mybetys);
System.out.println(res);
}
private static String aesDecrypt(String key, String iv, byte[] content) {
try {
byte[] keyb = key.getBytes("gb2312");
byte[] ivb = iv.getBytes("gb2312");
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(keyb, "AES");
IvParameterSpec ivspec = new IvParameterSpec(ivb);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(content);
return new String(original,"gb2312").trim();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
这样读取文件并将其解密就做好了
后来领导说,把这些功能加到文件上传就ok了,文件上传的参数类型大家都知道,是MultipartFile,我写的功能是参数是File,那只要将MultipartFile转换成File就ok了,这很简单,但我在网上找的方法,基本都不能用,很郁闷,只有上文链接中第四个链接可以用,这里贴出来,大家看一下就好
以上就是从周五下午搞到周一下午的结果(周末休了两天,想不到程序猿还能双休吧,哈哈哈)
如果你看到了这里,那就祝你工作顺利,天天进步~~~~