RandomLSB 输入流,使用与嵌入端相同的伪随机序列还原散布位。
| 10 | |
| 11 | |
| 12 | class RandomLSBInputStream: |
| 13 | """RandomLSB 输入流,使用与嵌入端相同的伪随机序列还原散布位。""" |
| 14 | |
| 15 | def __init__(self, image: Image.Image, config: LSBConfig, password: str = None): |
| 16 | self.config = config |
| 17 | self.pixels = np.array(image) |
| 18 | self.height, self.width, self.channels = self.pixels.shape |
| 19 | |
| 20 | seed = CommonUtil.password_hash(password) if password else 0 |
| 21 | total_positions = self.width * self.height * self.channels |
| 22 | rng = np.random.default_rng(seed % (2 ** 63)) |
| 23 | self.position_sequence = rng.permutation(total_positions) |
| 24 | |
| 25 | self._bit_offset = 0 |
| 26 | |
| 27 | # 先读固定部分以获取 filename_len,再读变长文件名 |
| 28 | fixed_size = (len(LSBDataHeader.DATA_STAMP) + len(LSBDataHeader.HEADER_VERSION) |
| 29 | + LSBDataHeader.FIXED_HEADER_LENGTH + LSBDataHeader.CRYPT_ALGO_LENGTH) |
| 30 | fixed_bytes = self._read_bytes(fixed_size) |
| 31 | |
| 32 | fixed_header_offset = len(LSBDataHeader.DATA_STAMP) + len(LSBDataHeader.HEADER_VERSION) |
| 33 | filename_len = fixed_bytes[fixed_header_offset + 5] |
| 34 | |
| 35 | filename_bytes = self._read_bytes(filename_len) if filename_len > 0 else b'' |
| 36 | self.header = LSBDataHeader.from_bytes(fixed_bytes + filename_bytes, config) |
| 37 | |
| 38 | def _read_bytes(self, n_bytes: int) -> bytes: |
| 39 | bits_per_ch = self.config.get_max_bits_used_per_channel() |
| 40 | n_bits = n_bytes * 8 |
| 41 | |
| 42 | if bits_per_ch == 1: |
| 43 | positions = self.position_sequence[self._bit_offset:self._bit_offset + n_bits].astype(np.int64) |
| 44 | pixel_indices = positions // self.channels |
| 45 | channel_indices = positions % self.channels |
| 46 | rows = pixel_indices // self.width |
| 47 | cols = pixel_indices % self.width |
| 48 | bits = (self.pixels[rows, cols, channel_indices] & 1).astype(np.uint8) |
| 49 | self._bit_offset += n_bits |
| 50 | return np.packbits(bits).tobytes() |
| 51 | else: |
| 52 | result = bytearray() |
| 53 | for _ in range(n_bytes): |
| 54 | byte_val = 0 |
| 55 | for bit_pos in range(8): |
| 56 | bit = self._read_one_bit() |
| 57 | byte_val = (byte_val << 1) | bit |
| 58 | result.append(byte_val) |
| 59 | return bytes(result) |
| 60 | |
| 61 | def _read_one_bit(self) -> int: |
| 62 | bits_per_ch = self.config.get_max_bits_used_per_channel() |
| 63 | ch_pos = self._bit_offset // bits_per_ch |
| 64 | bit_slot = self._bit_offset % bits_per_ch |
| 65 | pos = int(self.position_sequence[ch_pos]) |
| 66 | pixel_idx = pos // self.channels |
| 67 | channel = pos % self.channels |
| 68 | row = pixel_idx // self.width |
| 69 | col = pixel_idx % self.width |
no outgoing calls
no test coverage detected