본 튜토리얼에서는 Python의 내장 모듈인 hashlib
을 활용하여 안전한 해시를 생성하는 방법을 상세히 안내합니다.
해싱의 중요성과 보안 해시를 프로그래밍적으로 계산하는 방법을 이해하는 것은, 비록 응용 프로그램 보안 분야에 직접 종사하지 않더라도 매우 유용할 수 있습니다. 그렇다면 왜 그럴까요?
Python 프로젝트를 진행하다 보면, 비밀번호나 기타 중요한 정보를 데이터베이스나 소스 코드 파일에 그대로 저장하는 것에 대한 우려가 생길 수 있습니다. 이러한 상황에서는 민감한 정보에 해싱 알고리즘을 적용하여 원래 정보 대신 해시 값을 저장하는 것이 훨씬 안전한 방법입니다.
이 가이드에서는 해싱이 무엇인지, 그리고 암호화와는 어떻게 다른지 명확하게 설명합니다. 또한 보안 해시 함수의 주요 속성에 대해 알아보고, 일반적인 해싱 알고리즘을 사용하여 Python에서 평문 메시지의 해시 값을 계산하는 방법을 살펴봅니다. 이를 위해 Python에 내장된 hashlib
모듈을 활용합니다.
이제 이 모든 내용을 자세히 알아보도록 하겠습니다!
해싱이란 무엇인가?
해싱 과정은 메시지 문자열을 입력으로 받아 고정된 길이의 출력 값인 해시를 생성하는 과정입니다. 이는 주어진 해싱 알고리즘에 대해 입력 값의 길이에 상관없이 출력 해시의 길이가 항상 일정하다는 것을 의미합니다. 하지만 이것이 암호화와는 어떻게 다를까요?
암호화에서는 메시지 또는 평문을 암호화 알고리즘을 사용하여 암호화된 형태로 변환합니다. 이렇게 암호화된 출력 값은 다시 복호화 알고리즘을 통해 원래의 메시지 문자열로 복원할 수 있습니다.
그러나 해싱은 작동 방식이 다릅니다. 앞서 설명했듯이, 암호화는 암호화된 메시지를 원래의 메시지로, 또는 그 반대로 변환할 수 있다는 점에서 양방향 프로세스입니다.
하지만 해싱은 암호화와 달리 단방향 프로세스입니다. 즉, 해시 값으로부터 원래의 입력 메시지를 알아낼 수 없습니다.
해시 함수의 주요 속성
이제 해시 함수가 가져야 할 몇 가지 중요한 속성을 간단히 살펴보겠습니다.
- 결정성: 해시 함수는 결정적입니다. 즉, 동일한 메시지 m에 대해 해시 값은 항상 동일해야 합니다.
- 역상 저항성: 해싱이 단방향 작업이라고 앞서 설명했습니다. 역상 저항성 속성은 특정 해시 값으로부터 원래의 메시지 m을 찾는 것이 계산적으로 불가능해야 함을 의미합니다.
- 충돌 저항성: 서로 다른 두 메시지 문자열 m1과 m2에 대해, 해시(m1) = 해시(m2)가 되는 두 값을 찾는 것이 매우 어렵거나 계산적으로 불가능해야 합니다. 이러한 속성을 충돌 저항성이라고 합니다.
- 제2 역상 저항성: 이는 주어진 메시지 m1과 그 해시 값 해시(m1)에 대해, 해시(m1) = 해시(m2)를 만족하는 또 다른 메시지 m2를 찾는 것이 불가능해야 함을 의미합니다.
Python의 hashlib
모듈
Python에 내장된 hashlib
모듈은 SHA 및 MD5 알고리즘을 포함한 다양한 해싱 및 메시지 다이제스트 알고리즘을 제공합니다.
hashlib
모듈의 생성자와 내장 함수를 사용하려면 다음과 같이 작업 환경으로 가져와야 합니다.
import hashlib
hashlib
모듈은 algorithms_available
및 algorithms_guaranteed
라는 두 개의 상수를 제공합니다. algorithms_available
은 현재 플랫폼에서 구현 가능한 모든 알고리즘의 집합이며, algorithms_guaranteed
는 해당 플랫폼에서 항상 사용 가능한 알고리즘의 집합을 나타냅니다.
따라서 algorithms_guaranteed
는 algorithms_available
의 하위 집합입니다.
Python REPL을 시작하고 hashlib
을 가져온 후, algorithms_available
및 algorithms_guaranteed
상수에 접근해 보겠습니다.
>>> hashlib.algorithms_available
# 출력 결과 {'md5', 'md5-sha1', 'sha3_256', 'shake_128', 'sha384', 'sha512_256', 'sha512', 'md4', 'shake_256', 'whirlpool', 'sha1', 'sha3_512', 'sha3_384', 'sha256', 'ripemd160', 'mdc2', 'sha512_224', 'blake2s', 'blake2b', 'sha3_224', 'sm3', 'sha224'}
>>> hashlib.algorithms_guaranteed
# 출력 결과 {'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384', 'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}
결과를 보면 algorithms_guaranteed
가 실제로 algorithms_available
의 하위 집합임을 확인할 수 있습니다.
Python에서 해시 객체 생성 방법
다음으로 Python에서 해시 객체를 만드는 방법을 알아보겠습니다. 메시지 문자열의 SHA256 해시를 계산하는 방법은 다음과 같습니다.
- 일반적인
new()
생성자 사용 - 알고리즘별 생성자 사용
new()
생성자 사용
먼저 메시지 문자열을 초기화해 보겠습니다.
>>> message = "koreantech.org is awesome!"
해시 객체를 인스턴스화하려면 new()
생성자를 사용하고, 알고리즘 이름을 문자열 형태로 전달하면 됩니다.
>>> sha256_hash = hashlib.new("SHA256")
이제 메시지 문자열을 인수로 사용하여 해시 객체에서 update()
메서드를 호출할 수 있습니다.
>>> sha256_hash.update(message)
하지만 이렇게 하면 해싱 알고리즘이 바이트 문자열에서만 작동하기 때문에 오류가 발생합니다.
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Unicode-objects must be encoded before hashing
문자열을 인코딩하려면 해당 문자열 객체의 encode()
메서드를 호출한 후 update()
메서드 호출에서 사용할 수 있습니다. 그런 다음 hexdigest()
메서드를 호출하여 메시지 문자열에 해당하는 SHA256 해시 값을 얻을 수 있습니다.
sha256_hash.update(message.encode()) sha256_hash.hexdigest() # 출력 결과: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
encode()
메서드를 사용하여 메시지 문자열을 인코딩하는 대신, 다음과 같이 문자열 앞에 b
를 붙여 바이트 문자열로 정의할 수도 있습니다.
message = b"koreantech.org is awesome!" sha256_hash.update(message) sha256_hash.hexdigest() # 출력 결과: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
획득한 해시 값은 이전 해시 값과 동일하며, 이는 해시 함수의 결정론적 특성을 입증합니다.
또한 메시지 문자열에 아주 작은 변화만 주어도 해시 값이 크게 변경됩니다 (이러한 현상을 “눈사태 효과”라고도 합니다). 이를 확인하기 위해 ‘awesome’의 ‘a’를 ‘A’로 변경하고 해시 값을 계산해 보겠습니다.
message = "koreantech.org is Awesome!" h1 = hashlib.new("SHA256") h1.update(message.encode()) h1.hexdigest() # 출력 결과: '3c67f334cc598912dc66464f77acb71d88cfd6c8cba8e64a7b749d093c1a53ab'
해시 값이 완전히 변경된 것을 확인할 수 있습니다.
알고리즘별 생성자 사용
이전 예시에서는 일반적인 new()
생성자를 사용하고, 해시 객체를 생성하는 데 사용할 알고리즘 이름으로 “SHA256″을 전달했습니다.
이 대신 다음과 같이 sha256()
생성자를 직접 사용할 수도 있습니다.
sha256_hash = hashlib.sha256() message= "koreantech.org is awesome!" sha256_hash.update(message.encode()) sha256_hash.hexdigest() # 출력 결과: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
출력 해시 값은 메시지 문자열 “koreantech.org is awesome!”에 대해 이전에 얻은 해시 값과 동일합니다.
해시 객체의 속성 알아보기
해시 객체에는 몇 가지 유용한 속성이 있습니다.
digest_size
속성은 다이제스트의 크기를 바이트 단위로 나타냅니다. 예를 들어 SHA256 알고리즘은 32바이트에 해당하는 256비트 해시를 반환합니다.block_size
속성은 해싱 알고리즘에 사용되는 블록 크기를 나타냅니다.name
속성은new()
생성자에서 사용할 수 있는 알고리즘의 이름입니다. 해시 객체에 설명이 포함된 이름이 없을 때, 이 속성의 값을 조회하면 도움이 될 수 있습니다.
이전에 생성한 sha256_hash
객체에 대해 이러한 속성들을 확인해 보겠습니다.
>>> sha256_hash.digest_size 32 >>> sha256_hash.block_size 64 >>> sha256_hash.name 'sha256'
다음으로 Python의 hashlib
모듈을 사용하여 몇 가지 흥미로운 해싱 응용 프로그램을 살펴보겠습니다.
해싱의 실제 응용 예시
소프트웨어 및 파일의 무결성 확인
개발자로서 우리는 항상 소프트웨어 패키지를 다운로드하고 설치합니다. 이는 Linux 배포판에서 작업하든, Windows 또는 Mac에서 작업하든 마찬가지입니다.
하지만 소프트웨어 패키지의 일부 미러 사이트는 신뢰하기 어려울 수 있습니다. 따라서 다운로드 링크 옆에서 해시(또는 체크섬)를 찾을 수 있습니다. 다운로드한 소프트웨어의 무결성을 확인하려면 직접 해시를 계산한 후 공식 해시 값과 비교하면 됩니다.
이 방법은 컴퓨터 내의 파일에도 적용할 수 있습니다. 파일 내용에 아주 작은 변화만 주어도 해시 값이 크게 변경되므로, 해시 값을 확인하여 파일이 수정되었는지 여부를 알 수 있습니다.
다음은 간단한 예시입니다. 작업 디렉토리에 ‘my_file.txt’라는 텍스트 파일을 만들고 다음과 같은 내용을 추가해 보겠습니다.
$ cat my_file.txt This is a sample text file. We are going to compute the SHA256 hash of this text file and also check if the file has been modified by recomputing the hash.
그런 다음 파일을 바이너리 읽기 모드(‘rb’)로 열고, 파일 내용을 읽어 SHA256 해시 값을 계산할 수 있습니다.
>>> import hashlib >>> with open("my_file.txt","rb") as file: ... file_contents = file.read() ... sha256_hash = hashlib.sha256() ... sha256_hash.update(file_contents) ... original_hash = sha256_hash.hexdigest()
여기서 original_hash
변수는 현재 상태의 ‘my_file.txt’ 파일에 대한 해시 값입니다.
>>> original_hash # 출력 결과: '53bfd0551dc06c4515069d1f0dc715d002d451c8799add29f3e5b7328fda9f8f'
이제 ‘my_file.txt’ 파일을 수정해 보겠습니다. 예를 들어 ‘going’ 단어 앞에 있는 여분의 공백 하나를 제거할 수 있습니다.🙂
해시를 다시 계산하고 computed_hash
변수에 저장합니다.
>>> import hashlib >>> with open("my_file.txt","rb") as file: ... file_contents = file.read() ... sha256_hash = hashlib.sha256() ... sha256_hash.update(file_contents) ... computed_hash = sha256_hash.hexdigest()
그런 다음 computed_hash
가 original_hash
와 같은지 확인하는 간단한 assert
문을 추가할 수 있습니다.
>>> assert computed_hash == original_hash
파일이 수정되면 (이 경우에는 참이므로) AssertionError
가 발생해야 합니다.
Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
해싱은 데이터베이스에 비밀번호와 같은 중요한 정보를 저장할 때도 사용할 수 있습니다. 또한 데이터베이스에 연결할 때, 입력한 비밀번호의 해시 값을 올바른 비밀번호의 해시 값과 비교하는 방식으로 비밀번호 인증에도 사용할 수 있습니다.
결론
본 튜토리얼이 Python을 사용하여 보안 해시를 생성하는 방법을 배우는 데 도움이 되었기를 바랍니다. 주요 내용을 정리하면 다음과 같습니다.
- Python의
hashlib
모듈은 다양한 해싱 알고리즘을 바로 사용할 수 있도록 구현해 놓았습니다.hashlib.algorithms_guaranteed
를 사용하여 현재 플랫폼에서 보장되는 알고리즘 목록을 얻을 수 있습니다. - 해시 객체를 생성하려면
hashlib.new(“algo-name”)
구문과 함께 일반new()
생성자를 사용할 수 있습니다. 또는 SHA256 해시에 대해hashlib.sha256()
과 같이 특정 해싱 알고리즘에 해당하는 생성자를 사용할 수 있습니다. - 해시할 메시지 문자열과 해시 객체를 초기화한 후, 해시 객체에서
update()
메서드를 호출한 다음hexdigest()
메서드를 호출하여 해시 값을 가져올 수 있습니다. - 해싱은 소프트웨어 아티팩트 및 파일의 무결성을 확인하거나 민감한 정보를 데이터베이스에 저장하는 등의 작업에 유용하게 활용할 수 있습니다.
다음으로는 Python에서 임의 비밀번호 생성기를 코딩하는 방법에 대해 알아보겠습니다.