다산 정약용 선생님의 '유배지에서 보낸 편지'의 주요 문구를 정리하고 나름의 해석을 붙인 문서를 만든 다음, 원문 부분을 '기울임꼴'과 '파랑'으로 적용하고자 했습니다(아래 이미지 참조). 전체가 68개 항목이므로, 68번의 반복되는 수작업을 대신하, 간단한 Word 자동화 프로그램을 파이썬으로 구현하여 0.1초만에 정리를 한 코드입니다.


 
from docx import Document
from docx.shared import RGBColor

# 워드 문서 열기
doc = Document(r"C:\Users\user\Desktop\dasan_input.docx")

# 플래그: '가. 원문 인용'을 만난 뒤 다음 문단을 수정하기 위해 사용
mark_next = False  

for para in doc.paragraphs:
    text = para.text.strip()

    if mark_next:
        # 본문(원문 인용 부분) 스타일 적용
        for run in para.runs:
            run.italic = True  # 기울임꼴
            run.font.color.rgb = RGBColor(0, 0, 255)  # 파랑 (RGB 값)
        mark_next = False  # 한번 적용 후 초기화

    # '가. 원문 인용' 찾기
    if text.startswith("가. 원문 인용"):
        mark_next = True

# 수정된 문서 저장
doc.save(r"C:\Users\user\Desktop\dasan_output.docx")
 

 

* 주의 사항 1:

     터미널(또는 명령 프롬프트, PowerShell)에서 아래 실행: 

     pip install python-docx

       - docx 라고만 설치하는 다른 패키지가 있는데, 그것은 오래된 버전입니다. 꼭 python-docx 를 설치해야 합니다.

       - Jupyter Notebook을 쓰신다면 !pip install python-docx 로 실행하시면 됩니다.

 

 

* 주의 사항 2:

    Python 문자열에서 \U 같은 건 유니코드 이스케이프로 해석되기 때문에,
   파일 경로 C:\Users\user\Desktop\input.docx 를 그대로 쓰면 에러가 납니다.

       - 방법 1: r"문자열" (raw string) 사용

           doc = Document(r"C:\Users\user\Desktop\input.docx")

           r을 붙이면 \가 escape 되지 않고 그대로 처리됩니다. 가장 깔끔한 방법이에요.

       - 방법 2: 역슬래시 두 번 (\\) 사용

           doc = Document("C:\\Users\\user\\Desktop\\input.docx")

           Python에서 \\는 실제 \ 한 개로 인식됩니다.

       - 방법 3: 슬래시(/)로 바꾸기

           Windows에서도 /를 경로 구분자로 인식합니다.

           doc = Document("C:/Users/user/Desktop/input.docx")

    정리하면 → 제일 편한 건 방법 1 (raw string) 또는 방법 3 (슬래시 사용) 을 일반적으로 씁니다.

 

반응형

ExifTool은 사진·영상·문서 파일 등 다양한 파일의 메타데이터(metadata) 를 다룰 수 있는 세계적으로 가장 강력한 툴 중 하나라고 합니다. 사진 파일에서 "찍은 날짜"를 불러오고 싶다 등의 작업을 할때, 사실 ExifTool이 그러한 작업에 있어서 표준이자 최강자라고 할 수 있습니다. 아래에 상세히 정리합니다.


1. ExifTool이란 무엇인가?

  • 개발자: Phil Harvey (캐나다 출신 엔지니어, 오픈소스로 공개)
  • 형식: 명령줄(command line) 기반의 프로그램
  • 주요 기능:
    • 사진, 동영상, PDF, 오디오 등 130가지 이상의 파일 포맷 지원
    • 읽기(Read): 파일에 저장된 모든 메타데이터 확인 가능
    • 쓰기(Write): 메타데이터 수정/삭제/추가 가능
    • 변환(Convert): 특정 메타데이터를 기반으로 파일 이름 자동 변경, CSV/JSON/XML 등으로 내보내기
  • 지원하는 메타데이터 표준:
    • EXIF (카메라 촬영 정보, 날짜, 위치 등)
    • IPTC (언론·보도용 메타데이터)
    • XMP (어도비 표준 메타데이터)
    • MakerNotes (Canon, Nikon 등 제조사별 고유 데이터)

   즉, 사진 속 "촬영 날짜", "카메라 기종", "위치 좌표(GPS)", "수정 날짜" 등등을 자유롭게 꺼내거나 바꾸는 데 특화된 툴.


2. 어떤 경우에 사용되는가?

 가. 사진 정리

  • "촬영 날짜" 기반으로 파일명을 자동으로 바꾸기
  • 촬영 장소(GPS)로 폴더 분류
  • 중복된 사진 찾기

 나. 포렌식 / 법률

  • 디지털 증거물의 메타데이터 확인 (언제, 어디서 촬영되었는지)
  • 문서 위조 여부 검증

 다. 출판 / 언론

  • IPTC/XMP 태그 추가 (작가명, 저작권 정보, 설명 등)

 라. 개인 백업

  • 수천 장의 사진을 날짜/위치별로 정리
  • 촬영 일자 기반으로 자동 폴더 구조 생성 (예: 2020/2020-05-03/IMG_001.jpg)

3. 설치 방법

 가. Windows

  1. ExifTool 공식 홈페이지에서 exiftool-13.34_64.zip 다운로드(오늘 기준, 64-bit 경우의 버전)
  2. 압축을 풀면 exiftool(-k).exe 파일이 있음
  3. 파일명을 exiftool.exe로 바꾸고, 원하는 폴더(C:\ExifTool\)에 저장
  4. 환경변수(PATH)에 해당 폴더를 추가 → 어디서든 exiftool 실행 가능

   또는, Python이나 다른 언어에서 라이브러리 형태로 호출도 가능.


 나. macOS / Linux

  • Homebrew (macOS)
     
    brew install exiftool
  • Linux (Debian/Ubuntu 계열)
     
    sudo apt-get install libimage-exiftool-perl

4. 기본 사용법

명령어는 exiftool [옵션] 파일명 형태입니다.

 가. 사진 메타데이터 전체 확인→ 촬영일, 카메라, GPS 좌표 등 모든 정보 출력
     exiftool IMG_0001.JPG

 나. 촬영 날짜만 확인  
     exiftool -DateTimeOriginal IMG_0001.JPG

 다. 여러 장의 사진 날짜 기반으로 파일명 변경→ 예: 2016-01-01_14-26-00.jpg

     exiftool '-FileName<DateTimeOriginal' -d "%Y-%m-%d_%H-%M-%S.%%e" *.JPG

 라. 폴더 전체 파일 메타데이터를 CSV로 내보내기

     exiftool -csv *.JPG > metadata.csv

 마. 메타데이터 수정

     exiftool -Artist="Stephen Kim(me^^)" -Copyright="© 2025" IMG_0001.JPG

 
 
 
 
 

5. 장점 vs 단점

 가. 장점

  • 지원하는 파일 포맷이 세계 최다
  • 매우 안정적, 대량 처리 가능
  • 전문 사진가/포렌식 분야에서 신뢰도 높음
  • 오픈소스, 무료

 나. 단점

  • GUI(그래픽 인터페이스)가 없어서 초보자에겐 어렵게 느껴짐
  • 명령어가 길고 복잡할 수 있음
  • 대용량 폴더 처리 시 시간이 다소 걸릴 수 있음

정리하면, ExifTool은 “사진 파일 정리/이름 변경/날짜 추출” 같은 작업에서 가장 강력하고 신뢰할 수 있는 도구입니다. 예를 들어, "사진 찍은 날짜" 문제는 Pillow 같은 Python 라이브러리보다 ExifTool이 훨씬 정확하게 잡아냅니다.

반응형

아주 간단합니다. 다만,  googletrans의  최신 버전은 내부적으로 비동기 함수를 사용하기 때문에, translator.translate() 호출 시 await를 사용하지 않으면 coroutine 객체가 반환됩니다. 그러므로, await 문법을 사용하지 않는 googletrans의 구버전(4.0.0-rc1)을 설치하여 번역 프로그램을 만들수도 있습니다.

 

1단계: async와 await 사용 

비동기 방식을 사용하기 위해, asyncio 라이브러리를 사용하여 코드를 비동기적으로 실행해야 합니다.

 
import googletrans
import asyncio # asyncio 라이브러리 추가

async def main():
    """
    비동기 방식으로 번역을 실행하는 메인 함수
    """
    translator = googletrans.Translator()

    str_1 = "오늘은 가을비가 내리는 아침입니다."
    # translate 함수가 비동기 함수이므로, await를 사용해 결과를 기다립니다.
    result_1 = await translator.translate(str_1, dest="en", src="auto")
    print(f"오늘은 가을비가 내리는 아침입니다. -> {result_1.text}")

    str_2 = "I am so happy."
    # 마찬가지로 await를 사용합니다.
    result_2 = await translator.translate(str_2, dest="ko", src="auto")
    print(f"I am so happy => {result_2.text}")

if __name__ == "__main__":
    # asyncio.run()을 사용하여 비동기 함수인 main()을 실행합니다.
    asyncio.run(main())
 

 

  • import asyncio: Python의 비동기 프로그래밍을 위한 핵심 라이브러리입니다.
  • async def main():: async 키워드는 이 함수가 비동기 함수임을 명시합니다.
  • await translator.translate(): await 키워드는 translate() 함수가 작업을 완료할 때까지 기다리라고 지시합니다. 이 과정에서 다른 작업을 동시에 처리할 수 있어 효율적입니다.
  • asyncio.run(main()): asyncio.run() 함수는 비동기 프로그램을 시작하는 진입점 역할을 합니다. 이 함수를 통해 main() 함수가 실행되고, await로 표시된 모든 비동기 작업이 완료될 때까지 기다립니다.

 

2단계: 영문 문서를 번역해서, 한글 문서에 저장하기

 
import googletrans
import asyncio
from os import linesep

async def main():
    translator = googletrans.Translator()

    read_file_path = "원문(영문).txt"
    write_file_path = "원문번역(한글).txt"

    with open(read_file_path, "r") as f:
        read_lines = f.readlines()

    for lines in read_lines:
        result = await translator.translate(lines, dest="ko", src="en")
        print(result.text)

        with open(write_file_path, "a", encoding="utf-8") as f:
            f.write(result.text + "\n")

if __name__ == "__main__":
    asyncio.run(main())
 

[ 참조 ] coroutine 

coroutine은 파이썬에서 비동기(asynchronous) 프로그래밍을 가능하게 하는 특별한 함수입니다. 일반 함수와 달리, 실행 중에 잠시 멈췄다가 나중에 다시 이어서 실행될 수 있는 특징을 가지고 있습니다.

가. coroutine의 핵심 개념

일반 함수가 시작부터 끝까지 한 번에 실행되는 반면, coroutine은 await 키워드를 만나면 실행을 잠시 중단하고 제어권을 다른 coroutine에게 넘겨줍니다. 그리고 await 대상 작업(예: 네트워크 요청, 파일 읽기 등)이 완료되면 다시 실행을 재개합니다.

이러한 특성 덕분에 하나의 프로그램이 여러 작업을 동시에 처리하는 것처럼 보이게 만들 수 있습니다.

나. coroutine이 필요한 이유

예를 들어, 웹사이트 100개에서 정보를 가져오는 프로그램을 만든다고 가정해 봅시다.

  • 동기식(Synchronous):
    • 1번 웹사이트 요청 → 응답을 기다림 (10초 소요)
    • 2번 웹사이트 요청 → 응답을 기다림 (10초 소요)
    • ...
    • 총 1000초가 걸립니다.
  • 비동기식(Asynchronous) - Coroutine 활용:
    • 1번 웹사이트 요청 → await
    • 2번 웹사이트 요청 → await
    • 3번 웹사이트 요청 → await
    • ...
    • 100개의 요청을 동시에 보낸 뒤, 응답이 오는 대로 결과를 처리합니다. 가장 오래 걸리는 요청 시간만큼의 시간이 소요되어 전체 작업 시간이 획기적으로 줄어듭니다.

Coroutine은 이런 I/O 작업(Input/Output)이 많은 프로그램에서 빛을 발합니다. 컴퓨터가 데이터를 기다리는 동안 다른 작업을 처리하도록 만들어 효율성을 극대화합니다.

반응형

파이썬에서는 multiprocessing 모듈을 이용해서 여러 코어에서 동시에 조합을 시도할 수 있습니다. 다만, 비밀번호 크래킹을 단순 brute-force(완전 탐색)로만 하면 경우의 수가 기하급수적으로 커져서 사실상 현실적으로 풀기 힘들어집니다. 그래서 실제 보안 연구나 해킹 실무에서는 여러 현대식 접근 방식이 사용되는데, 아래 코드 하단에 정리합니다.

 

2. 멀티코어 병렬처리, 비밀번호 찾기 (ZIP + MS Office)

 
import itertools
import zipfile
import msoffcrypto
import io
import os
import multiprocessing as mp

class PasswordCracker:
    """
    다양한 조합으로 비밀번호를 추측하여 ZIP, DOCX, XLSX, PPTX 파일의
    암호를 해독하는 클래스입니다.
    멀티프로세싱을 사용하여 속도를 높입니다.
    """
    def __init__(self, passwd_string, min_len, max_len, processes=None):
        """
        클래스를 초기화합니다.
       
        Args:
            passwd_string (str): 비밀번호에 사용될 문자열(예: '0123456789abc').
            min_len (int): 추측할 비밀번호의 최소 길이.
            max_len (int): 추측할 비밀번호의 최대 길이.
            processes (int, optional): 사용할 프로세스(코어)의 수. 지정하지 않으면 CPU 코어 수만큼 사용.
        """
        self.passwd_string = passwd_string
        self.min_len = min_len
        self.max_len = max_len
        self.processes = processes or mp.cpu_count()

    def crack(self, file_path):
        """
        주어진 파일의 암호를 해독하는 메인 함수입니다.
        파일 확장자에 따라 적절한 해독 메서드를 호출합니다.

        Args:
            file_path (str): 암호를 해독할 파일의 경로.

        Returns:
            str or None: 비밀번호를 찾으면 비밀번호를, 찾지 못하면 None을 반환.
        """
        file_ext = os.path.splitext(file_path)[1].lower()

        if file_ext == ".zip":
            return self._crack_zip(file_path)
        elif file_ext in [".pptx", ".docx", ".xlsx"]:
            return self._crack_office(file_path)
        else:
            print(f"[!] {file_ext} 형식은 지원하지 않습니다. (zip, office만 지원)")
            return None

    # --- ZIP 파일 해독 관련 메서드 ---
    def _try_zip_password(self, args):
        """
        하나의 비밀번호로 ZIP 파일의 압축을 해제해 보는 함수입니다.
        성공하면 비밀번호를 반환하고, 실패하면 None을 반환합니다.

        Args:
            args (tuple): (파일 경로, 비밀번호) 튜플.

        Returns:
            str or None: 성공 시 비밀번호, 실패 시 None.
        """
        file_path, password = args
        try:
            zFile = zipfile.ZipFile(file_path)
            zFile.extractall(pwd=password.encode('utf-8'))
            return password
        except:
            return None

    def _crack_zip(self, file_path):
        """
        멀티프로세싱을 사용하여 ZIP 파일의 암호를 해독하는 함수입니다.
        각 비밀번호 추측 시도마다 터미널에 진행 상황을 표시합니다.

        Args:
            file_path (str): ZIP 파일의 경로.
           
        Returns:
            str or None: 비밀번호를 찾으면 비밀번호를, 찾지 못하면 None을 반환.
        """
        print(f"[*] ZIP 파일 '{file_path}' 암호 해독 시작...")
        with mp.Pool(self.processes) as pool:
            for length in range(self.min_len, self.max_len + 1):
                attempts = ("".join(p) for p in itertools.product(self.passwd_string, repeat=length))
                args_gen = ((file_path, pwd) for pwd in attempts)
               
                # imap_unordered를 사용하여 결과를 비동기적으로 처리
                for result in pool.imap_unordered(self._try_zip_password, args_gen, chunksize=1000):
                    # 현재 시도하고 있는 비밀번호를 터미널에 표시
                    print(f"[*] 시도 중: '{result if result else result, ' 실패한 추측'}'", end='\r')
                    if result:
                        print(f"\n[+] 비밀번호는 '{result}' 입니다.(zip)")
                        pool.terminate() # 비밀번호를 찾으면 모든 프로세스 종료
                        return result
       
        print("\n[-] 비밀번호를 찾지 못했습니다.(zip)")
        return None

    # --- MS Office 파일 해독 관련 메서드 ---
    def _try_office_password(self, args):
        """
        하나의 비밀번호로 MS Office 파일의 암호를 해제해 보는 함수입니다.
        성공하면 비밀번호를 반환하고, 실패하면 None을 반환합니다.

        Args:
            args (tuple): (파일 경로, 비밀번호) 튜플.

        Returns:
            str or None: 성공 시 비밀번호, 실패 시 None.
        """
        file_path, password = args
        try:
            with open(file_path, "rb") as f:
                office_file = msoffcrypto.OfficeFile(f)
                office_file.load_key(password=password)
                decrypted = io.BytesIO()
                office_file.decrypt(decrypted)
                return password
        except:
            return None

    def _crack_office(self, file_path):
        """
        멀티프로세싱을 사용하여 MS Office 파일의 암호를 해독하는 함수입니다.
        각 비밀번호 추측 시도마다 터미널에 진행 상황을 표시합니다.

        Args:
            file_path (str): MS Office 파일의 경로.

        Returns:
            str or None: 비밀번호를 찾으면 비밀번호를, 찾지 못하면 None을 반환.
        """
        print(f"[*] MS Office 파일 '{file_path}' 암호 해독 시작...")
        with mp.Pool(self.processes) as pool:
            for length in range(self.min_len, self.max_len + 1):
                attempts = ("".join(p) for p in itertools.product(self.passwd_string, repeat=length))
                args_gen = ((file_path, pwd) for pwd in attempts)
               
                # imap_unordered를 사용하여 결과를 비동기적으로 처리
                for result in pool.imap_unordered(self._try_office_password, args_gen, chunksize=500):
                    # 현재 시도하고 있는 비밀번호를 터미널에 표시
                    print(f"[*] 시도 중: '{result if result else result, ' 실패한 추측'}'", end='\r')
                    if result:
                        print(f"\n[+] 비밀번호는 '{result}' 입니다.(office)")
                        pool.terminate() # 비밀번호를 찾으면 모든 프로세스 종료
                        return result
       
        print("\n[-] 비밀번호를 찾지 못했습니다.(office)")
        return None

if __name__ == "__main__":
    # 비밀번호에 사용될 문자와 최소/최대 길이 설정
    passwd_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    cracker = PasswordCracker(passwd_string, min_len=3, max_len=4)

    # 파일 경로 지정
    file_path = "워드암호파일_123.docx"
   
    cracker.crack(file_path)
 

 

  • 멀티프로세싱 사용 (multiprocessing.Pool)
    • 여러 CPU 코어에서 동시에 비밀번호 대입 시도
    • imap_unordered → 결과를 비동기적으로 받아서 성공 즉시 종료
  • 성능 최적화
    • chunksize 조절로 워커별 처리량 최적화
    • 작은 chunksize → 빠른 응답 (즉시 종료 가능)

 

[ Brute-force 이외의 현대적 비밀번호 찾는 방안 정리 ]

1. Dictionary Attack (사전 공격)

  • 미리 만들어둔 비밀번호 후보 사전을 활용해 시도.
  • 예: password123, qwerty, iloveyou, 12345678 등
  • 장점: 실제 사람들이 자주 쓰는 패턴을 빠르게 맞출 수 있음.
  • 단점: 사전에 없는 랜덤 비밀번호는 못 잡음.

2. Hybrid Attack (혼합 공격)

  • 사전 단어 + 규칙 변형
  • 예: password → Password1!, pa$$w0rd, password2025
  • 보통 John the Ripper 나 Hashcat 같은 툴이 이 방식을 자동 지원합니다.

3. Mask Attack (패턴 기반 공격)

  • 비밀번호가 규칙적이라는 전제에서 범위를 줄임.
  • 예: 은행·회사 시스템에서 많이 쓰는 패턴
    • 숫자 6자리 (예: 123456, 202308)
    • 첫 글자 대문자 + 영문 + 숫자 2자리 (예: Abcd12)
  • 범위를 줄여서 brute-force보다 훨씬 빠르게 시도 가능.

4. Rainbow Table Attack

  • 해시된 비밀번호(예: MD5, SHA1 등)에 대해 미리 계산된 해시-평문 매핑 테이블을 이용.
  • 비밀번호 해시만 있으면 빠르게 역추적 가능.
  • 단점: Salt(난수) 기법이 적용된 경우 무력화됨.

5. Rule-based Attack

  • 특정 규칙 기반 변형 (ex. 첫 글자 대문자, 뒤에 ! 붙임).
  • 사전 공격과 결합해 확장.

6. AI / ML 기반 공격 (최신 연구 분야)

  • 최근에는 머신러닝으로 사람들의 비밀번호 생성 습관을 학습한 뒤,
    • 실제로 자주 쓰이는 비밀번호를 확률적으로 예측
    • brute-force보다 훨씬 빠르게 "사람이 쓸 법한" 후보를 뽑아냄
  • 예: PassGAN, OMEN, DeepCrack 같은 연구 프로젝트

7. GPU 가속 (Hashcat, CUDA/OpenCL 활용)

  • CPU 대신 GPU를 이용해 수억~수십억 번/s 시도 가능.
  • 일반 PC에서 brute-force로 8자리 영문/숫자 탐색은 현실적으로 불가하지만,
    • GPU 1대로는 수일
    • GPU 클러스터로는 몇 시간 안에 가능

[ 정리 ]

  • Brute-force는 최후의 방법 (시간 너무 오래 걸림)
  • 현실적인 접근은:
    1. 사전 + 하이브리드 먼저
    2. 특정 패턴/마스크 기반
    3. 그래도 안 되면 GPU 병렬 brute-force

 

반응형

본인이 작성한 오피스(워드, 엑셀, 파워포인트)나 집파일에 암호를 걸어두고, 시간이 지나 비밀번호가 생각나지 않을때가 종종 있어 난처한 경우가 발생합니다. 이때, 시간 여유가 많이 있고 또한 아주 간단한 암호를 설정했다면 '단순 brute-force(무차별 대입) 방식'을 시도해 볼 수 있습니다. (*암호화된 파일을 분실한 경우, 개발사에서도 암호를 복구해 주지 않는 것으로 알고 있습니다. 사본/이전본을 찾는 게 현실적인 해법입니다. 추가로, 본문 하단에 암호분실을 대비한 예방팁을 정리하였습니다.)


  • ZIP 파일
    • zipfile 모듈로 열 수 있고, extractall(pwd=...) 같은 방식으로 비밀번호 검증 가능.
  • MS Office (ppt, pptx, doc, docx, xls, xlsx 등)
    • pptx/docx/xlsx는 사실상 ZIP 압축 기반 구조지만, 내부에 MS가 정한 암호화 헤더가 포함돼 있음.
    • 단, 전용 라이브러리 필요.
  • 아래한글(HWP)
    • HWP 5.0 이후는 OLE Compound File 포맷 (마이크로소프트 OLE 구조).
    • 암호화는 자체 알고리즘으로 되어 있어 파이썬에서 직접 풀 수 있는 공개 라이브러리는 사실상 없음.
    • → olefile + 한글 암호화 해석 구현이 필요 (공식 라이브러리 없음).
    • 암호화된 HWP는 한글에서 AES, SHA1 기반 암호화를 쓰는데, 비밀번호 없이는 복호화 불가.
    • 가능하다면 “암호 해제 프로그램” (상용 소프트웨어) 사용이 현실적이나, 현재까지 그러한 프로그램은 없다고 알고 있음.

1. 통합 코드 (ZIP + MS Office 파일 비밀번호 찾기) 

 
# pip install msoffcrypto-tool

import itertools
import zipfile
import msoffcrypto
import io
import os

class PasswordCracker:
    """
    다양한 조합으로 비밀번호를 추측하여 ZIP, DOCX, XLSX, PPTX 파일의
    암호를 해독하는 클래스입니다.
    """
    def __init__(self, passwd_string, min_len, max_len):
        """
        클래스를 초기화합니다.
       
        Args:
            passwd_string (str): 비밀번호에 사용될 문자열(예: '0123456789').
            min_len (int): 추측할 비밀번호의 최소 길이.
            max_len (int): 추측할 비밀번호의 최대 길이.
        """
        self.passwd_string = passwd_string
        self.min_len = min_len
        self.max_len = max_len

    def crack(self, file_path):
        """
        주어진 파일의 암호를 해독하는 메인 함수입니다.
        파일 확장자에 따라 적절한 해독 메서드를 호출합니다.

        Args:
            file_path (str): 암호를 해독할 파일의 경로.

        Returns:
            str or None: 비밀번호를 찾으면 비밀번호를, 찾지 못하면 None을 반환.
        """
        file_ext = os.path.splitext(file_path)[1].lower()
       
        if file_ext == ".zip":
            return self._crack_zip(file_path)
        elif file_ext in [".pptx", ".docx", ".xlsx"]: # 'xlsx'는 문자열로 변경
            return self._crack_office(file_path)
        else:
            print(f"[!] {file_ext} 형식은 지원하지 않습니다.(zip, office만 지원)")
            return None
   
    def _crack_zip(self, file_path):
        """
        ZIP 파일의 암호를 해독하는 함수입니다.
        비밀번호를 찾으면 즉시 해당 비밀번호를 반환하고, 함수를 종료합니다.
        """
        try:
            zFile = zipfile.ZipFile(file_path)
        except zipfile.BadZipFile:
            print("[-] 유효하지 않은 ZIP 파일입니다.")
            return None

        for length in range(self.min_len, self.max_len + 1):
            for attempt in itertools.product(self.passwd_string, repeat = length):
                passwd = "".join(attempt)
                # 시도 중인 비밀번호를 터미널에 표시 (동일 라인에 덮어쓰기)
                print(f"[*] 시도 중: '{passwd}'", end='\r')
                try:
                    zFile.extractall(pwd=passwd.encode('utf-8'))
                    print(f"\n[+] 비밀번호는 '{passwd}'입니다.(zip)")
                    return passwd # 비밀번호를 찾으면 즉시 반환
                except (RuntimeError, zipfile.BadZipFile):
                    # 비밀번호가 틀렸을 때 발생하는 예외는 무시하고 다음 시도를 계속
                    continue
       
        print("\n[-] 비밀번호를 찾지 못했습니다.(zip)")
        return None
   
    def _crack_office(self, file_path):
        """
        MS Office 파일의 암호를 해독하는 함수입니다.
        비밀번호를 찾으면 즉시 해당 비밀번호를 반환하고, 함수를 종료합니다.
        """
        for length in range(self.min_len, self.max_len + 1):
            for attempt in itertools.product(self.passwd_string, repeat=length):
                passwd = "".join(attempt)
                # 시도 중인 비밀번호를 터미널에 표시 (동일 라인에 덮어쓰기)
                print(f"[*] 시도 중: '{passwd}'", end='\r')
                try:
                    with open(file_path, "rb") as f:
                        office_file = msoffcrypto.OfficeFile(f)
                        office_file.load_key(password=passwd)
                        decrypted = io.BytesIO()
                        office_file.decrypt(decrypted)
                        print(f"\n[+] 비밀번호는 '{passwd}'입니다.(office)")
                        return passwd # 비밀번호를 찾으면 즉시 반환
                except Exception as e:
                    # 비밀번호가 틀렸을 때 발생하는 예외는 무시하고 다음 시도를 계속
                    continue
                   
        print("\n[-] 비밀번호를 찾지 못했습니다.(office)")
        return None

if __name__ == "__main__":
    # 비밀번호에 사용될 문자와 최소/최대 길이 설정
    passwd_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    cracker = PasswordCracker(passwd_string, min_len=3, max_len=4)

    # 파일 경로 지정
    file_path = "워드암호파일_123.docx"
   
    cracker.crack(file_path)
 

 

  • 파일 확장자 자동 구분
    • .zip → zipfile 모듈로 brute-force
    • .pptx/.docx/.xlsx → msoffcrypto-tool로 brute-force
  • 무차별 대입(brute-force)
    • min_len ~ max_len 길이의 모든 조합을 생성 (itertools.product)
    • 하나씩 대입 → 맞으면 성공, 프로그램 종료

 

[ 예방 팁 ]

  • 비밀번호 관리자 사용: 조직/개인별로 안전하게 공유·보관.
  • 버전 기록이 있는 저장소 사용: OneDrive/SharePoint/Google Drive 등.
  • 중요 문서는 이중 보호: 암호+접근권한(ACL)로 관리하고, 비상 복구 담당자/절차를 미리 지정.
  • 규칙적 백업: 로컬+클라우드 3-2-1 규칙(사본 3개, 매체 2종, 오프사이트 1개).
반응형

1. 내부 IP & 외부 IP 

  • socket + getsockname() → 내부 IP 확인
  • urllib 또는 http.client + api.ipify.org → 외부 IP 확인
### =====  내부 IP 확인 =====
import socket          # 네트워크 소켓 통신을 위한 표준 라이브러리
import urllib.request  # HTTP 요청을 보낼 수 있는 표준 라이브러리
import json            # JSON 형식 데이터를 파싱하기 위한 라이브러리
import ssl             # SSL 인증서 처리 라이브러리

in_addr = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # TCP 소켓 객체 생성 (IPv4, TCP 방식)
in_addr.connect(("www.google.com", 443))                      # 구글 서버(443: HTTPS 포트)에 연결 → 내 PC의 네트워크 인터페이스가 사용됨
print("내부 IP : ", in_addr.getsockname()[0])                 # 소켓이 사용한 내 PC의 IP 주소를 가져와 출력


### ===== 외부 IP - 표준 라이브러리(1) urllib 활용 =====
url = "https://api.ipify.org?format=json"                     # 내 외부 IP를 반환해주는 공개 API URL

# SSL 인증서 검증을 비활성화 (테스트용; 보안적으로는 권장되지 않음)
ssl._create_default_https_context = ssl._create_unverified_context

with urllib.request.urlopen(url) as response:                 # 지정한 URL로 HTTPS 요청 전송
    data = response.read().decode("utf-8")                    # 응답(body)을 읽고 UTF-8 문자열로 디코딩
    ip = json.loads(data)["ip"]                               # JSON 문자열을 파싱 후, "ip" 키 값만 추출

print("외부 IP 주소: ", ip)                                   # 외부 IP 출력


### ===== 외부 IP - 표준 라이브러리(2) http.client 활용 =====
import http.client                                            # HTTP 요청을 저수준으로 다룰 수 있는 표준 라이브러리
import json                                                   # JSON 파싱 라이브러리 (다시 import해도 무방)
import ssl                                                    # SSL 인증서 관련 라이브러리 (다시 import해도 무방)

# SSL 인증서 검증을 비활성화한 context 생성 (로컬 테스트용)
context = ssl._create_unverified_context()

conn = http.client.HTTPSConnection("api.ipify.org", context = context)  # api.ipify.org에 HTTPS 연결 생성
conn.request("GET", "/?format=json")                                    # GET 요청 보내기

res = conn.getresponse()                                                # 서버로부터 응답 받기
data = res.read().decode("utf-8")                                       # 응답 내용을 읽어서 UTF-8 문자열로 변환
ip = json.loads(data)["ip"]                                             # JSON 파싱 후, "ip" 값 추출

print("외부 IP 주소: ", ip)                                             # 외부 IP 출력

 

2. 함수로 정리

  • get_internal_ip(), get_external_ip_urllib(), get_external_ip_httpclient() 형태로 호출할 수 있게 정리
 
import socket             # 네트워크 소켓(통신) 기능 제공
import urllib.request     # 고수준 HTTP(S) 요청을 보낼 수 있는 표준 라이브러리
import http.client        # 저수준 HTTP(S) 요청을 다루는 표준 라이브러리
import json               # JSON 문자열 ↔ 파이썬 객체 변환
import ssl                # SSL/TLS 인증서 검증 및 컨텍스트 설정


def get_internal_ip(target_host="www.google.com", port=443, timeout=5):
    """
    내부(사설) IP를 추출합니다.
    원리: 외부 서버에 소켓으로 연결하면, OS가 사용할 네트워크 인터페이스를 선택하는데,
          그때 소켓의 로컬 주소에 내부 IP가 담깁니다.
    """
    # 외부 서버(기본: 구글 443)로 TCP 연결을 생성 (타임아웃 설정)
    with socket.create_connection((target_host, port), timeout=timeout) as s:
        # 연결된 소켓의 로컬 주소(내 컴퓨터 측 주소)를 얻고, 그중 IP만 추출
        return s.getsockname()[0]


def get_external_ip_urllib(verify_ssl=True, timeout=5):
    """
    외부(공인) IP를 urllib로 조회합니다.
    기본은 SSL 인증서 검증을 수행(verify_ssl=True). 검증 문제 시 False로 끌 수 있습니다(테스트용).
    """
    # 외부 IP를 JSON으로 반환하는 공개 API URL

    # SSL 컨텍스트 생성: 검증 모드/비검증 모드 선택
    context = ssl.create_default_context() if verify_ssl else ssl._create_unverified_context()

    # 지정 URL로 HTTPS 요청을 보냄 (컨텍스트/타임아웃 지정)
    with urllib.request.urlopen(url, context=context, timeout=timeout) as resp:
        # 응답 본문 바이트를 읽고 UTF-8로 디코딩
        body = resp.read().decode("utf-8")
        # JSON 문자열을 파싱하고 "ip" 필드만 추출
        return json.loads(body)["ip"]


def get_external_ip_httpclient(verify_ssl=True, timeout=5):
    """
    외부(공인) IP를 http.client로 조회합니다.
    기본은 SSL 인증서 검증을 수행(verify_ssl=True). 검증 문제 시 False로 끌 수 있습니다(테스트용).
    """
    # SSL 컨텍스트 생성: 검증 모드/비검증 모드 선택
    context = ssl.create_default_context() if verify_ssl else ssl._create_unverified_context()

    # HTTPS 연결 객체를 생성 (호스트: api.ipify.org, SSL 컨텍스트/타임아웃 적용)
    conn = http.client.HTTPSConnection("api.ipify.org", context=context, timeout=timeout)
    try:
        # GET 요청 전송 (경로에 ?format=json 쿼리로 JSON을 요청)
        conn.request("GET", "/?format=json")
        # 서버 응답 객체 수신
        res = conn.getresponse()
        # 응답 바디를 읽고 UTF-8로 디코딩
        body = res.read().decode("utf-8")
        # JSON 파싱 후 "ip" 필드만 반환
        return json.loads(body)["ip"]
    finally:
        # 네트워크 리소스 정리를 위해 연결 닫기
        conn.close()


# ------ 사용 예시 (직접 실행 시) ------
if __name__ == "__main__":
    # 내부 IP 출력 (연결 가능한 외부 호스트를 통해 로컬 인터페이스 확인)
    print("내부 IP :", get_internal_ip())

    # 외부 IP 출력 - urllib (인증서 검증 ON: 운영 권장)
    try:
        print("외부 IP (urllib, verify=ON):", get_external_ip_urllib(verify_ssl=True))
    except Exception as e:
        print("urllib 검증 ON 실패 → 테스트용 비검증으로 재시도:", e)
        print("외부 IP (urllib, verify=OFF):", get_external_ip_urllib(verify_ssl=False))

    # 외부 IP 출력 - http.client (인증서 검증 ON: 운영 권장)
    try:
        print("외부 IP (http.client, verify=ON):", get_external_ip_httpclient(verify_ssl=True))
    except Exception as e:
        print("http.client 검증 ON 실패 → 테스트용 비검증으로 재시도:", e)
        print("외부 IP (http.client, verify=OFF):", get_external_ip_httpclient(verify_ssl=False))

(SSL 인증서 문제가 있는 환경을 고려해, verify_ssl 옵션으로 검증을 끄거나 켤 수 있게 했습니다. 운영/배포 환경에선 verify_ssl=True를 권장합니다.)

  • 운영/배포: verify_ssl=True 유지(기본값).
  • 로컬 테스트에서 인증서 오류 발생: 일시적으로 verify_ssl=False로 호출 → 동작 확인 후, 인증서 환경을 정상화하세요.
    • (macOS) Python 설치 경로의 Install Certificates.command 실행
    • (Windows/Linux) 시스템 루트 인증서 최신 상태로 유지

3. 클래스로 묶기

  • IPFetcher 클래스 안에 get_internal_ip(), get_external_ip_urllib(), get_external_ip_httpclient() 메서드 3개를 넣었습니다.
  • verify_ssl, timeout, target_host 등은 생성자에서 기본값 설정 후 메서드에서 사용합니다.  

 

 
import socket             # 네트워크 소켓(통신) 기능 제공
import urllib.request     # 고수준 HTTP(S) 요청을 보낼 수 있는 표준 라이브러리
import http.client        # 저수준 HTTP(S) 요청을 다루는 표준 라이브러리
import json               # JSON 문자열 ↔ 파이썬 객체 변환
import ssl                # SSL/TLS 인증서 검증 및 컨텍스트 설정


class IPFetcher:
    """
    내부 IP(사설 IP)와 외부 IP(공인 IP)를 조회하는 기능을 제공하는 클래스.
    """

    def __init__(self, target_host="www.google.com", port=443, timeout=5, verify_ssl=True):
        """
        클래스 초기화.
        :param target_host: 내부 IP 확인을 위해 연결할 외부 호스트 (기본: 구글)
        :param port: 연결 포트 (기본: 443)
        :param timeout: 네트워크 연결 타임아웃 (초)
        :param verify_ssl: 외부 IP 조회 시 SSL 인증서 검증 여부 (운영환경: True 권장)
        """
        self.target_host = target_host
        self.port = port
        self.timeout = timeout
        self.verify_ssl = verify_ssl

    def get_internal_ip(self):
        """
        내부(사설) IP 조회.
        원리: 외부 호스트에 소켓 연결을 열면, OS가 선택한 네트워크 인터페이스의 로컬 주소(IP)를 알 수 있음.
        """
        with socket.create_connection((self.target_host, self.port), timeout=self.timeout) as s:
            return s.getsockname()[0]

    def get_external_ip_urllib(self):
        """
        외부(공인) IP 조회 - urllib 사용.
        :return: 외부 IP 문자열
        """
        context = ssl.create_default_context() if self.verify_ssl else ssl._create_unverified_context()

        with urllib.request.urlopen(url, context=context, timeout=self.timeout) as resp:
            body = resp.read().decode("utf-8")
            return json.loads(body)["ip"]

    def get_external_ip_httpclient(self):
        """
        외부(공인) IP 조회 - http.client 사용.
        :return: 외부 IP 문자열
        """
        context = ssl.create_default_context() if self.verify_ssl else ssl._create_unverified_context()
        conn = http.client.HTTPSConnection("api.ipify.org", context=context, timeout=self.timeout)
        try:
            conn.request("GET", "/?format=json")
            res = conn.getresponse()
            body = res.read().decode("utf-8")
            return json.loads(body)["ip"]
        finally:
            conn.close()


# ---- 사용 예시 ----
if __name__ == "__main__":
    fetcher = IPFetcher(timeout=5, verify_ssl=True)

    # 내부 IP 확인
    print("내부 IP :", fetcher.get_internal_ip())

    # 외부 IP 확인 (urllib)
    try:
        print("외부 IP (urllib):", fetcher.get_external_ip_urllib())
    except Exception as e:
        print("urllib 오류:", e)
        print("재시도 (verify_ssl=False):", IPFetcher(verify_ssl=False).get_external_ip_urllib())

    # 외부 IP 확인 (http.client)
    try:
        print("외부 IP (http.client):", fetcher.get_external_ip_httpclient())
    except Exception as e:
        print("http.client 오류:", e)
        print("재시도 (verify_ssl=False):", IPFetcher(verify_ssl=False).get_external_ip_httpclient())

 

이제, 아래 예시처럼 IPFetcher 객체 하나로 내부/외부 IP 조회 기능을 모두 사용할 수 있습니다.

 
fetcher = IPFetcher(timeout=3, verify_ssl=True)
print(fetcher.get_internal_ip())
print(fetcher.get_external_ip_urllib())
print(fetcher.get_external_ip_httpclient())
 

 

 

반응형

+ Recent posts