Skip to content

Instantly share code, notes, and snippets.

@oskar456
Created September 1, 2026 19:44
Show Gist options
  • Select an option

  • Save oskar456/b9f611d9d94709cec7e9f118b7ab1039 to your computer and use it in GitHub Desktop.

Select an option

Save oskar456/b9f611d9d94709cec7e9f118b7ab1039 to your computer and use it in GitHub Desktop.
Convert PEM ACME account key into base64url thumprint of the JWK representation used in http-01 challenge
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "cryptography",
# ]
# ///
import base64
import hashlib
import json
import sys
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def b64url(data: bytes) -> str:
"""Base64URL-encode without padding."""
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def load_key() -> object:
if len(sys.argv) > 1:
with Path(sys.argv[1]).open("rb") as f:
return load_pem_private_key(f.read(), password=None)
return load_pem_private_key(sys.stdin.buffer.read(), password=None)
def main() -> None:
key = load_key()
public = key.public_key()
if isinstance(public, rsa.RSAPublicKey):
numbers = public.public_numbers()
jwk = {
"e": b64url(numbers.e.to_bytes(
(numbers.e.bit_length() + 7) // 8, "big"
)),
"kty": "RSA",
"n": b64url(numbers.n.to_bytes(
(numbers.n.bit_length() + 7) // 8, "big"
)),
}
elif isinstance(public, ec.EllipticCurvePublicKey):
numbers = public.public_numbers()
curves = {
"secp256r1": "P-256",
"secp384r1": "P-384",
"secp521r1": "P-521",
}
try:
curve = curves[public.curve.name]
except KeyError:
raise ValueError(
f"Unsupported EC curve: {public.curve.name}"
) from None
coordinate_size = (public.curve.key_size + 7) // 8
jwk = {
"crv": curve,
"kty": "EC",
"x": b64url(numbers.x.to_bytes(coordinate_size, "big")),
"y": b64url(numbers.y.to_bytes(coordinate_size, "big")),
}
else:
raise ValueError(f"Unsupported key type: {type(public).__name__}")
# RFC 7638 canonical JWK representation:
# lexicographically sorted member names, no whitespace.
canonical_jwk = json.dumps(
jwk,
sort_keys=True,
separators=(",", ":"),
)
thumbprint = b64url(
hashlib.sha256(canonical_jwk.encode("utf-8")).digest()
)
print(thumbprint)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment