hashlib.sha256(text.encode()).hexdigest() returns the SHA-256 of a string as 64 hex characters. .encode() gives the text’s UTF-8 bytes, which is what every other tool hashes.
import hashlib
hashlib.sha256("hello".encode()).hexdigest()
# '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
The tool below shows the same digest for hello in its SHA-256 row.
Strings must be encoded
hashlib hashes bytes. A str is refused:
hashlib.sha256("hello")
# TypeError: Strings must be encoded before hashing
Encode with UTF-8, which is what .encode() does unless you name another codec. A string from input() or a file read in text mode is already a str; a line read with readline() keeps its newline, and that changes everything:
hashlib.sha256("hello\n".encode()).hexdigest()
# '5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03'
hexdigest or digest
.hexdigest() is the 64-character hex text that checksum files and APIs show. .digest() is the raw 32 bytes, for when the hash goes on to another step, such as Base64:
import base64
hashlib.sha256(b"hello").digest()
# b',\xf2M\xba_\xb0\xa3\x0e&\xe8;*\xc5\xb9\xe2\x9e\x1b\x16\x1e\\\x1f\xa7B^s\x043b\x93\x8b\x98$'
base64.b64encode(hashlib.sha256(b"hello").digest()).decode()
# 'LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ='
Calling .update() more than once hashes the pieces as one stream: update(b"hel") then update(b"lo") gives the same 2cf24dba… as hello.
Files
Open the file in binary mode and feed it in chunks, so a 4 GB image never sits in memory. Python 3.11 and later do this for you:
with open("ubuntu.iso", "rb") as f:
print(hashlib.file_digest(f, "sha256").hexdigest())
On older versions, loop:
def sha256_file(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
A file holding hello and nothing else gives 2cf24dba… with either. Text mode would hand you a str and turn Windows line endings into \n, so the bytes hashed wouldn’t be the file’s.
Keys and passwords
A hash with a secret mixed in is an HMAC, not sha256(secret + message), which SHA-256’s length-extension property makes forgeable:
import hmac
hmac.new(b"secret", b"hello", hashlib.sha256).hexdigest()
# '88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b'
Don’t store passwords as SHA-256. It’s fast by design, so leaked hashes can be guessed in bulk; use a slow, salted function such as hashlib.scrypt, bcrypt or Argon2.