2026年8月31日 星期一

Python一下:照片與語音不是文字——認識二進位檔與雜湊驗證

《Python一下:從風土資料到智慧生活》第 19 篇

照片與語音不是文字——認識二進位檔與雜湊驗證

安全保存水井三寶影像、長者口述錄音及設備原始檔,並確認備份前後一個位元組都沒有改變。

CH10 二進位檔bytesSHA-256
學習目標
完成本篇後,你能分辨文字與二進位資料,使用 rbwbxb 讀寫位元組,以分塊方式處理大型媒體檔,使用Base64進行文字化傳輸,並用SHA-256驗證檔案完整性。

一、為什麼照片不能用UTF-8讀?

文字檔需要編碼把位元組解讀成字元;JPEG、PNG、MP3、WAV等檔案則有自己的二進位格式。若以 encoding="utf-8" 開啟照片,Python會嘗試把影像位元組解碼為文字,通常產生 UnicodeDecodeError

資料Python型別常用模式
逐字稿、CSV、JSONstrrw,指定編碼
照片、錄音、PDF、韌體bytesrbwb,不指定編碼
資料權利提醒:照片與錄音可能包含肖像、聲音、住家環境、位置及個人故事。能讀取或複製檔案不等於取得公開、訓練AI或再利用權利。應保存同意範圍、撤回方式及公開版本,原始媒體需限制存取。

二、認識bytes

text = "水井三寶"
data = text.encode("utf-8")

print(type(text), len(text))
print(type(data), len(data))
print(data)
print(data.decode("utf-8"))

bytes 是0到255整數組成的不可變序列。文字可經編碼變成位元組,但照片位元組本身不應任意 decode() 成文字。

三、讀取二進位檔的前幾個位元組

from pathlib import Path


def read_header(path, size=16):
    path = Path(path)
    with path.open("rb") as file:
        return file.read(size)


# 實際使用時換成資料副本
# header = read_header("照片/水井三寶.jpg")
# print(header)
# print(header.hex())

許多檔案格式有特徵位元組,但只檢查檔頭不能證明整份檔案安全或內容正確。副檔名與檔頭都只能作為初步判斷。

四、建立教學用二進位檔

from pathlib import Path

path = Path("教學資料.bin")
sample = bytes([0, 1, 2, 127, 128, 254, 255])

with path.open("xb") as file:
    file.write(sample)

with path.open("rb") as file:
    restored = file.read()

print(restored)
print(list(restored))

xb 表示排他建立二進位檔;目標已存在時會拒絕覆蓋,適合保護重要資料。

五、分塊讀取大型照片或錄音

使用 read() 一次讀完整段錄音可能占用大量記憶體。分塊處理能固定每次使用量。

from pathlib import Path


def count_bytes(path, chunk_size=64 * 1024):
    total = 0
    with Path(path).open("rb") as file:
        while chunk := file.read(chunk_size):
            total += len(chunk)
    return total


print(count_bytes("教學資料.bin"))

六、安全複製:目標存在就停止

from pathlib import Path


def copy_binary(source, target, chunk_size=1024 * 1024):
    source = Path(source)
    target = Path(target)
    if not source.is_file():
        raise FileNotFoundError(f"來源不存在:{source}")

    target.parent.mkdir(parents=True, exist_ok=True)
    with source.open("rb") as input_file:
        with target.open("xb") as output_file:
            while chunk := input_file.read(chunk_size):
                output_file.write(chunk)
    return target


# copy_binary("原始媒體/訪談.wav", "備份/訪談.wav")
避免半成品:若複製途中失敗,目標可能只寫入一部分。正式流程可先寫入暫存檔,驗證雜湊後再更名;失敗時保留錯誤紀錄並人工處理。

七、用SHA-256建立檔案指紋

import hashlib
from pathlib import Path


def sha256_file(path, chunk_size=1024 * 1024):
    digest = hashlib.sha256()
    with Path(path).open("rb") as file:
        while chunk := file.read(chunk_size):
            digest.update(chunk)
    return digest.hexdigest()


print(sha256_file("教學資料.bin"))

相同位元組會產生相同摘要;只要內容改變,摘要通常就不同。雜湊適合驗證完整性,但不能從摘要還原檔案。

八、複製後驗證是否一致

def verify_same_file(source, target):
    source_hash = sha256_file(source)
    target_hash = sha256_file(target)
    return {
        "來源SHA256": source_hash,
        "目標SHA256": target_hash,
        "內容一致": source_hash == target_hash,
    }


# result = verify_same_file("原始媒體/訪談.wav", "備份/訪談.wav")
# print(result)
雜湊不是備份:摘要只能幫助發現差異,不能救回遺失檔案。真正備份至少要有獨立副本、權限管理、定期驗證與還原演練。

九、Base64:把位元組轉成文字

import base64

original = b"USR binary demo"
encoded = base64.b64encode(original)
decoded = base64.b64decode(encoded, validate=True)

print(encoded.decode("ascii"))
print(decoded)
print(original == decoded)

Base64常用於JSON、電子郵件或文字通道傳輸二進位內容,但體積會增加,而且不是加密。任何拿到內容的人都能解碼,不能用來保護訪談錄音或設備憑證。

十、建立二進位檔清冊

from pathlib import Path


def binary_inventory(folder):
    allowed = {".jpg", ".jpeg", ".png", ".wav", ".mp3", ".pdf"}
    records = []

    for path in sorted(Path(folder).rglob("*")):
        if path.is_file() and path.suffix.lower() in allowed:
            records.append({
                "相對路徑": str(path.relative_to(folder)),
                "大小": path.stat().st_size,
                "SHA256": sha256_file(path),
            })
    return records


# for record in binary_inventory("教學媒體"):
#     print(record)

清冊公開前要檢查檔名是否含姓名、地址或活動參與者資訊。SHA-256摘要本身通常不透露內容,但可用於比對已知檔案,仍應依資料政策管理。

十一、驗證備份清冊

def compare_inventories(source_records, backup_records):
    source_map = {
        record["相對路徑"]: record["SHA256"]
        for record in source_records
    }
    backup_map = {
        record["相對路徑"]: record["SHA256"]
        for record in backup_records
    }

    all_paths = sorted(source_map.keys() | backup_map.keys())
    return [
        {
            "相對路徑": path,
            "狀態": (
                "一致"
                if source_map.get(path) == backup_map.get(path)
                else "缺少或內容不同"
            ),
        }
        for path in all_paths
    ]

十二、不要用雜湊保存密碼

本篇SHA-256用於檔案完整性,不適合直接保存使用者密碼。密碼需要專用、帶鹽且刻意緩慢的演算法與成熟框架;也不要自己發明加密方式。

十三、常見錯誤檢查表

現象原因修正
照片出現UnicodeDecodeError以文字模式讀取改用rb,不指定encoding
大型錄音占滿記憶體一次read全部內容使用固定大小分塊
備份覆蓋原檔使用wb且未檢查使用xb並分開來源與目標
檔案大小相同就判斷一致大小不能代表內容比較SHA-256
把Base64當加密誤解其用途視為編碼,不存敏感資料

十四、USR實作挑戰

挑戰A|媒體清冊
對已授權的教學副本建立相對路徑、大小與SHA-256清冊,檢查檔名是否洩露個資。
挑戰B|備份驗證
複製三個小型教學檔案到獨立資料夾,比對雜湊;再修改副本一個位元組,觀察結果。
挑戰C|授權分級
為原始錄音、內部研究版、講述者確認版與公開剪輯版設計不同資料夾與存取規則。

十五、與生成式AI協作

請擔任Python二進位檔與USR媒體保存助教。
請檢查我的照片與錄音備份程式:
1. 使用rb與xb,不得覆蓋原始媒體;
2. 大檔案必須分塊處理;
3. 複製後以SHA-256驗證;
4. Base64只能視為編碼,不是加密;
5. 清冊不得洩露不必要個資;
6. 能存取檔案不代表取得公開或AI訓練授權。
請先列出風險,再提供可測試函式。
本篇小結
二進位檔要用位元組模式處理,大檔案要分塊,重要副本要拒絕覆蓋並用雜湊驗證。技術確保內容完整,授權與權限制度則確保使用正當。

沒有留言:

張貼留言