mirror of
https://codeberg.org/privacy1st/paper-secret
synced 2024-12-22 23:56:05 +01:00
init
This commit is contained in:
commit
125f5c968f
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/venv
|
||||
/.idea
|
||||
**/__pycache__
|
||||
/build
|
||||
/dist
|
||||
**/*.egg-info
|
24
.run/depaper.run.xml
Normal file
24
.run/depaper.run.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="depaper" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
||||
<module name="paper-secret" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="$USER_HOME$/venv-310/bin/python" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/paper_secret/depaper.py" />
|
||||
<option name="PARAMETERS" value="secret.txt.split-text.txt" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
24
.run/enpaper.run.xml
Normal file
24
.run/enpaper.run.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="enpaper" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
|
||||
<module name="paper-secret" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="$USER_HOME$/venv-310/bin/python" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/paper_secret/enpaper.py" />
|
||||
<option name="PARAMETERS" value="secret.txt" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
121
LICENSE
Normal file
121
LICENSE
Normal file
@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
58
README.md
Normal file
58
README.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Paper-Secret
|
||||
|
||||
Shamir Secret Sharing on paper using gfshare.
|
||||
|
||||
## Installation
|
||||
|
||||
`gfshare` is required to split and merge the secret.
|
||||
See `man gfshare` for an explanation of Shamir Secret Sharing in gf(2**8).
|
||||
|
||||
```shell
|
||||
sudo pacman -S --needed libgfshare
|
||||
```
|
||||
|
||||
`qrencode` and `imagemagick` (`convert`) are required to create and merge QR-codes during the split process.
|
||||
One can set the according parameters of `split_encode` to `False` to skip this step.
|
||||
|
||||
```shell
|
||||
sudo pacman -S --needed qrencode imagemagick
|
||||
```
|
||||
|
||||
`enscript` is required to create a PDF containing the split secret in text form.
|
||||
One can set the according parameters of `split_encode` to `False` to skip this step.
|
||||
|
||||
```shell
|
||||
sudo pacman -S --needed enscript
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Create a secret:
|
||||
|
||||
```shell
|
||||
cat > secret.txt
|
||||
```
|
||||
|
||||
Split the secret into 5 lines:
|
||||
|
||||
```shell
|
||||
./enpaper.py secret.txt
|
||||
```
|
||||
|
||||
Manually delete up to 2 of the 5 lines in `secret.txt_txt.txt`.
|
||||
|
||||
Then recreate the secret:
|
||||
|
||||
```shell
|
||||
./depaper.py secret.txt.split-text.txt
|
||||
```
|
||||
|
||||
Print the secret:
|
||||
|
||||
```shell
|
||||
cat secret.txt.split-text.txt.merged.txt
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
* https://en.wikipedia.org/Shamir's_Secret_Sharing
|
8
pyproject.toml
Normal file
8
pyproject.toml
Normal file
@ -0,0 +1,8 @@
|
||||
# https://packaging.python.org/tutorials/packaging-projects/#creating-pyproject-toml
|
||||
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
0
requirements.txt
Normal file
0
requirements.txt
Normal file
36
setup.cfg
Normal file
36
setup.cfg
Normal file
@ -0,0 +1,36 @@
|
||||
; setup.cfg is the configuration file for setuptools.
|
||||
; https://packaging.python.org/tutorials/packaging-projects/#configuring-metadata
|
||||
|
||||
[metadata]
|
||||
name = paper-secret
|
||||
version = 0.1.0
|
||||
author = Daniel Langbein
|
||||
author_email = daniel@systemli.org
|
||||
description = Shamir Secret Sharing on paper using gfshare
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://codeberg.org/privacy1st/paper-secret
|
||||
project_urls =
|
||||
Bug Tracker = https://codeberg.org/privacy1st/paper-secret/issues
|
||||
|
||||
; https://pypi.org/classifiers/
|
||||
classifiers =
|
||||
Development Status :: 4 - Beta
|
||||
Programming Language :: Python :: 3
|
||||
License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
Operating System :: Unix
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= src
|
||||
packages = find:
|
||||
python_requires = >=3.10
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
||||
[options.entry_points]
|
||||
; https://setuptools.readthedocs.io/en/latest/userguide/entry_point.html
|
||||
console_scripts=
|
||||
enpaper = paper_secret.enpaper:main
|
||||
depaper = paper_secret.depaper:main
|
0
src/paper_secret/__init__.py
Normal file
0
src/paper_secret/__init__.py
Normal file
56
src/paper_secret/depaper.py
Executable file
56
src/paper_secret/depaper.py
Executable file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import paper_secret.util as util
|
||||
|
||||
|
||||
def main():
|
||||
assert len(sys.argv) == 2, 'Expected one argument'
|
||||
merge_decode(Path(sys.argv[1]))
|
||||
|
||||
|
||||
def merge_decode(src: Path) -> Path:
|
||||
"""
|
||||
:param src: A file with at least k non-empty lines.
|
||||
:return: Path to file containing reconstructed secret.
|
||||
"""
|
||||
assert src.exists(), str(src)
|
||||
assert src.is_file(), str(src)
|
||||
|
||||
content = src.read_text('UTF-8')
|
||||
# All non-empty lines
|
||||
lines = [line.strip() for line in content.splitlines()
|
||||
if len(line.strip()) > 0]
|
||||
assert len(lines) >= 2
|
||||
|
||||
merged = src.parent.joinpath(src.name + '.merged.txt')
|
||||
assert not merged.exists()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
tmpdir = Path(tmpdirname)
|
||||
|
||||
parts = []
|
||||
for line in lines:
|
||||
assert len(line) >= 4
|
||||
|
||||
encoded_suffix: str
|
||||
encoded_part: str
|
||||
encoded_suffix, encoded_part = line[0:3], line[3:]
|
||||
|
||||
binary_part = base64.b64decode(encoded_part.encode('UTF-8'))
|
||||
|
||||
part = tmpdir.joinpath('.' + encoded_suffix)
|
||||
part.write_bytes(binary_part)
|
||||
parts.append(part)
|
||||
|
||||
command = ['gfcombine', '-o', str(merged)] + [str(part) for part in parts]
|
||||
util.execute_stdin_capture(command)
|
||||
|
||||
return merged
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
92
src/paper_secret/enpaper.py
Executable file
92
src/paper_secret/enpaper.py
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import paper_secret.util as util
|
||||
|
||||
|
||||
def main():
|
||||
assert len(sys.argv) == 2, 'Expected one argument'
|
||||
split_encode(Path(sys.argv[1]))
|
||||
|
||||
|
||||
def split_encode(
|
||||
secret: Path,
|
||||
k: int = 3,
|
||||
n: int = 5,
|
||||
create_qr_codes: bool = True,
|
||||
merge_qr_codes: bool = True,
|
||||
create_text_pdf: bool = True
|
||||
) -> list[Path]:
|
||||
"""
|
||||
Creates a file ending in `_txt.txt`. This file consists of n lines.
|
||||
k of these n lines are enough to reconstruct the secret.
|
||||
|
||||
The same content is also stored in a `.pdf` file.
|
||||
The lines are split up into n blocks of grouped lines.
|
||||
k of these n blocks are enough to reconstruct the secret.
|
||||
|
||||
Each line is also available as QR-code inside a `.pdf` file.
|
||||
|
||||
:param secret: File containing a secret.
|
||||
:param k: Threshold for recombination.
|
||||
:param n: Number of shares to generate
|
||||
:return: List of created files.
|
||||
"""
|
||||
assert secret.exists(), str(secret)
|
||||
assert secret.is_file(), str(secret)
|
||||
|
||||
txt_file = secret.parent.joinpath(secret.name + '.split-text.txt')
|
||||
qr_pdf = secret.parent.joinpath(secret.name + '.split-QR.pdf')
|
||||
txt_pdf = secret.parent.joinpath(secret.name + '.split-text.pdf')
|
||||
assert not txt_file.exists() and not qr_pdf.exists() and not txt_pdf.exists()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
tmpdir = Path(tmpdirname)
|
||||
tmpdirfile = tmpdir.joinpath('_')
|
||||
|
||||
# Creates n files _.xxx
|
||||
# One can reconstruct secret based on k out of n files
|
||||
util.execute_stdin_capture(['gfsplit', '-n', str(k), '-m', str(n), str(secret), str(tmpdirfile)])
|
||||
|
||||
qrcodes = []
|
||||
lines = ''
|
||||
for part in tmpdir.iterdir():
|
||||
binary_part = part.read_bytes()
|
||||
encoded_part = base64.b64encode(binary_part).decode('UTF-8')
|
||||
encoded_suffix = part.suffix[1:]
|
||||
|
||||
suffix_and_content = encoded_suffix + encoded_part
|
||||
lines = lines + suffix_and_content + '\n'
|
||||
|
||||
encoded = part.parent.joinpath(part.name + '.txt')
|
||||
encoded.write_text(suffix_and_content)
|
||||
|
||||
if create_qr_codes:
|
||||
qrcode = part.parent.joinpath(part.name + '.png')
|
||||
qrcodes.append(qrcode)
|
||||
util.execute_stdin_capture(['qrencode', '-o', str(qrcode), '-s', '10'],
|
||||
suffix_and_content.encode('UTF-8'))
|
||||
|
||||
txt_file.write_text(lines)
|
||||
|
||||
if create_text_pdf:
|
||||
command = ['enscript', '-B', '--margins=24:24:', '-o', str(txt_pdf), '-f', 'Courier@12/12']
|
||||
util.execute_stdin_capture(command, lines.replace('\n', '\n\n\n').encode('UTF-8'))
|
||||
|
||||
if create_qr_codes and merge_qr_codes:
|
||||
command = ['convert'] + [str(qrcode) for qrcode in qrcodes] + [str(qr_pdf)]
|
||||
util.execute_stdin_capture(command)
|
||||
|
||||
files = [txt_file]
|
||||
if create_text_pdf:
|
||||
files.append(txt_pdf)
|
||||
if create_qr_codes and merge_qr_codes:
|
||||
files.append(qr_pdf)
|
||||
return files
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
32
src/paper_secret/util.py
Normal file
32
src/paper_secret/util.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def execute_stdin_capture(command: list[str], stdin: bytes = b'', cwd: Path = Path()):
|
||||
"""
|
||||
Executes the given `command`, passes `stdin` into it and returns its stdout.
|
||||
|
||||
:raises Exception: In case of non-zero exit code.
|
||||
This exception includes the exit_code, stdout and stderr in its message.
|
||||
"""
|
||||
|
||||
# If a Linux shell's locale is en_GB.UTF-8, the output will be encoded to UTF-8.
|
||||
encoding = 'UTF-8'
|
||||
assert sys.stdout.encoding.upper() == encoding
|
||||
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
cwd=cwd,
|
||||
input=stdin,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise Exception(
|
||||
f'command: {command}\n'
|
||||
f'exit_code: {completed.returncode}\n'
|
||||
f'stdout: {completed.stdout}\n'
|
||||
f'stderr: {completed.stderr}'
|
||||
)
|
Loading…
Reference in New Issue
Block a user