A Python implementation of the Minisign signature system.
Installation
With uv:
uv add py-minisign
With pip:
python3 -m pip install py-minisign
Verify a signature
import minisign
public_key = minisign.PublicKey.from_base64(
"RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"
)
signature = minisign.Signature.from_bytes(
b"untrusted comment: signature from minisign secret key\n"
b"RWQf6LRCGA9i59SLOFxz6NxvASXDJeRtuZykwQepbDEGt87ig1BNpWaVWuNrm73YiIiJbq71Wi+dP9eKL8OC351vwIasSSbXxwA=\n"
b"trusted comment: timestamp:1555779966\tfile:test\n"
b"QtKMXWyYcwdpZAlPF7tE2ENJkRd1ujvKjlj1m9RtHTBnZPa5WKU5uWRs5GoP5M/VqE81QFuMKI5k/SfNQUaOAA=="
)
public_key.verify(b"test", signature)
verify() raises VerifyError if the signature is invalid.
Sign data
Secret keys loaded from disk are usually encrypted. Decrypt the key before signing and wipe its mutable secret buffers when it is no longer needed:
import minisign
with minisign.SecretKey.from_file("/path/to/minisign.key") as secret_key:
secret_key.decrypt("strong password")
signature = secret_key.sign(
b"very important data",
trusted_comment="release 1.0",
)
print(bytes(signature).decode())
The default signing mode uses a BLAKE2b prehash. Pass prehash=False when a legacy Ed signature is required.
Generate and store a key pair
KeyPair.generate() creates an unencrypted key using KDF_NONE. Calling encrypt() upgrades it to scrypt and encrypts the secret key before it is serialized:
import os
import minisign
key_pair = minisign.KeyPair.generate()
with key_pair.secret_key as secret_key:
secret_key.encrypt("strong password")
with open(
os.open(
"/path/to/minisign.key",
os.O_CREAT | os.O_EXCL | os.O_WRONLY,
0o600,
),
"wb",
) as file:
file.write(bytes(secret_key) + b"\n")
with open("/path/to/minisign.pub", "wb") as file:
file.write(bytes(key_pair.public_key) + b"\n")
encrypt() accepts custom scrypt operation and memory limits. These values are stored in the secret-key format, and the concrete N, r and p parameters are derived from them in the same way as in Minisign:
key_pair = minisign.KeyPair.generate()
key_pair.secret_key.encrypt(
"strong password",
opslimit=2_097_152,
memlimit=67_108_864,
)
Increasing these limits makes password derivation more expensive. Keep the defaults unless the additional cost has been measured for every system that will need to decrypt the key.
Change or remove a password
Decrypt and encrypt a secret key again to change its password:
import minisign
secret_key = minisign.SecretKey.from_file("/path/to/minisign.key")
secret_key.decrypt("old password")
secret_key.encrypt("new password")
encrypt() generates a new random salt. The key identifier, Ed25519 key material and public key remain unchanged, so existing signatures remain valid.
Use remove_password() to remove password protection completely:
import minisign
secret_key = minisign.SecretKey.from_file("/path/to/minisign.key")
secret_key.remove_password("current password")
After this operation the key uses KDF_NONE. Serializing it with bytes(secret_key) produces an unencrypted secret key; store it only in a file with appropriately restricted permissions.
To intentionally keep a secret key unencrypted, serialize it without calling encrypt():
key_pair = minisign.KeyPair.generate()
encoded_secret_key = bytes(key_pair.secret_key)
Sign and verify files
import minisign
public_key = minisign.PublicKey.from_file("/path/to/minisign.pub")
with minisign.SecretKey.from_file("/path/to/minisign.key") as secret_key:
secret_key.decrypt("strong password")
secret_key.sign_file(
"archive.tar.gz",
prehash=True,
drop_signature=True,
)
# Reads archive.tar.gz.minisig automatically.
public_key.verify_file("archive.tar.gz")
Comments
Comment properties contain only their values. They do not include the untrusted comment: or trusted comment: prefixes; serialization adds these prefixes automatically.
Untrusted comments are not authenticated and may be changed without invalidating a signature. Trusted comments are covered by the global signature.
Memory wiping
SecretKey.wipe() overwrites the mutable secret-key buffers and prevents the key object from being used again. Using SecretKey as a context manager calls wipe() automatically, including when the block exits with an exception.
Development
Install the project and development dependencies:
make sync
Run the test suite and static checks:
make format
make check
Build the source distribution and wheel:
make build
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_minisign-0.20.0.tar.gz.
File metadata
- Download URL: py_minisign-0.20.0.tar.gz
- Upload date:
- Size: 9.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf1690da051dcb1da0890b041d6d8056821130877d77619af4b26b0b5c70b1f2
|
|
| MD5 |
88ebf33860ceb296d6ca2ceeb039892a
|
|
| BLAKE2b-256 |
9889a2f7ee51c0df3593e47b6c321d11229c066ce8cd1433779b677ff098f252
|
Provenance
The following attestation bundles were made for py_minisign-0.20.0.tar.gz:
Publisher:
publish.yml on x13a/py-minisign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_minisign-0.20.0.tar.gz -
Subject digest:
cf1690da051dcb1da0890b041d6d8056821130877d77619af4b26b0b5c70b1f2 - Sigstore transparency entry: 2394513585
- Sigstore integration time:
-
Permalink:
x13a/py-minisign@561c9f9d9846fc2ab7dc73806a5481254ed2d4e3 -
Branch / Tag:
refs/tags/v0.20.0 - Owner: https://github.com/x13a
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@561c9f9d9846fc2ab7dc73806a5481254ed2d4e3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file py_minisign-0.20.0-py3-none-any.whl.
File metadata
- Download URL: py_minisign-0.20.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44ad9a9f21743cd3f6a600b42034fb12db3ed60289b5739a09e36e17b8869d67
|
|
| MD5 |
3e24b05b1a60818b08f881901f34558f
|
|
| BLAKE2b-256 |
225676fa323d66572947ad5ca670e761c3a651f858ca013e865e49af0b0bc486
|
Provenance
The following attestation bundles were made for py_minisign-0.20.0-py3-none-any.whl:
Publisher:
publish.yml on x13a/py-minisign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_minisign-0.20.0-py3-none-any.whl -
Subject digest:
44ad9a9f21743cd3f6a600b42034fb12db3ed60289b5739a09e36e17b8869d67 - Sigstore transparency entry: 2394513620
- Sigstore integration time:
-
Permalink:
x13a/py-minisign@561c9f9d9846fc2ab7dc73806a5481254ed2d4e3 -
Branch / Tag:
refs/tags/v0.20.0 - Owner: https://github.com/x13a
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@561c9f9d9846fc2ab7dc73806a5481254ed2d4e3 -
Trigger Event:
push
-
Statement type: