Hashing and Digital Signature

codakcodak·2026년 1월 19일

Security

목록 보기
3/4

Hash Function(해시 함수)

  • 임의의 길이를 가진 입력 데이터를 받아,
    고정된 길이의 출력값(Hash, Digest) 을 생성하는 함수
  • 출력값은 보통 메시지 다이제스트(Message Digest) 또는 체크섬(Checksum) 이라고 불린다.
  • 입력 데이터의 크기에는 제한이 없다.
  • 출력은 Hash algorithm에 따라 항상 고정된 길이이다.
    • SHA-256 → 256비트
    • SHA-384 → 384비트

MongoDB Checksum

  • mongodb-macos-x86_64-7.0.24.tgz 패키지 파일을 다운로드 후 mongoDB의 해당 패키지로 공지된 sha256 값을 비교한 무결성 검사
import requests
import hashlib


BASE_URL = "https://fastdl.mongodb.org/osx"

MONGODB_FILE = "mongodb-macos-x86_64-7.0.24.tgz"
CHECKSUM_FILE = MONGODB_FILE + ".sha256"

MONGODB_URL = f"{BASE_URL}/{MONGODB_FILE}"
CHECKSUM_URL = f"{BASE_URL}/{CHECKSUM_FILE}"


def download_file(url, output_path):
    response = requests.get(url, stream=True)
    response.raise_for_status()

    with open(output_path, "wb") as f:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                f.write(chunk)

    print(f"[+] Downloaded: {output_path}")


def calculate_sha256(file_path):
    sha256 = hashlib.sha256()

    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            sha256.update(chunk)

    return sha256.hexdigest()


def read_expected_hash(sha256_file):
    with open(sha256_file, "r") as f:
        # 형식: <hash> <filename>
        return f.read().split()[0]


if __name__ == "__main__":
    # 1. MongoDB 바이너리 다운로드
    download_file(MONGODB_URL, MONGODB_FILE)

    # 2. SHA-256 체크섬 파일 다운로드
    download_file(CHECKSUM_URL, CHECKSUM_FILE)

    # 3. 기대 해시 값 읽기
    expected_hash = read_expected_hash(CHECKSUM_FILE)

    # 4. 실제 파일 해시 계산
    downloaded_hash = calculate_sha256(MONGODB_FILE)

    print("\n[*] Expected SHA-256  :", expected_hash)
    print("[*] Downloaded SHA-256:", downloaded_hash)

    # 5. 비교
    if downloaded_hash == expected_hash:
        print("\n[✔] File integrity verified (hash match)")
    else:
        print("\n[✘] Hash mismatch! File may be corrupted or tampered with")
  • 출력값
[+] Downloaded: mongodb-macos-x86_64-7.0.24.tgz
[+] Downloaded: mongodb-macos-x86_64-7.0.24.tgz.sha256

[*] Expected SHA-256  : 5e84567b3d11690d4da8cf7f7fb9f98e2e0119a2cd32a78590f134215b455e5b
[*] Downloaded SHA-256: 5e84567b3d11690d4da8cf7f7fb9f98e2e0119a2cd32a78590f134215b455e5b

[✔] File integrity verified (hash match)

Salt

  • Salt는 비밀번호를 해시할 때
    비밀번호에 추가되는 랜덤 데이터를 의미한다.
  • 모든 사용자는 서로 다른 salt를 가진다.
  • Salt는 해시와 함께 저장된다
  • Rainbow Table
    * 공격자가 미리 계산해 둔
    (비밀번호 후보 → 해시 값) 의 테이블이다.
  • salt는 비밀번호 역추적을 완전히 차단하지는 않지만,
    해시 결과의 재사용(Rainbow Table)을 방지함으로써 공격 비용을 크게 증가시킨다.

Digital Signature

  • 메시지가 변조되지 않았음을 증명하고,
    해당 메시지가 누가 보냈는지를 확인하며,
    송신자가 나중에 부인할 수 없도록 하기 위한
    암호학적 검증 메커니즘이다. (암호화 및 복호화 연산은 하지 않는다.)
  • 전자서명 생성 (Sign)
    1. 메시지 생성
    2. 메시지 해시 계산
    3. 해시값을 개인키로 서명
    4. 메시지 + 서명 전송
  • 전자서명 검증 (Verify)
    1. 메시지 수신
    2. 메시지 해시 재계산
    3. 서명을 공개키로 검증
    4. 두 해시 값 비교
    5. 일치 → 유효 / 불일치 → 무효
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from cryptography.exceptions import InvalidSignature
import binascii


private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()


def sign_message(message: bytes, private_key):
    # 1. 메시지 생성
    print("Message:", message.decode())

    # 2. 메시지 해시 계산
    digest = hashes.Hash(hashes.SHA256())
    digest.update(message)
    hashed = digest.finalize()
    print("Hash (SHA-256):", binascii.hexlify(hashed).decode())

    # 3. 해시값을 개인키로 서명
    signature = private_key.sign(hashed, ec.ECDSA(Prehashed(hashes.SHA256())))
    print("Signature:", binascii.hexlify(signature).decode())

    # 4. 메시지 + 서명 전송
    packet = {"message": message, "signature": signature}
    return packet


def verify_message(packet, public_key) -> bool:
    # 1. 메시지 수신
    message = packet["message"]
    signature = packet["signature"]

    print("\nReceived message:", message.decode())
    print("Received signature:", binascii.hexlify(signature).decode())

    # 2. 메시지 해시 재계산
    digest = hashes.Hash(hashes.SHA256())
    digest.update(message)
    hashed = digest.finalize()
    print("Recomputed Hash (SHA-256):", binascii.hexlify(hashed).decode())

    # 3. 서명을 공개키로 검증
    try:
        public_key.verify(signature, hashed, ec.ECDSA(Prehashed(hashes.SHA256())))
        # 4. 두 해시 값 비교
        # 5. 일치 → 유효
        return True
    except InvalidSignature:
        # 5. 불일치 → 무효
        return False


message = b"You're hired, Bob"

packet = sign_message(message, private_key)
result = verify_message(packet, public_key)

print("\nSignature valid:", result)
  • 출력값
Message: You're hired, Bob
Hash (SHA-256): ef8b68a4b1e0d561814359be6633b70d6e28b7c3457c18013eda59de2cbbda2a
Signature: 30450220546c94c5e0da970fad0bb1a7cd453cfec4cadfc4733ae839e5c8ee8ee7f691b9022100e0fe3eed73e4f830f8483873d0bfce4d0875894691d0107edc3a7162021eb915

Received message: You're hired, Bob
Received signature: 30450220546c94c5e0da970fad0bb1a7cd453cfec4cadfc4733ae839e5c8ee8ee7f691b9022100e0fe3eed73e4f830f8483873d0bfce4d0875894691d0107edc3a7162021eb915
Recomputed Hash (SHA-256): ef8b68a4b1e0d561814359be6633b70d6e28b7c3457c18013eda59de2cbbda2a

Signature valid: True
profile
숲을 보는 코더

0개의 댓글