verificador-hashes / hash_tools.py
WallaceBrasil's picture
Upload 4 files
79e1111 verified
Raw
History Blame Contribute Delete
6.1 kB
# hash_tools.py
"""
Gera uma planilha .xlsx (cabeçalho preto, linhas cinza) contendo:
- Nome do arquivo
- Extensão
- SHA256
- SHA512
- MD5
Entrada: lista com 0..N caminhos; cada item pode ser:
- um arquivo "solto" OU
- um .zip (com subpastas)
Para .zip, os arquivos são lidos de dentro do zip (sem extrair em disco).
"""
from __future__ import annotations
from pathlib import Path
from typing import Iterable, List, Tuple
import hashlib
import zipfile
import io
import time
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
# ------------------------------------------------------------
# Hash helpers
# ------------------------------------------------------------
def _hash_stream(stream: io.BufferedReader, chunk_size: int = 1024 * 1024) -> Tuple[str, str, str]:
"""Lê um stream binário e calcula SHA256, SHA512 e MD5 em uma única passada."""
h256 = hashlib.sha256()
h512 = hashlib.sha512()
hmd5 = hashlib.md5()
while True:
chunk = stream.read(chunk_size)
if not chunk:
break
h256.update(chunk)
h512.update(chunk)
hmd5.update(chunk)
return h256.hexdigest(), h512.hexdigest(), hmd5.hexdigest()
def _hash_file(path: Path) -> Tuple[str, str, str]:
"""Calcula os hashes de um arquivo do disco."""
with path.open("rb") as f:
return _hash_stream(f)
def _hash_zip_entry(zf: zipfile.ZipFile, zi: zipfile.ZipInfo) -> Tuple[str, str, str]:
"""Calcula hashes de um item dentro do ZIP, sem extrair para disco."""
with zf.open(zi, "r") as f:
return _hash_stream(io.BufferedReader(f))
# ------------------------------------------------------------
# Coleta de arquivos (múltiplas origens)
# ------------------------------------------------------------
def _iter_zip_files(zip_path: Path) -> Iterable[Tuple[str, str, str, str, str]]:
"""
Itera pelos arquivos de um .zip gerando tuplas:
(nome_exibicao, extensao, sha256, sha512, md5)
- nome_exibicao: "<zip>::<caminho/interno>"
- extensao: sufixo do nome (minúsculo, incluindo '.'); vazio se não existir
"""
with zipfile.ZipFile(zip_path, "r") as zf:
for zi in zf.infolist():
if zi.is_dir():
continue
nome_rel = f"{zip_path.name}::{zi.filename}"
ext = Path(zi.filename).suffix.lower()
sha256, sha512, md5 = _hash_zip_entry(zf, zi)
yield (nome_rel, ext, sha256, sha512, md5)
def _iter_single_file(file_path: Path) -> Iterable[Tuple[str, str, str, str, str]]:
"""Itera um arquivo único retornando a mesma tupla de dados."""
ext = file_path.suffix.lower()
sha256, sha512, md5 = _hash_file(file_path)
yield (file_path.name, ext, sha256, sha512, md5)
def iterar_entradas(paths: List[Path]) -> Iterable[Tuple[str, str, str, str, str]]:
"""
Recebe uma lista de caminhos. Para cada caminho:
- se for .zip, itera os itens internos;
- caso contrário, calcula diretamente.
"""
for p in paths:
if p.suffix.lower() == ".zip":
yield from _iter_zip_files(p)
else:
yield from _iter_single_file(p)
# ------------------------------------------------------------
# Excel estilizado (cabeçalho preto, linhas cinza)
# ------------------------------------------------------------
def _escrever_excel_formatado(linhas: List[Tuple[str, str, str, str, str]], xlsx_path: Path) -> None:
"""
Gera um XLSX com:
- Cabeçalho: fundo preto, fonte branca e negrito
- Linhas: fundo cinza claro
- Bordas finas e alinhamento à esquerda
- Largura de coluna autoajustada (com limite)
"""
wb = Workbook()
ws = wb.active
ws.title = "Hashes"
headers = ["Nome do arquivo", "Extensão", "SHA256", "SHA512", "MD5"]
fill_header = PatternFill("solid", fgColor="111111") # preto
font_header = Font(bold=True, color="FFFFFF") # branco
fill_row = PatternFill("solid", fgColor="E5E5E5") # cinza claro
border_thin = Border(
left=Side(style="thin", color="444444"),
right=Side(style="thin", color="444444"),
top=Side(style="thin", color="444444"),
bottom=Side(style="thin", color="444444"),
)
align_left = Alignment(horizontal="left", vertical="center")
# Cabeçalho
ws.append(headers)
for col in range(1, len(headers) + 1):
c = ws.cell(row=1, column=col)
c.fill = fill_header
c.font = font_header
c.alignment = align_left
c.border = border_thin
# Linhas
for row_vals in linhas:
ws.append(list(row_vals))
for row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=1, max_col=len(headers)):
for c in row:
c.fill = fill_row
c.alignment = align_left
c.border = border_thin
# Ajuste de largura (máx 100)
for col_cells in ws.columns:
max_len = 0
letter = col_cells[0].column_letter
for c in col_cells:
val = "" if c.value is None else str(c.value)
if len(val) > max_len:
max_len = len(val)
ws.column_dimensions[letter].width = min(max_len + 2, 100)
wb.save(xlsx_path)
# ------------------------------------------------------------
# API pública: gera XLSX a partir de 1..N entradas (arquivos e/ou zips)
# ------------------------------------------------------------
def gerar_planilha_hash_multi(paths: List[Path]) -> str:
"""
Recebe lista de caminhos (mistos: arquivos e/ou .zip).
Retorna o caminho do XLSX gerado (no diretório atual).
"""
linhas: List[Tuple[str, str, str, str, str]] = list(iterar_entradas(paths))
# nome amigável com timestamp
stamp = time.strftime("%Y%m%d-%H%M%S")
out_path = Path.cwd() / f"hashes_{stamp}.xlsx"
_escrever_excel_formatado(linhas, out_path)
return str(out_path)