未验证 提交 aa4f182f 编写于 作者: D Dan Hipschman 提交者: GitHub

Merge branch 'master' into microservices-with-grpc

# Bitwise Operators in Python
Source code of the least-significant bit **steganography** example from the [Bitwise Operators in Python](https://realpython.com/python-bitwise-operators/) article.
## Installation
There are no external dependencies except for a Python interpreter.
## Running
Change directory to the current folder, where this `README.md` file is located, and then execute Python module:
```shell
$ python -m stegano /path/to/bitmap (--encode /path/to/file | --decode | --erase)
```
For example, to extract a secret file from the attached bitmap, type the following:
```shell
$ python -m stegano example.bmp -d
Extracted a secret file: podcast.mp4
```
"""
Runnable module, can be executed as:
$ python -m stegano
"""
from .bitmap import Bitmap
from .cli import CommandLineArguments, parse_args
from .decoder import decode, DecodingError
from .encoder import encode, EncodingError
from .eraser import erase
def main(args: CommandLineArguments) -> None:
"""Entry point to the script."""
with Bitmap(args.bitmap) as bitmap:
if args.encode:
encode(bitmap, args.encode)
elif args.decode:
decode(bitmap)
elif args.erase:
erase(bitmap)
if __name__ == "__main__":
try:
main(parse_args())
except (EncodingError, DecodingError) as ex:
print(ex)
"""
Bitmap read/write operations.
"""
import pathlib
from dataclasses import dataclass
from itertools import islice
from mmap import mmap, ACCESS_WRITE
from struct import pack, unpack
from typing import Any, Union, Iterator
class Bitmap:
"""High-level interface to a bitmap file."""
def __init__(self, path: pathlib.Path) -> None:
self._file = path.open(mode="r+b")
self._file_bytes = mmap(self._file.fileno(), 0, access=ACCESS_WRITE)
self._header = Header.from_bytes(self._file_bytes[:50])
def __enter__(self) -> "Bitmap":
return self
def __exit__(self, *args, **kwargs) -> None:
self._file_bytes.close()
self._file.close()
def __getattr__(self, name: str) -> Any:
return getattr(self._header, name)
def __getitem__(self, offset: Union[int, slice]) -> Union[int, bytes]:
return self._file_bytes[offset]
def __setitem__(
self, offset: Union[int, slice], value: Union[int, bytes]
) -> None:
self._file_bytes[offset] = value
@property
def max_bytes(self) -> int:
"""The maximum number of bytes the bitmap can hide."""
return self.width * self.height * 3
@property
def byte_offsets(self) -> Iterator[int]:
"""Return an iterator over byte offsets (skip the padding)."""
start_index = self.pixels_offset
end_index = self.pixels_offset + self.pixel_size_bytes
scanline_bytes = self.pixel_size_bytes // self.height
for scanline in range(start_index, end_index, scanline_bytes):
yield from range(scanline, scanline + self.width * 3)
@property
def byte_slices(self) -> Iterator[slice]:
"""Generator iterator of 8-byte long slices."""
for byte_index in islice(self.byte_offsets, 0, self.max_bytes, 8):
yield slice(byte_index, byte_index + 8)
@property
def reserved_field(self) -> int:
"""Return a little-endian 32-bit unsigned integer."""
return unsigned_int(self._file_bytes, 0x06)
@reserved_field.setter
def reserved_field(self, value: int) -> None:
"""Store a little-endian 32-bit unsigned integer."""
self._file_bytes.seek(0x06)
self._file_bytes.write(pack("<I", value))
@dataclass
class Header:
"""Bitmap metadata from the file header."""
signature: bytes
file_size_bytes: int
pixel_size_bytes: int
pixels_offset: int
width: int
height: int
bit_depth: int
compressed: bool
has_palette: bool
def __post_init__(self):
assert self.signature == b"BM", "Unknown file signature"
assert not self.compressed, "Compression unsupported"
assert not self.has_palette, "Color palette unsupported"
assert self.bit_depth == 24, "Only 24-bit depth supported"
@staticmethod
def from_bytes(data: bytes) -> "Header":
"""Factory method to deserialize the header from bytes."""
return Header(
signature=data[0x00:2],
file_size_bytes=unsigned_int(data, 0x02),
pixels_offset=unsigned_int(data, 0x0A),
width=unsigned_int(data, 0x12),
height=unsigned_int(data, 0x16),
bit_depth=unsigned_short(data, 0x1C),
compressed=unsigned_int(data, 0x1E) != 0,
has_palette=unsigned_int(data, 0x2E) != 0,
pixel_size_bytes=unsigned_int(data, 0x22),
)
def unsigned_int(data: Union[bytes, mmap], offset: int) -> int:
"""Read a little-endian 32-bit unsigned integer."""
return unpack("<I", data[offset : offset + 4])[0]
def unsigned_short(data: Union[bytes, mmap], offset: int) -> int:
"""Read a little-endian 16-bit unsigned integer."""
return unpack("<H", data[offset : offset + 2])[0]
"""
Command line arguments parsing.
"""
import argparse
import pathlib
from dataclasses import dataclass
from typing import Optional
@dataclass
class CommandLineArguments:
"""Parsed command line arguments."""
bitmap: pathlib.Path
encode: Optional[pathlib.Path]
decode: bool
erase: bool
def parse_args() -> CommandLineArguments:
"""Parse command line arguments."""
parser = argparse.ArgumentParser()
parser.add_argument("bitmap", type=path_factory)
modes = parser.add_mutually_exclusive_group()
modes.add_argument("--encode", "-e", metavar="file", type=path_factory)
modes.add_argument("--decode", "-d", action="store_true")
modes.add_argument("--erase", "-x", action="store_true")
args = parser.parse_args()
if not any([args.encode, args.decode, args.erase]):
parser.error("Mode required: --encode file | --decode | --erase")
return CommandLineArguments(**vars(args))
def path_factory(argument: str) -> pathlib.Path:
"""Convert the argument to a path instance."""
path = pathlib.Path(argument)
if not path.exists():
raise argparse.ArgumentTypeError("file doesn't exist")
if not path.is_file():
raise argparse.ArgumentTypeError("must be a file")
return path
"""
Secret file decoder.
"""
from itertools import islice, takewhile
from pathlib import Path
from typing import Iterator
from .bitmap import Bitmap
class DecodingError(Exception):
pass
def decode(bitmap: Bitmap) -> None:
"""Extract a secret file from the bitmap."""
if bitmap.reserved_field <= 0:
raise DecodingError("Secret file not found in the bitmap")
iterator = secret_bytes(bitmap)
filename = "".join(map(chr, takewhile(lambda x: x != 0, iterator)))
with Path(filename).open(mode="wb") as file:
file.write(bytes(islice(iterator, bitmap.reserved_field)))
print(f"Extracted a secret file: {filename}")
def secret_bytes(bitmap) -> Iterator[int]:
"""Return an iterator over secret bytes."""
for eight_bytes in bitmap.byte_slices:
yield sum(
[
(byte & 1) << (7 - i)
for i, byte in enumerate(bitmap[eight_bytes])
]
)
"""
Secret file encoder.
"""
import pathlib
from typing import Iterator
from .bitmap import Bitmap
class EncodingError(Exception):
pass
class SecretFile:
"""Convenience class for serializing secret data."""
def __init__(self, path: pathlib.Path):
self.path = path
self.filename = path.name.encode("utf-8") + b"\x00"
self.size_bytes = path.stat().st_size
@property
def num_secret_bytes(self) -> int:
"""Total number of bytes including the null-terminated string."""
return len(self.filename) + self.size_bytes
@property
def secret_bytes(self) -> Iterator[int]:
"""Null-terminated name followed by the file content."""
yield from self.filename
with self.path.open(mode="rb") as file:
yield from file.read()
def encode(bitmap: Bitmap, path: pathlib.Path) -> None:
"""Embed a secret file in the bitmap."""
file = SecretFile(path)
if file.num_secret_bytes > bitmap.max_bytes:
raise EncodingError("Not enough pixels to embed a secret file")
bitmap.reserved_field = file.size_bytes
for secret_byte, eight_bytes in zip(file.secret_bytes, bitmap.byte_slices):
secret_bits = [(secret_byte >> i) & 1 for i in reversed(range(8))]
bitmap[eight_bytes] = bytes(
[
byte | 1 if bit else byte & ~1
for byte, bit in zip(bitmap[eight_bytes], secret_bits)
]
)
print("Secret file was embedded in the bitmap")
"""
Secret file eraser.
"""
from itertools import islice
from random import random
from .bitmap import Bitmap
def erase(bitmap: Bitmap) -> None:
"""Scramble a previously hidden data."""
if bitmap.reserved_field > 0:
for byte_offset in islice(bitmap.byte_offsets, bitmap.reserved_field):
bitmap[byte_offset] = randomize_lsb(bitmap[byte_offset])
bitmap.reserved_field = 0
print("Erased a secret file from the bitmap")
else:
print("Secret file not found in the bitmap")
def randomize_lsb(value: int) -> int:
"""Set a random bit on the least-significant position."""
return value & ~1 if random() < 0.5 else value | 1
runtime: python38
\ No newline at end of file
from flask import Flask
from flask import request
app = Flask(__name__)
def fahrenheit_from(celsius):
"""Convert Celsius to Fahrenheit degrees."""
try:
fahrenheit = float(celsius) * 9 / 5 + 32
fahrenheit = round(fahrenheit, 3) # Round to three decimal places
return str(fahrenheit)
except ValueError:
return "invalid input"
@app.route("/")
def index():
celsius = request.args.get("celsius", "")
if celsius:
fahrenheit = fahrenheit_from(celsius)
else:
fahrenheit = ""
return (
"""<form action="" method="get">
Celsius temperature: <input type="text" name="celsius">
<input type="submit" value="Convert to Fahrenheit">
</form>"""
+ "Fahrenheit: "
+ fahrenheit
)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080, debug=True)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册