ECB模式RSA的Java简单实现中遇到的问题

连续使用一个字节数组读取文件

  在使用java的数组来实现连续读取数据的时候,遇到这样一个问题。

  我通过一个8字节数组来通过FileOutputStream.read()读取文件中的数据,所以我每次最多读8个字节,我文件中的数据如下,总共47个字符也就是47个字节,所以需要读6次;

dshakjdhjksahklfdahkjhkjf hdljksahkfjlkjhadskjh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void read(String path) {
try {
File file = new File(file);
FileInputStream fis = new FileInputStream(file);
byte [] word = new byte[8];
int count = 0;
int num = 0;
while ((count = fis.read(word)) != -1) {
for (byte s : word) {
num ++;
System.out.print(num +": " + (char)s + " ");
}
System.out.println();
}
fis.close();
}catch (IOException e) {
e.printStackTrace();
}
}

  当我每读一次就把当前读到数组里的数据输出时,我发现数据与我文件中的不匹配。读取的结果如下:

1
2
3
4
5
6
1: d 2: s 3: h 4: a 5: k 6: j 7: d 8: h
9: j 10: k 11: s 12: a 13: h 14: k 15: l 16: f
17: d 18: a 19: h 20: k 21: j 22: h 23: k 24: j
25: f 26: 27: h 28: d 29: l 30: j 31: k 32: s
33: a 34: h 35: k 36: f 37: j 38: l 39: k 40: j
41: h 42: a 43: d 44: s 45: k 46: j 47: h 48: j

  我们可以看到输出了48个字节多输出了一个字节,思考了一下我们可以想到,当我们最后一次读取文件数据时,如果这时候我们可以读取的数据已经不足8个字节,那么会怎么样呢,那么我们可以想到这最后一个字节可能来自上次8个字节的最后一个字节即第40个字节,每次使用FileInputStream.read()去读取数据,当前读取的数据都会把之前读取的数据给覆盖掉。所以我们每次读取之后,都要先把byte数组清空之后再进行下次读取;可以使用java.util.Arrays.fill()来实现;

一个字节的表示

  首先对于一个字节来说,表示有符号数时,范围是-128~127,其中-128的表示为10000000;正数为原码形式存在,负数为补码形式存在;比如对于一个字节来说,如果向计算机写入-1,它是一个有符号的负数,所以计算机会计算它的补码,从而转换为11111111;而对于128~255之间的数,计算机会自动视其为无符号数,那就以无符号原码表示,如0xff即255,就是11111111

  所以有时候,我们就会遇到,255和-1都表示为11111111的情况;

BigInteger.toByteArray()怎么实现的

  在网上找了一个说的比较好的解答,也有了自己的理解。

  下面是一个int的编码实例,其中需要注意二进制的4位表示一个十六进制数字,负数二进制开始为1,正数开始于0;

1
2
3
4
5
6
7
8
-2147483648 is encoded as 1000 0000 0000 0000 0000 0000 0000 0000 or 0x80000000.
-16 is encoded as 1111 1111 1111 1111 1111 1111 1111 0000 or 0xfffffff0.
-2 is encoded as 1111 1111 1111 1111 1111 1111 1111 1110 or 0xfffffffe.
-1 is encoded as 1111 1111 1111 1111 1111 1111 1111 1111 or 0xffffffff.
0 is encoded as 0000 0000 0000 0000 0000 0000 0000 0000 or 0x00000000.
1 is encoded as 0000 0000 0000 0000 0000 0000 0000 0001 or 0x00000001.
16 is encoded as 0000 0000 0000 0000 0000 0000 0001 0000 or 0x00000010.
2147483647 is encoded as 0111 1111 1111 1111 1111 1111 1111 1111 or 0x7fffffff.

  java只是把,每个8位块中现有的位拷贝到目标数组中的问题。一个字节即是一个8位的块.

  我们将一个BigInteger转换为二进制补码,我们想要的只是它的后面的有效的位,而不包含前面的无数的前导0和前导1;之后每8位的块即是一个字节;

  下面用数组里的十六进制来表示BigInteger;

1
2
3
4
5
6
7
8
9
                   0 [0x00000000]
2147483647 [0x7fffffff]
2147483648 [0x00000000, 0x80000000]
4294967296 [0x00000001, 0x00000000]
8589934592 [0x00000002, 0x00000000]
281474976710655 [0x0000ffff, 0xffffffff]
281474976710656 [0x00010000, 0x00000000]
18446744073709551616 [0x00000001, 0x00000000, 0x00000000]
-281474976710656 [0xffff0000, 0x00000000]

  之后分割、拿出其中有效的byte,即以每八位分割一次;

1
2
8589934592 [0x00 00 00 02, 0x00 00 00 00]
bytes: [ 02, 00,00,00,00]
  1. 前面的三个00都可以被忽略,因为第一个有效数字之前有25个0;
  2. 然后将这些二进制位拷贝到字节中即可。

对于官方的文档,其中有这样一句话,toByteArray()这个方法返回一个数组,这个数组包含可以代表这个BigInteger最小数量的字节,其中包含至少一个符号位。

符号位是什么意思呢。对于-128~127之间的数组,我们都可以用一个字节来表示,而对于128~255之间的数呢,比如255与-1,虽然二进制表示相同,但他们终究是不同的,所以这个方法考虑到这个问题,对于255就额外加了一个0x00表示符号位(如果第一个字节的大小大于0x7f,就需要在最前面加上一个0x00);或者可以这样想,对于有符号数来说,128~255之间的数,一个字节是表示不了的,所以采用两个字节来表示,只不过最左边的那个字节是0x00;比如对于下面的两个byte数组:

1
2
255 [0x00, 0xff]
-1 [ , 0xff]

这个问题需要特别注意

byte数组转BigInteger

  BigInteger有这样一个构造函数:public BigInteger(byte[] val),它的参数是byte数组;对于[0x00,0xff]得到的BigInteger的到的结果是255,对于[0xff]的结果是-1;

  BigInteger还有这样一个构造函数:public BigInteger(int signum,byte[] magnitude),其中多加了一个signum参数代表你的BigInteger的正负,为-1代表负数,为1代表整数,为0代表byte数组表示的数一定是0(即这个byte数组仅包括字节0x00),如果byte数组表示的数不为0,会报错。

  实践后发现,可能是这样的,如果signum为0,则在byte数组最前面加上一个0x00,然后反转上面toByteArray()的操作,得到最后的结果,如果signum为1,前面计算的结果上加上一个负号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
byte [] arr1 = {0x00};
byte [] arr2 = {0x00,(byte)0xff};
byte [] arr3 = {(byte)0xff};
byte [] arr4 = {-1,(byte)0xff};
System.out.println(new BigInteger(arr4)); //-32513
System.out.println(new BigInteger(1,arr4)); //33023
System.out.println(new BigInteger(-1,arr4));//-33023
System.out.println(new BigInteger(-1,arr1));//0
System.out.println(new BigInteger(1,arr2)); //255
System.out.println(new BigInteger(-1,arr2));//-255
System.out.println(new BigInteger(1,arr3)); //255
System.out.println(new BigInteger(-1,arr3));//-255
}
}