transcrypto 1.1.1__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transcrypto/aes.py +4 -3
- transcrypto/base.py +84 -30
- transcrypto/dsa.py +153 -19
- transcrypto/elgamal.py +224 -28
- transcrypto/rsa.py +203 -19
- transcrypto/sss.py +159 -20
- transcrypto/transcrypto.py +401 -178
- {transcrypto-1.1.1.dist-info → transcrypto-1.2.0.dist-info}/METADATA +684 -426
- transcrypto-1.2.0.dist-info/RECORD +15 -0
- transcrypto-1.1.1.dist-info/RECORD +0 -15
- {transcrypto-1.1.1.dist-info → transcrypto-1.2.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.1.1.dist-info → transcrypto-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {transcrypto-1.1.1.dist-info → transcrypto-1.2.0.dist-info}/top_level.txt +0 -0
transcrypto/transcrypto.py
CHANGED
|
@@ -12,11 +12,11 @@ isprime, primegen, mersenne
|
|
|
12
12
|
gcd, xgcd, and grouped mod inv|div|exp|poly|lagrange|crt
|
|
13
13
|
random bits|int|bytes|prime, hash sha256|sha512|file
|
|
14
14
|
aes key frompass, aes encrypt|decrypt (GCM), aes ecb encrypt|decrypt
|
|
15
|
-
rsa new|encrypt|decrypt|sign|verify
|
|
16
|
-
elgamal shared|new|encrypt|decrypt|sign|verify
|
|
17
|
-
dsa shared|new|sign|verify
|
|
15
|
+
rsa new|encrypt|decrypt|sign|verify|rawencrypt|rawdecrypt|rawsign|rawverify
|
|
16
|
+
elgamal shared|new|encrypt|decrypt|sign|verify|rawencrypt|rawdecrypt|rawsign|rawverify
|
|
17
|
+
dsa shared|new|sign|verify|rawsign|rawverify
|
|
18
18
|
bid new|verify
|
|
19
|
-
sss new|shares|recover|
|
|
19
|
+
sss new|shares|recover|rawshares|rawrecover|rawverify
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
@@ -36,6 +36,9 @@ __version__: str = base.__version__ # version comes from base!
|
|
|
36
36
|
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
_NULL_AES_KEY = aes.AESKey(key256=b'\x00' * 32)
|
|
40
|
+
|
|
41
|
+
|
|
39
42
|
def _ParseInt(s: str, /) -> int:
|
|
40
43
|
"""Parse int, try to determine if binary, octal, decimal, or hexadecimal."""
|
|
41
44
|
s = s.strip().lower().replace('_', '')
|
|
@@ -248,9 +251,9 @@ def _GenerateCLIMarkdown() -> str: # pylint: disable=too-many-locals
|
|
|
248
251
|
top_subs: list[argparse.Action] = [a for a in parser._actions if _ActionIsSubparser(a)] # type: ignore[attr-defined] # pylint: disable=protected-access
|
|
249
252
|
for action in top_subs:
|
|
250
253
|
for name, sp in action.choices.items(): # type: ignore[union-attr]
|
|
251
|
-
help_text: str = (sp.description or sp.format_usage().splitlines()
|
|
252
|
-
short: str = (sp.help if hasattr(sp, 'help') else '') or ''
|
|
253
|
-
help_text = short or help_text
|
|
254
|
+
help_text: str = (sp.description or ' '.join(i.strip() for i in sp.format_usage().splitlines())).strip() # type:ignore
|
|
255
|
+
short: str = (sp.help if hasattr(sp, 'help') else '') or '' # type:ignore
|
|
256
|
+
help_text = short or help_text # type:ignore
|
|
254
257
|
help_text = help_text.replace('usage: ', '').strip() # type:ignore
|
|
255
258
|
lines.append(f'- **`{name}`** — `{help_text}`')
|
|
256
259
|
lines.append('')
|
|
@@ -328,30 +331,30 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
328
331
|
' poetry run transcrypto aes ecb -k "<b64key>" decrypt "<128bithexblock>"\n\n' # cspell:disable-line
|
|
329
332
|
' # --- RSA ---\n'
|
|
330
333
|
' poetry run transcrypto -p rsa-key rsa new --bits 2048\n'
|
|
331
|
-
' poetry run transcrypto -p rsa-key.pub rsa
|
|
332
|
-
' poetry run transcrypto -p rsa-key.priv rsa
|
|
333
|
-
' poetry run transcrypto -p rsa-key.priv rsa
|
|
334
|
-
' poetry run transcrypto -p rsa-key.pub rsa
|
|
334
|
+
' poetry run transcrypto -p rsa-key.pub rsa rawencrypt <plaintext>\n'
|
|
335
|
+
' poetry run transcrypto -p rsa-key.priv rsa rawdecrypt <ciphertext>\n'
|
|
336
|
+
' poetry run transcrypto -p rsa-key.priv rsa rawsign <message>\n'
|
|
337
|
+
' poetry run transcrypto -p rsa-key.pub rsa rawverify <message> <signature>\n\n'
|
|
335
338
|
' # --- ElGamal ---\n'
|
|
336
339
|
' poetry run transcrypto -p eg-key elgamal shared --bits 2048\n'
|
|
337
340
|
' poetry run transcrypto -p eg-key elgamal new\n'
|
|
338
|
-
' poetry run transcrypto -p eg-key.pub elgamal
|
|
339
|
-
' poetry run transcrypto -p eg-key.priv elgamal
|
|
340
|
-
' poetry run transcrypto -p eg-key.priv elgamal
|
|
341
|
-
' poetry run transcrypto-p eg-key.pub elgamal
|
|
341
|
+
' poetry run transcrypto -p eg-key.pub elgamal rawencrypt <plaintext>\n'
|
|
342
|
+
' poetry run transcrypto -p eg-key.priv elgamal rawdecrypt <c1:c2>\n'
|
|
343
|
+
' poetry run transcrypto -p eg-key.priv elgamal rawsign <message>\n'
|
|
344
|
+
' poetry run transcrypto-p eg-key.pub elgamal rawverify <message> <s1:s2>\n\n'
|
|
342
345
|
' # --- DSA ---\n'
|
|
343
346
|
' poetry run transcrypto -p dsa-key dsa shared --p-bits 2048 --q-bits 256\n'
|
|
344
347
|
' poetry run transcrypto -p dsa-key dsa new\n'
|
|
345
|
-
' poetry run transcrypto -p dsa-key.priv dsa
|
|
346
|
-
' poetry run transcrypto -p dsa-key.pub dsa
|
|
348
|
+
' poetry run transcrypto -p dsa-key.priv dsa rawsign <message>\n'
|
|
349
|
+
' poetry run transcrypto -p dsa-key.pub dsa rawverify <message> <s1:s2>\n\n'
|
|
347
350
|
' # --- Public Bid ---\n'
|
|
348
351
|
' poetry run transcrypto --bin bid new "tomorrow it will rain"\n'
|
|
349
352
|
' poetry run transcrypto --out-bin bid verify\n\n'
|
|
350
353
|
' # --- Shamir Secret Sharing (SSS) ---\n'
|
|
351
354
|
' poetry run transcrypto -p sss-key sss new 3 --bits 1024\n'
|
|
352
|
-
' poetry run transcrypto -p sss-key sss
|
|
353
|
-
' poetry run transcrypto -p sss-key sss
|
|
354
|
-
' poetry run transcrypto -p sss-key sss
|
|
355
|
+
' poetry run transcrypto -p sss-key sss rawshares <secret> 5\n'
|
|
356
|
+
' poetry run transcrypto -p sss-key sss rawrecover\n'
|
|
357
|
+
' poetry run transcrypto -p sss-key sss rawverify <secret>'
|
|
355
358
|
),
|
|
356
359
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
357
360
|
sub = parser.add_subparsers(dest='command')
|
|
@@ -676,9 +679,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
676
679
|
# RSA group
|
|
677
680
|
p_rsa: argparse.ArgumentParser = sub.add_parser(
|
|
678
681
|
'rsa',
|
|
679
|
-
help=('
|
|
680
|
-
'(BEWARE: no OAEP/PSS padding or validation). '
|
|
681
|
-
'These are pedagogical/raw primitives; do not use for new protocols. '
|
|
682
|
+
help=('RSA (Rivest-Shamir-Adleman) asymmetric cryptography. '
|
|
682
683
|
'No measures are taken here to prevent timing attacks. '
|
|
683
684
|
'All methods require file key(s) as `-p`/`--key-path` (see provided examples).'))
|
|
684
685
|
rsa_sub = p_rsa.add_subparsers(dest='rsa_command')
|
|
@@ -694,49 +695,90 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
694
695
|
p_rsa_new.add_argument(
|
|
695
696
|
'--bits', type=int, default=3332, help='Modulus size in bits; the default is a safe size')
|
|
696
697
|
|
|
697
|
-
# Encrypt
|
|
698
|
-
|
|
699
|
-
'
|
|
700
|
-
help='
|
|
701
|
-
|
|
702
|
-
|
|
698
|
+
# Encrypt with public key
|
|
699
|
+
p_rsa_enc_raw: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
700
|
+
'rawencrypt',
|
|
701
|
+
help=('Raw encrypt *integer* `message` with public key '
|
|
702
|
+
'(BEWARE: no OAEP/PSS padding or validation).'),
|
|
703
|
+
epilog='-p rsa-key.pub rsa rawencrypt 999\n6354905961171348600')
|
|
704
|
+
p_rsa_enc_raw.add_argument(
|
|
703
705
|
'message', type=str, help='Integer message to encrypt, 1≤`message`<*modulus*')
|
|
706
|
+
p_rsa_enc_safe: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
707
|
+
'encrypt',
|
|
708
|
+
help='Encrypt `message` with public key.',
|
|
709
|
+
epilog=('--bin --out-b64 -p rsa-key.pub rsa encrypt "abcde" -a "xyz"\n'
|
|
710
|
+
'AO6knI6xwq6TGR…Qy22jiFhXi1eQ=='))
|
|
711
|
+
p_rsa_enc_safe.add_argument('plaintext', type=str, help='Message to encrypt')
|
|
712
|
+
p_rsa_enc_safe.add_argument(
|
|
713
|
+
'-a', '--aad', type=str, default='',
|
|
714
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)')
|
|
704
715
|
|
|
705
|
-
# Decrypt
|
|
706
|
-
|
|
707
|
-
'
|
|
708
|
-
help='
|
|
709
|
-
|
|
710
|
-
|
|
716
|
+
# Decrypt ciphertext with private key
|
|
717
|
+
p_rsa_dec_raw: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
718
|
+
'rawdecrypt',
|
|
719
|
+
help=('Raw decrypt *integer* `ciphertext` with private key '
|
|
720
|
+
'(BEWARE: no OAEP/PSS padding or validation).'),
|
|
721
|
+
epilog='-p rsa-key.priv rsa rawdecrypt 6354905961171348600\n999')
|
|
722
|
+
p_rsa_dec_raw.add_argument(
|
|
711
723
|
'ciphertext', type=str, help='Integer ciphertext to decrypt, 1≤`ciphertext`<*modulus*')
|
|
724
|
+
p_rsa_dec_safe: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
725
|
+
'decrypt',
|
|
726
|
+
help='Decrypt `ciphertext` with private key.',
|
|
727
|
+
epilog=('--b64 --out-bin -p rsa-key.priv rsa decrypt "AO6knI6xwq6TGR…Qy22jiFhXi1eQ==" '
|
|
728
|
+
'-a "eHl6"\nabcde'))
|
|
729
|
+
p_rsa_dec_safe.add_argument('ciphertext', type=str, help='Ciphertext to decrypt')
|
|
730
|
+
p_rsa_dec_safe.add_argument(
|
|
731
|
+
'-a', '--aad', type=str, default='',
|
|
732
|
+
help='Associated data (optional; has to be exactly the same as used during encryption)')
|
|
712
733
|
|
|
713
|
-
# Sign
|
|
714
|
-
|
|
734
|
+
# Sign message with private key
|
|
735
|
+
p_rsa_sig_raw: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
736
|
+
'rawsign',
|
|
737
|
+
help=('Raw sign *integer* `message` with private key '
|
|
738
|
+
'(BEWARE: no OAEP/PSS padding or validation).'),
|
|
739
|
+
epilog='-p rsa-key.priv rsa rawsign 999\n7632909108672871784')
|
|
740
|
+
p_rsa_sig_raw.add_argument(
|
|
741
|
+
'message', type=str, help='Integer message to sign, 1≤`message`<*modulus*')
|
|
742
|
+
p_rsa_sig_safe: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
715
743
|
'sign',
|
|
716
|
-
help='Sign
|
|
717
|
-
epilog='-p rsa-key.priv rsa sign
|
|
718
|
-
|
|
744
|
+
help='Sign `message` with private key.',
|
|
745
|
+
epilog='--bin --out-b64 -p rsa-key.priv rsa sign "xyz"\n91TS7gC6LORiL…6RD23Aejsfxlw==') # cspell:disable-line
|
|
746
|
+
p_rsa_sig_safe.add_argument('message', type=str, help='Message to sign')
|
|
747
|
+
p_rsa_sig_safe.add_argument(
|
|
748
|
+
'-a', '--aad', type=str, default='',
|
|
749
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)')
|
|
719
750
|
|
|
720
|
-
# Verify
|
|
721
|
-
|
|
722
|
-
'
|
|
723
|
-
help='
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
751
|
+
# Verify signature with public key
|
|
752
|
+
p_rsa_ver_raw: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
753
|
+
'rawverify',
|
|
754
|
+
help=('Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
755
|
+
'(BEWARE: no OAEP/PSS padding or validation).'),
|
|
756
|
+
epilog=('-p rsa-key.pub rsa rawverify 999 7632909108672871784\nRSA signature: OK $$ '
|
|
757
|
+
'-p rsa-key.pub rsa rawverify 999 7632909108672871785\nRSA signature: INVALID'))
|
|
758
|
+
p_rsa_ver_raw.add_argument(
|
|
727
759
|
'message', type=str, help='Integer message that was signed earlier, 1≤`message`<*modulus*')
|
|
728
|
-
|
|
760
|
+
p_rsa_ver_raw.add_argument(
|
|
729
761
|
'signature', type=str,
|
|
730
762
|
help='Integer putative signature for `message`, 1≤`signature`<*modulus*')
|
|
763
|
+
p_rsa_ver_safe: argparse.ArgumentParser = rsa_sub.add_parser(
|
|
764
|
+
'verify',
|
|
765
|
+
help='Verify `signature` for `message` with public key.',
|
|
766
|
+
epilog=('--b64 -p rsa-key.pub rsa verify "eHl6" '
|
|
767
|
+
'"91TS7gC6LORiL…6RD23Aejsfxlw=="\nRSA signature: OK $$ ' # cspell:disable-line
|
|
768
|
+
'--b64 -p rsa-key.pub rsa verify "eLl6" '
|
|
769
|
+
'"91TS7gC6LORiL…6RD23Aejsfxlw=="\nRSA signature: INVALID')) # cspell:disable-line
|
|
770
|
+
p_rsa_ver_safe.add_argument('message', type=str, help='Message that was signed earlier')
|
|
771
|
+
p_rsa_ver_safe.add_argument('signature', type=str, help='Putative signature for `message`')
|
|
772
|
+
p_rsa_ver_safe.add_argument(
|
|
773
|
+
'-a', '--aad', type=str, default='',
|
|
774
|
+
help='Associated data (optional; has to be exactly the same as used during signing)')
|
|
731
775
|
|
|
732
776
|
# ========================= ElGamal ==============================================================
|
|
733
777
|
|
|
734
778
|
# ElGamal group
|
|
735
779
|
p_eg: argparse.ArgumentParser = sub.add_parser(
|
|
736
780
|
'elgamal',
|
|
737
|
-
help=('
|
|
738
|
-
'(BEWARE: no ECIES-style KEM/DEM padding or validation). These are '
|
|
739
|
-
'pedagogical/raw primitives; do not use for new protocols. '
|
|
781
|
+
help=('El-Gamal asymmetric cryptography. '
|
|
740
782
|
'No measures are taken here to prevent timing attacks. '
|
|
741
783
|
'All methods require file key(s) as `-p`/`--key-path` (see provided examples).'))
|
|
742
784
|
eg_sub = p_eg.add_subparsers(dest='eg_command')
|
|
@@ -761,54 +803,96 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
761
803
|
help='Generate an individual El-Gamal private/public key pair from a shared key.',
|
|
762
804
|
epilog='-p eg-key elgamal new\nEl-Gamal private/public keys saved to \'eg-key.priv/.pub\'')
|
|
763
805
|
|
|
764
|
-
# Encrypt
|
|
765
|
-
|
|
766
|
-
'
|
|
767
|
-
help='
|
|
768
|
-
|
|
769
|
-
|
|
806
|
+
# Encrypt with public key
|
|
807
|
+
p_eg_enc_raw: argparse.ArgumentParser = eg_sub.add_parser(
|
|
808
|
+
'rawencrypt',
|
|
809
|
+
help=('Raw encrypt *integer* `message` with public key '
|
|
810
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'),
|
|
811
|
+
epilog='-p eg-key.pub elgamal rawencrypt 999\n2948854810728206041:15945988196340032688')
|
|
812
|
+
p_eg_enc_raw.add_argument(
|
|
770
813
|
'message', type=str, help='Integer message to encrypt, 1≤`message`<*modulus*')
|
|
814
|
+
p_eg_enc_safe: argparse.ArgumentParser = eg_sub.add_parser(
|
|
815
|
+
'encrypt',
|
|
816
|
+
help='Encrypt `message` with public key.',
|
|
817
|
+
epilog=(' --bin --out-b64 -p eg-key.pub elgamal encrypt "abcde" -a "xyz"\n'
|
|
818
|
+
'CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==')) # cspell:disable-line
|
|
819
|
+
p_eg_enc_safe.add_argument('plaintext', type=str, help='Message to encrypt')
|
|
820
|
+
p_eg_enc_safe.add_argument(
|
|
821
|
+
'-a', '--aad', type=str, default='',
|
|
822
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)')
|
|
771
823
|
|
|
772
824
|
# Decrypt El-Gamal ciphertext tuple (c1,c2)
|
|
773
|
-
|
|
774
|
-
'
|
|
775
|
-
help='
|
|
776
|
-
|
|
777
|
-
|
|
825
|
+
p_eg_dec_raw: argparse.ArgumentParser = eg_sub.add_parser(
|
|
826
|
+
'rawdecrypt',
|
|
827
|
+
help=('Raw decrypt *integer* `ciphertext` with private key '
|
|
828
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'),
|
|
829
|
+
epilog='-p eg-key.priv elgamal rawdecrypt 2948854810728206041:15945988196340032688\n999')
|
|
830
|
+
p_eg_dec_raw.add_argument(
|
|
778
831
|
'ciphertext', type=str,
|
|
779
832
|
help=('Integer ciphertext to decrypt; expects `c1:c2` format with 2 integers, '
|
|
780
833
|
' 2≤`c1`,`c2`<*modulus*'))
|
|
834
|
+
p_eg_dec_safe: argparse.ArgumentParser = eg_sub.add_parser(
|
|
835
|
+
'decrypt',
|
|
836
|
+
help='Decrypt `ciphertext` with private key.',
|
|
837
|
+
epilog=('--b64 --out-bin -p eg-key.priv elgamal decrypt '
|
|
838
|
+
'"CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==" -a "eHl6"\nabcde')) # cspell:disable-line
|
|
839
|
+
p_eg_dec_safe.add_argument('ciphertext', type=str, help='Ciphertext to decrypt')
|
|
840
|
+
p_eg_dec_safe.add_argument(
|
|
841
|
+
'-a', '--aad', type=str, default='',
|
|
842
|
+
help='Associated data (optional; has to be exactly the same as used during encryption)')
|
|
781
843
|
|
|
782
|
-
# Sign
|
|
783
|
-
|
|
844
|
+
# Sign message with private key
|
|
845
|
+
p_eg_sig_raw: argparse.ArgumentParser = eg_sub.add_parser(
|
|
846
|
+
'rawsign',
|
|
847
|
+
help=('Raw sign *integer* message with private key '
|
|
848
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation). '
|
|
849
|
+
'Output will 2 *integers* in a `s1:s2` format.'),
|
|
850
|
+
epilog='-p eg-key.priv elgamal rawsign 999\n4674885853217269088:14532144906178302633')
|
|
851
|
+
p_eg_sig_raw.add_argument(
|
|
852
|
+
'message', type=str, help='Integer message to sign, 1≤`message`<*modulus*')
|
|
853
|
+
p_eg_sig_safe: argparse.ArgumentParser = eg_sub.add_parser(
|
|
784
854
|
'sign',
|
|
785
|
-
help='Sign
|
|
786
|
-
epilog='-p eg-key.priv elgamal sign
|
|
787
|
-
|
|
855
|
+
help='Sign message with private key.',
|
|
856
|
+
epilog='--bin --out-b64 -p eg-key.priv elgamal sign "xyz"\nXl4hlYK8SHVGw…0fCKJE1XVzA==') # cspell:disable-line
|
|
857
|
+
p_eg_sig_safe.add_argument('message', type=str, help='Message to sign')
|
|
858
|
+
p_eg_sig_safe.add_argument(
|
|
859
|
+
'-a', '--aad', type=str, default='',
|
|
860
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)')
|
|
788
861
|
|
|
789
862
|
# Verify El-Gamal signature (s1,s2)
|
|
790
|
-
|
|
791
|
-
'
|
|
792
|
-
help='
|
|
793
|
-
|
|
863
|
+
p_eg_ver_raw: argparse.ArgumentParser = eg_sub.add_parser(
|
|
864
|
+
'rawverify',
|
|
865
|
+
help=('Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
866
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'),
|
|
867
|
+
epilog=('-p eg-key.pub elgamal rawverify 999 4674885853217269088:14532144906178302633\n'
|
|
794
868
|
'El-Gamal signature: OK $$ '
|
|
795
|
-
'-p eg-key.pub elgamal
|
|
869
|
+
'-p eg-key.pub elgamal rawverify 999 4674885853217269088:14532144906178302632\n'
|
|
796
870
|
'El-Gamal signature: INVALID'))
|
|
797
|
-
|
|
871
|
+
p_eg_ver_raw.add_argument(
|
|
798
872
|
'message', type=str, help='Integer message that was signed earlier, 1≤`message`<*modulus*')
|
|
799
|
-
|
|
873
|
+
p_eg_ver_raw.add_argument(
|
|
800
874
|
'signature', type=str,
|
|
801
875
|
help=('Integer putative signature for `message`; expects `s1:s2` format with 2 integers, '
|
|
802
876
|
' 2≤`s1`,`s2`<*modulus*'))
|
|
877
|
+
p_eg_ver_safe: argparse.ArgumentParser = eg_sub.add_parser(
|
|
878
|
+
'verify',
|
|
879
|
+
help='Verify `signature` for `message` with public key.',
|
|
880
|
+
epilog=('--b64 -p eg-key.pub elgamal verify "eHl6" "Xl4hlYK8SHVGw…0fCKJE1XVzA=="\n' # cspell:disable-line
|
|
881
|
+
'El-Gamal signature: OK $$ '
|
|
882
|
+
'--b64 -p eg-key.pub elgamal verify "eLl6" "Xl4hlYK8SHVGw…0fCKJE1XVzA=="\n' # cspell:disable-line
|
|
883
|
+
'El-Gamal signature: INVALID'))
|
|
884
|
+
p_eg_ver_safe.add_argument('message', type=str, help='Message that was signed earlier')
|
|
885
|
+
p_eg_ver_safe.add_argument('signature', type=str, help='Putative signature for `message`')
|
|
886
|
+
p_eg_ver_safe.add_argument(
|
|
887
|
+
'-a', '--aad', type=str, default='',
|
|
888
|
+
help='Associated data (optional; has to be exactly the same as used during signing)')
|
|
803
889
|
|
|
804
890
|
# ========================= DSA ==================================================================
|
|
805
891
|
|
|
806
892
|
# DSA group
|
|
807
893
|
p_dsa: argparse.ArgumentParser = sub.add_parser(
|
|
808
894
|
'dsa',
|
|
809
|
-
help=('
|
|
810
|
-
'(BEWARE: no ECDSA/EdDSA padding or validation). These are pedagogical/raw '
|
|
811
|
-
'primitives; do not use for new protocols. '
|
|
895
|
+
help=('DSA (Digital Signature Algorithm) asymmetric signing/verifying. '
|
|
812
896
|
'No measures are taken here to prevent timing attacks. '
|
|
813
897
|
'All methods require file key(s) as `-p`/`--key-path` (see provided examples).'))
|
|
814
898
|
dsa_sub = p_dsa.add_subparsers(dest='dsa_command')
|
|
@@ -818,7 +902,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
818
902
|
'shared',
|
|
819
903
|
help=('Generate a shared DSA key with `p-bits`/`q-bits` prime modulus sizes, which is '
|
|
820
904
|
'the first step in key generation. `q-bits` should be larger than the secrets that '
|
|
821
|
-
'will be protected and `p-bits` should be much larger than `q-bits` (e.g.
|
|
905
|
+
'will be protected and `p-bits` should be much larger than `q-bits` (e.g. 4096/544). '
|
|
822
906
|
'The shared key can safely be used by any number of users to generate their '
|
|
823
907
|
'private/public key pairs (with the `new` command). The shared keys are "public". '
|
|
824
908
|
'Requires `-p`/`--key-path` to set the basename for output files.'),
|
|
@@ -826,10 +910,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
826
910
|
'# NEVER use such a small key: example only!\n'
|
|
827
911
|
'DSA shared key saved to \'dsa-key.shared\''))
|
|
828
912
|
p_dsa_shared.add_argument(
|
|
829
|
-
'--p-bits', type=int, default=
|
|
913
|
+
'--p-bits', type=int, default=4096,
|
|
830
914
|
help='Prime modulus (`p`) size in bits; the default is a safe size')
|
|
831
915
|
p_dsa_shared.add_argument(
|
|
832
|
-
'--q-bits', type=int, default=
|
|
916
|
+
'--q-bits', type=int, default=544,
|
|
833
917
|
help=('Prime modulus (`q`) size in bits; the default is a safe size ***IFF*** you '
|
|
834
918
|
'are protecting symmetric keys or regular hashes'))
|
|
835
919
|
|
|
@@ -839,25 +923,48 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
839
923
|
help='Generate an individual DSA private/public key pair from a shared key.',
|
|
840
924
|
epilog='-p dsa-key dsa new\nDSA private/public keys saved to \'dsa-key.priv/.pub\'')
|
|
841
925
|
|
|
842
|
-
# Sign
|
|
843
|
-
|
|
926
|
+
# Sign message with private key
|
|
927
|
+
p_dsa_sign_raw: argparse.ArgumentParser = dsa_sub.add_parser(
|
|
928
|
+
'rawsign',
|
|
929
|
+
help=('Raw sign *integer* message with private key '
|
|
930
|
+
'(BEWARE: no ECDSA/EdDSA padding or validation). '
|
|
931
|
+
'Output will 2 *integers* in a `s1:s2` format.'),
|
|
932
|
+
epilog='-p dsa-key.priv dsa rawsign 999\n2395961484:3435572290')
|
|
933
|
+
p_dsa_sign_raw.add_argument('message', type=str, help='Integer message to sign, 1≤`message`<`q`')
|
|
934
|
+
p_dsa_sign_safe: argparse.ArgumentParser = dsa_sub.add_parser(
|
|
844
935
|
'sign',
|
|
845
|
-
help='Sign
|
|
846
|
-
epilog='-p dsa-key.priv dsa sign
|
|
847
|
-
|
|
936
|
+
help='Sign message with private key.',
|
|
937
|
+
epilog='--bin --out-b64 -p dsa-key.priv dsa sign "xyz"\nyq8InJVpViXh9…BD4par2XuA=')
|
|
938
|
+
p_dsa_sign_safe.add_argument('message', type=str, help='Message to sign')
|
|
939
|
+
p_dsa_sign_safe.add_argument(
|
|
940
|
+
'-a', '--aad', type=str, default='',
|
|
941
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)')
|
|
848
942
|
|
|
849
943
|
# Verify DSA signature (s1,s2)
|
|
850
|
-
|
|
851
|
-
'
|
|
852
|
-
help='
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
944
|
+
p_dsa_verify_raw: argparse.ArgumentParser = dsa_sub.add_parser(
|
|
945
|
+
'rawverify',
|
|
946
|
+
help=('Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
947
|
+
'(BEWARE: no ECDSA/EdDSA padding or validation).'),
|
|
948
|
+
epilog=('-p dsa-key.pub dsa rawverify 999 2395961484:3435572290\nDSA signature: OK $$ '
|
|
949
|
+
'-p dsa-key.pub dsa rawverify 999 2395961484:3435572291\nDSA signature: INVALID'))
|
|
950
|
+
p_dsa_verify_raw.add_argument(
|
|
856
951
|
'message', type=str, help='Integer message that was signed earlier, 1≤`message`<`q`')
|
|
857
|
-
|
|
952
|
+
p_dsa_verify_raw.add_argument(
|
|
858
953
|
'signature', type=str,
|
|
859
954
|
help=('Integer putative signature for `message`; expects `s1:s2` format with 2 integers, '
|
|
860
955
|
' 2≤`s1`,`s2`<`q`'))
|
|
956
|
+
p_dsa_verify_safe: argparse.ArgumentParser = dsa_sub.add_parser(
|
|
957
|
+
'verify',
|
|
958
|
+
help='Verify `signature` for `message` with public key.',
|
|
959
|
+
epilog=('--b64 -p dsa-key.pub dsa verify "eHl6" "yq8InJVpViXh9…BD4par2XuA="\n'
|
|
960
|
+
'DSA signature: OK $$ '
|
|
961
|
+
'--b64 -p dsa-key.pub dsa verify "eLl6" "yq8InJVpViXh9…BD4par2XuA="\n'
|
|
962
|
+
'DSA signature: INVALID'))
|
|
963
|
+
p_dsa_verify_safe.add_argument('message', type=str, help='Message that was signed earlier')
|
|
964
|
+
p_dsa_verify_safe.add_argument('signature', type=str, help='Putative signature for `message`')
|
|
965
|
+
p_dsa_verify_safe.add_argument(
|
|
966
|
+
'-a', '--aad', type=str, default='',
|
|
967
|
+
help='Associated data (optional; has to be exactly the same as used during signing)')
|
|
861
968
|
|
|
862
969
|
# ========================= Public Bid ===========================================================
|
|
863
970
|
|
|
@@ -891,9 +998,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
891
998
|
# SSS group
|
|
892
999
|
p_sss: argparse.ArgumentParser = sub.add_parser(
|
|
893
1000
|
'sss',
|
|
894
|
-
help=('
|
|
895
|
-
'(BEWARE: no modern message wrapping, padding or validation). These are '
|
|
896
|
-
'pedagogical/raw primitives; do not use for new protocols. '
|
|
1001
|
+
help=('SSS (Shamir Shared Secret) secret sharing crypto scheme. '
|
|
897
1002
|
'No measures are taken here to prevent timing attacks. '
|
|
898
1003
|
'All methods require file key(s) as `-p`/`--key-path` (see provided examples).'))
|
|
899
1004
|
sss_sub = p_sss.add_subparsers(dest='sss_command')
|
|
@@ -916,45 +1021,69 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
|
|
|
916
1021
|
'than the size of the secret you want to protect with this scheme'))
|
|
917
1022
|
|
|
918
1023
|
# Issue N shares for a secret
|
|
919
|
-
|
|
920
|
-
'
|
|
921
|
-
help='Issue `count` private shares for an integer `secret
|
|
922
|
-
|
|
1024
|
+
p_sss_shares_raw: argparse.ArgumentParser = sss_sub.add_parser(
|
|
1025
|
+
'rawshares',
|
|
1026
|
+
help=('Raw shares: Issue `count` private shares for an *integer* `secret` '
|
|
1027
|
+
'(BEWARE: no modern message wrapping, padding or validation).'),
|
|
1028
|
+
epilog=('-p sss-key sss rawshares 999 5\n'
|
|
923
1029
|
'SSS 5 individual (private) shares saved to \'sss-key.share.1…5\'\n'
|
|
924
1030
|
'$ rm sss-key.share.2 sss-key.share.4 '
|
|
925
1031
|
'# this is to simulate only having shares 1,3,5'))
|
|
926
|
-
|
|
1032
|
+
p_sss_shares_raw.add_argument(
|
|
927
1033
|
'secret', type=str, help='Integer secret to be protected, 1≤`secret`<*modulus*')
|
|
928
|
-
|
|
1034
|
+
p_sss_shares_raw.add_argument(
|
|
1035
|
+
'count', type=int,
|
|
1036
|
+
help=('How many shares to produce; must be ≥ `minimum` used in `new` command or else the '
|
|
1037
|
+
'`secret` would become unrecoverable'))
|
|
1038
|
+
p_sss_shares_safe: argparse.ArgumentParser = sss_sub.add_parser(
|
|
1039
|
+
'shares',
|
|
1040
|
+
help='Shares: Issue `count` private shares for a `secret`.',
|
|
1041
|
+
epilog=('--bin -p sss-key sss shares "abcde" 5\n'
|
|
1042
|
+
'SSS 5 individual (private) shares saved to \'sss-key.share.1…5\'\n'
|
|
1043
|
+
'$ rm sss-key.share.2 sss-key.share.4 '
|
|
1044
|
+
'# this is to simulate only having shares 1,3,5'))
|
|
1045
|
+
p_sss_shares_safe.add_argument('secret', type=str, help='Secret to be protected')
|
|
1046
|
+
p_sss_shares_safe.add_argument(
|
|
929
1047
|
'count', type=int,
|
|
930
1048
|
help=('How many shares to produce; must be ≥ `minimum` used in `new` command or else the '
|
|
931
1049
|
'`secret` would become unrecoverable'))
|
|
932
1050
|
|
|
933
1051
|
# Recover secret from shares
|
|
1052
|
+
sss_sub.add_parser(
|
|
1053
|
+
'rawrecover',
|
|
1054
|
+
help=('Raw recover *integer* secret from shares; will use any available shares '
|
|
1055
|
+
'that were found (BEWARE: no modern message wrapping, padding or validation).'),
|
|
1056
|
+
epilog=('-p sss-key sss rawrecover\n'
|
|
1057
|
+
'Loaded SSS share: \'sss-key.share.3\'\n'
|
|
1058
|
+
'Loaded SSS share: \'sss-key.share.5\'\n'
|
|
1059
|
+
'Loaded SSS share: \'sss-key.share.1\' '
|
|
1060
|
+
'# using only 3 shares: number 2/4 are missing\n'
|
|
1061
|
+
'Secret:\n999'))
|
|
934
1062
|
sss_sub.add_parser(
|
|
935
1063
|
'recover',
|
|
936
1064
|
help='Recover secret from shares; will use any available shares that were found.',
|
|
937
|
-
epilog=('-p sss-key sss recover\n'
|
|
1065
|
+
epilog=('--out-bin -p sss-key sss recover\n'
|
|
938
1066
|
'Loaded SSS share: \'sss-key.share.3\'\n'
|
|
939
1067
|
'Loaded SSS share: \'sss-key.share.5\'\n'
|
|
940
1068
|
'Loaded SSS share: \'sss-key.share.1\' '
|
|
941
1069
|
'# using only 3 shares: number 2/4 are missing\n'
|
|
942
|
-
'Secret:\
|
|
1070
|
+
'Secret:\nabcde'))
|
|
943
1071
|
|
|
944
1072
|
# Verify a share against a secret
|
|
945
|
-
|
|
946
|
-
'
|
|
947
|
-
help='
|
|
948
|
-
|
|
1073
|
+
p_sss_verify_raw: argparse.ArgumentParser = sss_sub.add_parser(
|
|
1074
|
+
'rawverify',
|
|
1075
|
+
help=('Raw verify shares against a secret (private params; '
|
|
1076
|
+
'BEWARE: no modern message wrapping, padding or validation).'),
|
|
1077
|
+
epilog=('-p sss-key sss rawverify 999\n'
|
|
949
1078
|
'SSS share \'sss-key.share.3\' verification: OK\n'
|
|
950
1079
|
'SSS share \'sss-key.share.5\' verification: OK\n'
|
|
951
1080
|
'SSS share \'sss-key.share.1\' verification: OK $$ '
|
|
952
|
-
'-p sss-key sss
|
|
1081
|
+
'-p sss-key sss rawverify 998\n'
|
|
953
1082
|
'SSS share \'sss-key.share.3\' verification: INVALID\n'
|
|
954
1083
|
'SSS share \'sss-key.share.5\' verification: INVALID\n'
|
|
955
1084
|
'SSS share \'sss-key.share.1\' verification: INVALID'))
|
|
956
|
-
|
|
957
|
-
'secret', type=str, help='Integer secret used to generate the shares
|
|
1085
|
+
p_sss_verify_raw.add_argument(
|
|
1086
|
+
'secret', type=str, help='Integer secret used to generate the shares')
|
|
958
1087
|
|
|
959
1088
|
# ========================= Markdown Generation ==================================================
|
|
960
1089
|
|
|
@@ -977,45 +1106,36 @@ def AESCommand(
|
|
|
977
1106
|
"""Execute `aes` command."""
|
|
978
1107
|
pt: bytes
|
|
979
1108
|
ct: bytes
|
|
1109
|
+
aad: bytes | None = None
|
|
1110
|
+
aes_key: aes.AESKey = _NULL_AES_KEY
|
|
980
1111
|
aes_cmd: str = args.aes_command.lower().strip() if args.aes_command else ''
|
|
1112
|
+
if aes_cmd in ('encrypt', 'decrypt', 'ecb'):
|
|
1113
|
+
if args.key:
|
|
1114
|
+
aes_key = aes.AESKey(key256=_BytesFromText(args.key, in_format))
|
|
1115
|
+
elif args.key_path:
|
|
1116
|
+
aes_key = _LoadObj(args.key_path, args.protect or None, aes.AESKey)
|
|
1117
|
+
else:
|
|
1118
|
+
raise base.InputError('provide -k/--key or -p/--key-path')
|
|
1119
|
+
if aes_cmd != 'ecb':
|
|
1120
|
+
aad = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
981
1121
|
match aes_cmd:
|
|
982
1122
|
case 'key':
|
|
983
|
-
aes_key
|
|
1123
|
+
aes_key = aes.AESKey.FromStaticPassword(args.password)
|
|
984
1124
|
if args.key_path:
|
|
985
1125
|
_SaveObj(aes_key, args.key_path, args.protect or None)
|
|
986
1126
|
print(f'AES key saved to {args.key_path!r}')
|
|
987
1127
|
else:
|
|
988
1128
|
print(_BytesToText(aes_key.key256, out_format))
|
|
989
1129
|
case 'encrypt':
|
|
990
|
-
if args.key:
|
|
991
|
-
aes_key = aes.AESKey(key256=_BytesFromText(args.key, in_format))
|
|
992
|
-
elif args.key_path:
|
|
993
|
-
aes_key = _LoadObj(args.key_path, args.protect or None, aes.AESKey)
|
|
994
|
-
else:
|
|
995
|
-
raise base.InputError('provide -k/--key or -p/--key-path')
|
|
996
|
-
aad: bytes | None = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
997
1130
|
pt = _BytesFromText(args.plaintext, in_format)
|
|
998
1131
|
ct = aes_key.Encrypt(pt, associated_data=aad)
|
|
999
1132
|
print(_BytesToText(ct, out_format))
|
|
1000
1133
|
case 'decrypt':
|
|
1001
|
-
if args.key:
|
|
1002
|
-
aes_key = aes.AESKey(key256=_BytesFromText(args.key, in_format))
|
|
1003
|
-
elif args.key_path:
|
|
1004
|
-
aes_key = _LoadObj(args.key_path, args.protect or None, aes.AESKey)
|
|
1005
|
-
else:
|
|
1006
|
-
raise base.InputError('provide -k/--key or -p/--key-path')
|
|
1007
|
-
aad = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
1008
1134
|
ct = _BytesFromText(args.ciphertext, in_format)
|
|
1009
1135
|
pt = aes_key.Decrypt(ct, associated_data=aad)
|
|
1010
1136
|
print(_BytesToText(pt, out_format))
|
|
1011
1137
|
case 'ecb':
|
|
1012
1138
|
ecb_cmd: str = args.aes_ecb_command.lower().strip() if args.aes_ecb_command else ''
|
|
1013
|
-
if args.key:
|
|
1014
|
-
aes_key = aes.AESKey(key256=_BytesFromText(args.key, in_format))
|
|
1015
|
-
elif args.key_path:
|
|
1016
|
-
aes_key = _LoadObj(args.key_path, args.protect or None, aes.AESKey)
|
|
1017
|
-
else:
|
|
1018
|
-
raise base.InputError('provide -k/--key or -p/--key-path')
|
|
1019
1139
|
match ecb_cmd:
|
|
1020
1140
|
case 'encrypt':
|
|
1021
1141
|
ecb: aes.AESKey.ECBEncoderClass = aes_key.ECBEncoder()
|
|
@@ -1029,48 +1149,85 @@ def AESCommand(
|
|
|
1029
1149
|
raise NotImplementedError()
|
|
1030
1150
|
|
|
1031
1151
|
|
|
1032
|
-
def RSACommand(
|
|
1152
|
+
def RSACommand(
|
|
1153
|
+
args: argparse.Namespace, in_format: _StrBytesType, out_format: _StrBytesType, /) -> None:
|
|
1033
1154
|
"""Execute `rsa` command."""
|
|
1034
1155
|
c: int
|
|
1035
1156
|
m: int
|
|
1157
|
+
pt: bytes
|
|
1158
|
+
ct: bytes
|
|
1159
|
+
aad: bytes | None = None
|
|
1160
|
+
rsa_priv: rsa.RSAPrivateKey
|
|
1161
|
+
rsa_pub: rsa.RSAPublicKey
|
|
1036
1162
|
rsa_cmd: str = args.rsa_command.lower().strip() if args.rsa_command else ''
|
|
1163
|
+
if rsa_cmd in ('encrypt', 'verify', 'decrypt', 'sign'):
|
|
1164
|
+
aad = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
1037
1165
|
match rsa_cmd:
|
|
1038
1166
|
case 'new':
|
|
1039
|
-
rsa_priv
|
|
1040
|
-
rsa_pub
|
|
1167
|
+
rsa_priv = rsa.RSAPrivateKey.New(args.bits)
|
|
1168
|
+
rsa_pub = rsa.RSAPublicKey.Copy(rsa_priv)
|
|
1041
1169
|
_SaveObj(rsa_priv, args.key_path + '.priv', args.protect or None)
|
|
1042
1170
|
_SaveObj(rsa_pub, args.key_path + '.pub', args.protect or None)
|
|
1043
1171
|
print(f'RSA private/public keys saved to {args.key_path + ".priv/.pub"!r}')
|
|
1044
|
-
case '
|
|
1172
|
+
case 'rawencrypt':
|
|
1045
1173
|
rsa_pub = rsa.RSAPublicKey.Copy(
|
|
1046
1174
|
_LoadObj(args.key_path, args.protect or None, rsa.RSAPublicKey))
|
|
1047
1175
|
m = _ParseInt(args.message)
|
|
1048
|
-
print(rsa_pub.
|
|
1049
|
-
case '
|
|
1176
|
+
print(rsa_pub.RawEncrypt(m))
|
|
1177
|
+
case 'rawdecrypt':
|
|
1050
1178
|
rsa_priv = _LoadObj(args.key_path, args.protect or None, rsa.RSAPrivateKey)
|
|
1051
1179
|
c = _ParseInt(args.ciphertext)
|
|
1052
|
-
print(rsa_priv.
|
|
1053
|
-
case '
|
|
1180
|
+
print(rsa_priv.RawDecrypt(c))
|
|
1181
|
+
case 'rawsign':
|
|
1054
1182
|
rsa_priv = _LoadObj(args.key_path, args.protect or None, rsa.RSAPrivateKey)
|
|
1055
1183
|
m = _ParseInt(args.message)
|
|
1056
|
-
print(rsa_priv.
|
|
1057
|
-
case '
|
|
1184
|
+
print(rsa_priv.RawSign(m))
|
|
1185
|
+
case 'rawverify':
|
|
1058
1186
|
rsa_pub = rsa.RSAPublicKey.Copy(
|
|
1059
1187
|
_LoadObj(args.key_path, args.protect or None, rsa.RSAPublicKey))
|
|
1060
1188
|
m = _ParseInt(args.message)
|
|
1061
1189
|
sig: int = _ParseInt(args.signature)
|
|
1062
|
-
print('RSA signature: ' + ('OK' if rsa_pub.
|
|
1190
|
+
print('RSA signature: ' + ('OK' if rsa_pub.RawVerify(m, sig) else 'INVALID'))
|
|
1191
|
+
case 'encrypt':
|
|
1192
|
+
rsa_pub = _LoadObj(args.key_path, args.protect or None, rsa.RSAPublicKey)
|
|
1193
|
+
pt = _BytesFromText(args.plaintext, in_format)
|
|
1194
|
+
ct = rsa_pub.Encrypt(pt, associated_data=aad)
|
|
1195
|
+
print(_BytesToText(ct, out_format))
|
|
1196
|
+
case 'decrypt':
|
|
1197
|
+
rsa_priv = _LoadObj(args.key_path, args.protect or None, rsa.RSAPrivateKey)
|
|
1198
|
+
ct = _BytesFromText(args.ciphertext, in_format)
|
|
1199
|
+
pt = rsa_priv.Decrypt(ct, associated_data=aad)
|
|
1200
|
+
print(_BytesToText(pt, out_format))
|
|
1201
|
+
case 'sign':
|
|
1202
|
+
rsa_priv = _LoadObj(args.key_path, args.protect or None, rsa.RSAPrivateKey)
|
|
1203
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1204
|
+
ct = rsa_priv.Sign(pt, associated_data=aad)
|
|
1205
|
+
print(_BytesToText(ct, out_format))
|
|
1206
|
+
case 'verify':
|
|
1207
|
+
rsa_pub = _LoadObj(args.key_path, args.protect or None, rsa.RSAPublicKey)
|
|
1208
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1209
|
+
ct = _BytesFromText(args.signature, in_format)
|
|
1210
|
+
print('RSA signature: ' +
|
|
1211
|
+
('OK' if rsa_pub.Verify(pt, ct, associated_data=aad) else 'INVALID'))
|
|
1063
1212
|
case _:
|
|
1064
1213
|
raise NotImplementedError()
|
|
1065
1214
|
|
|
1066
1215
|
|
|
1067
|
-
def ElGamalCommand(
|
|
1216
|
+
def ElGamalCommand( # pylint: disable=too-many-statements
|
|
1217
|
+
args: argparse.Namespace, in_format: _StrBytesType, out_format: _StrBytesType, /) -> None:
|
|
1068
1218
|
"""Execute `elgamal` command."""
|
|
1069
1219
|
c1: str
|
|
1070
1220
|
c2: str
|
|
1071
1221
|
m: int
|
|
1072
1222
|
ss: tuple[int, int]
|
|
1223
|
+
pt: bytes
|
|
1224
|
+
ct: bytes
|
|
1225
|
+
aad: bytes | None = None
|
|
1226
|
+
eg_priv: elgamal.ElGamalPrivateKey
|
|
1227
|
+
eg_pub: elgamal.ElGamalPublicKey
|
|
1073
1228
|
eg_cmd: str = args.eg_command.lower().strip() if args.eg_command else ''
|
|
1229
|
+
if eg_cmd in ('encrypt', 'verify', 'decrypt', 'sign'):
|
|
1230
|
+
aad = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
1074
1231
|
match eg_cmd:
|
|
1075
1232
|
case 'shared':
|
|
1076
1233
|
shared_eg: elgamal.ElGamalSharedPublicKey = elgamal.ElGamalSharedPublicKey.NewShared(
|
|
@@ -1078,46 +1235,75 @@ def ElGamalCommand(args: argparse.Namespace, /) -> None:
|
|
|
1078
1235
|
_SaveObj(shared_eg, args.key_path + '.shared', args.protect or None)
|
|
1079
1236
|
print(f'El-Gamal shared key saved to {args.key_path + ".shared"!r}')
|
|
1080
1237
|
case 'new':
|
|
1081
|
-
eg_priv
|
|
1238
|
+
eg_priv = elgamal.ElGamalPrivateKey.New(
|
|
1082
1239
|
_LoadObj(args.key_path + '.shared', args.protect or None, elgamal.ElGamalSharedPublicKey))
|
|
1083
|
-
eg_pub
|
|
1240
|
+
eg_pub = elgamal.ElGamalPublicKey.Copy(eg_priv)
|
|
1084
1241
|
_SaveObj(eg_priv, args.key_path + '.priv', args.protect or None)
|
|
1085
1242
|
_SaveObj(eg_pub, args.key_path + '.pub', args.protect or None)
|
|
1086
1243
|
print(f'El-Gamal private/public keys saved to {args.key_path + ".priv/.pub"!r}')
|
|
1087
|
-
case '
|
|
1244
|
+
case 'rawencrypt':
|
|
1088
1245
|
eg_pub = elgamal.ElGamalPublicKey.Copy(
|
|
1089
1246
|
_LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPublicKey))
|
|
1090
1247
|
m = _ParseInt(args.message)
|
|
1091
|
-
ss = eg_pub.
|
|
1248
|
+
ss = eg_pub.RawEncrypt(m)
|
|
1092
1249
|
print(f'{ss[0]}:{ss[1]}')
|
|
1093
|
-
case '
|
|
1250
|
+
case 'rawdecrypt':
|
|
1094
1251
|
eg_priv = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPrivateKey)
|
|
1095
1252
|
c1, c2 = args.ciphertext.split(':')
|
|
1096
1253
|
ss = (_ParseInt(c1), _ParseInt(c2))
|
|
1097
|
-
print(eg_priv.
|
|
1098
|
-
case '
|
|
1254
|
+
print(eg_priv.RawDecrypt(ss))
|
|
1255
|
+
case 'rawsign':
|
|
1099
1256
|
eg_priv = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPrivateKey)
|
|
1100
1257
|
m = _ParseInt(args.message)
|
|
1101
|
-
ss = eg_priv.
|
|
1258
|
+
ss = eg_priv.RawSign(m)
|
|
1102
1259
|
print(f'{ss[0]}:{ss[1]}')
|
|
1103
|
-
case '
|
|
1260
|
+
case 'rawverify':
|
|
1104
1261
|
eg_pub = elgamal.ElGamalPublicKey.Copy(
|
|
1105
1262
|
_LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPublicKey))
|
|
1106
1263
|
m = _ParseInt(args.message)
|
|
1107
1264
|
c1, c2 = args.signature.split(':')
|
|
1108
1265
|
ss = (_ParseInt(c1), _ParseInt(c2))
|
|
1109
|
-
print('El-Gamal signature: ' + ('OK' if eg_pub.
|
|
1266
|
+
print('El-Gamal signature: ' + ('OK' if eg_pub.RawVerify(m, ss) else 'INVALID'))
|
|
1267
|
+
case 'encrypt':
|
|
1268
|
+
eg_pub = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPublicKey)
|
|
1269
|
+
pt = _BytesFromText(args.plaintext, in_format)
|
|
1270
|
+
ct = eg_pub.Encrypt(pt, associated_data=aad)
|
|
1271
|
+
print(_BytesToText(ct, out_format))
|
|
1272
|
+
case 'decrypt':
|
|
1273
|
+
eg_priv = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPrivateKey)
|
|
1274
|
+
ct = _BytesFromText(args.ciphertext, in_format)
|
|
1275
|
+
pt = eg_priv.Decrypt(ct, associated_data=aad)
|
|
1276
|
+
print(_BytesToText(pt, out_format))
|
|
1277
|
+
case 'sign':
|
|
1278
|
+
eg_priv = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPrivateKey)
|
|
1279
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1280
|
+
ct = eg_priv.Sign(pt, associated_data=aad)
|
|
1281
|
+
print(_BytesToText(ct, out_format))
|
|
1282
|
+
case 'verify':
|
|
1283
|
+
eg_pub = _LoadObj(args.key_path, args.protect or None, elgamal.ElGamalPublicKey)
|
|
1284
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1285
|
+
ct = _BytesFromText(args.signature, in_format)
|
|
1286
|
+
print('El-Gamal signature: ' +
|
|
1287
|
+
('OK' if eg_pub.Verify(pt, ct, associated_data=aad) else 'INVALID'))
|
|
1110
1288
|
case _:
|
|
1111
1289
|
raise NotImplementedError()
|
|
1112
1290
|
|
|
1113
1291
|
|
|
1114
|
-
def DSACommand(
|
|
1292
|
+
def DSACommand(
|
|
1293
|
+
args: argparse.Namespace, in_format: _StrBytesType, out_format: _StrBytesType, /) -> None:
|
|
1115
1294
|
"""Execute `dsa` command."""
|
|
1116
1295
|
c1: str
|
|
1117
1296
|
c2: str
|
|
1118
1297
|
m: int
|
|
1119
1298
|
ss: tuple[int, int]
|
|
1299
|
+
pt: bytes
|
|
1300
|
+
ct: bytes
|
|
1301
|
+
aad: bytes | None = None
|
|
1302
|
+
dsa_priv: dsa.DSAPrivateKey
|
|
1303
|
+
dsa_pub: dsa.DSAPublicKey
|
|
1120
1304
|
dsa_cmd: str = args.dsa_command.lower().strip() if args.dsa_command else ''
|
|
1305
|
+
if dsa_cmd in ('verify', 'sign'):
|
|
1306
|
+
aad = _BytesFromText(args.aad, in_format) if args.aad else None
|
|
1121
1307
|
match dsa_cmd:
|
|
1122
1308
|
case 'shared':
|
|
1123
1309
|
dsa_shared: dsa.DSASharedPublicKey = dsa.DSASharedPublicKey.NewShared(
|
|
@@ -1125,24 +1311,35 @@ def DSACommand(args: argparse.Namespace, /) -> None:
|
|
|
1125
1311
|
_SaveObj(dsa_shared, args.key_path + '.shared', args.protect or None)
|
|
1126
1312
|
print(f'DSA shared key saved to {args.key_path + ".shared"!r}')
|
|
1127
1313
|
case 'new':
|
|
1128
|
-
dsa_priv
|
|
1314
|
+
dsa_priv = dsa.DSAPrivateKey.New(
|
|
1129
1315
|
_LoadObj(args.key_path + '.shared', args.protect or None, dsa.DSASharedPublicKey))
|
|
1130
|
-
dsa_pub
|
|
1316
|
+
dsa_pub = dsa.DSAPublicKey.Copy(dsa_priv)
|
|
1131
1317
|
_SaveObj(dsa_priv, args.key_path + '.priv', args.protect or None)
|
|
1132
1318
|
_SaveObj(dsa_pub, args.key_path + '.pub', args.protect or None)
|
|
1133
1319
|
print(f'DSA private/public keys saved to {args.key_path + ".priv/.pub"!r}')
|
|
1134
|
-
case '
|
|
1320
|
+
case 'rawsign':
|
|
1135
1321
|
dsa_priv = _LoadObj(args.key_path, args.protect or None, dsa.DSAPrivateKey)
|
|
1136
1322
|
m = _ParseInt(args.message) % dsa_priv.prime_seed
|
|
1137
|
-
ss = dsa_priv.
|
|
1323
|
+
ss = dsa_priv.RawSign(m)
|
|
1138
1324
|
print(f'{ss[0]}:{ss[1]}')
|
|
1139
|
-
case '
|
|
1325
|
+
case 'rawverify':
|
|
1140
1326
|
dsa_pub = dsa.DSAPublicKey.Copy(
|
|
1141
1327
|
_LoadObj(args.key_path, args.protect or None, dsa.DSAPublicKey))
|
|
1142
1328
|
m = _ParseInt(args.message) % dsa_pub.prime_seed
|
|
1143
1329
|
c1, c2 = args.signature.split(':')
|
|
1144
1330
|
ss = (_ParseInt(c1), _ParseInt(c2))
|
|
1145
|
-
print('DSA signature: ' + ('OK' if dsa_pub.
|
|
1331
|
+
print('DSA signature: ' + ('OK' if dsa_pub.RawVerify(m, ss) else 'INVALID'))
|
|
1332
|
+
case 'sign':
|
|
1333
|
+
dsa_priv = _LoadObj(args.key_path, args.protect or None, dsa.DSAPrivateKey)
|
|
1334
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1335
|
+
ct = dsa_priv.Sign(pt, associated_data=aad)
|
|
1336
|
+
print(_BytesToText(ct, out_format))
|
|
1337
|
+
case 'verify':
|
|
1338
|
+
dsa_pub = _LoadObj(args.key_path, args.protect or None, dsa.DSAPublicKey)
|
|
1339
|
+
pt = _BytesFromText(args.message, in_format)
|
|
1340
|
+
ct = _BytesFromText(args.signature, in_format)
|
|
1341
|
+
print('DSA signature: ' +
|
|
1342
|
+
('OK' if dsa_pub.Verify(pt, ct, associated_data=aad) else 'INVALID'))
|
|
1146
1343
|
case _:
|
|
1147
1344
|
raise NotImplementedError()
|
|
1148
1345
|
|
|
@@ -1154,15 +1351,15 @@ def BidCommand(
|
|
|
1154
1351
|
match bid_cmd:
|
|
1155
1352
|
case 'new':
|
|
1156
1353
|
secret: bytes = _BytesFromText(args.secret, in_format)
|
|
1157
|
-
bid_priv: base.
|
|
1158
|
-
bid_pub: base.
|
|
1354
|
+
bid_priv: base.PrivateBid512 = base.PrivateBid512.New(secret)
|
|
1355
|
+
bid_pub: base.PublicBid512 = base.PublicBid512.Copy(bid_priv)
|
|
1159
1356
|
_SaveObj(bid_priv, args.key_path + '.priv', args.protect or None)
|
|
1160
1357
|
_SaveObj(bid_pub, args.key_path + '.pub', args.protect or None)
|
|
1161
1358
|
print(f'Bid private/public commitments saved to {args.key_path + ".priv/.pub"!r}')
|
|
1162
1359
|
case 'verify':
|
|
1163
|
-
bid_priv = _LoadObj(args.key_path + '.priv', args.protect or None, base.
|
|
1164
|
-
bid_pub = _LoadObj(args.key_path + '.pub', args.protect or None, base.
|
|
1165
|
-
bid_pub_expect: base.
|
|
1360
|
+
bid_priv = _LoadObj(args.key_path + '.priv', args.protect or None, base.PrivateBid512)
|
|
1361
|
+
bid_pub = _LoadObj(args.key_path + '.pub', args.protect or None, base.PublicBid512)
|
|
1362
|
+
bid_pub_expect: base.PublicBid512 = base.PublicBid512.Copy(bid_priv)
|
|
1166
1363
|
print('Bid commitment: ' + (
|
|
1167
1364
|
'OK' if (bid_pub.VerifyBid(bid_priv.private_key, bid_priv.secret_bid) and
|
|
1168
1365
|
bid_pub == bid_pub_expect) else 'INVALID'))
|
|
@@ -1172,8 +1369,13 @@ def BidCommand(
|
|
|
1172
1369
|
raise NotImplementedError()
|
|
1173
1370
|
|
|
1174
1371
|
|
|
1175
|
-
def SSSCommand(
|
|
1372
|
+
def SSSCommand(
|
|
1373
|
+
args: argparse.Namespace, in_format: _StrBytesType, out_format: _StrBytesType, /) -> None:
|
|
1176
1374
|
"""Execute `sss` command."""
|
|
1375
|
+
pt: bytes
|
|
1376
|
+
sss_share: sss.ShamirSharePrivate
|
|
1377
|
+
subset: list[sss.ShamirSharePrivate]
|
|
1378
|
+
data_share: sss.ShamirShareData | None
|
|
1177
1379
|
sss_cmd: str = args.sss_command.lower().strip() if args.sss_command else ''
|
|
1178
1380
|
match sss_cmd:
|
|
1179
1381
|
case 'new':
|
|
@@ -1183,32 +1385,53 @@ def SSSCommand(args: argparse.Namespace, /) -> None:
|
|
|
1183
1385
|
_SaveObj(sss_priv, args.key_path + '.priv', args.protect or None)
|
|
1184
1386
|
_SaveObj(sss_pub, args.key_path + '.pub', args.protect or None)
|
|
1185
1387
|
print(f'SSS private/public keys saved to {args.key_path + ".priv/.pub"!r}')
|
|
1186
|
-
case '
|
|
1388
|
+
case 'rawshares':
|
|
1187
1389
|
sss_priv = _LoadObj(
|
|
1188
1390
|
args.key_path + '.priv', args.protect or None, sss.ShamirSharedSecretPrivate)
|
|
1189
1391
|
secret: int = _ParseInt(args.secret)
|
|
1190
|
-
sss_share
|
|
1191
|
-
for i, sss_share in enumerate(sss_priv.Shares(secret, max_shares=args.count)):
|
|
1392
|
+
for i, sss_share in enumerate(sss_priv.RawShares(secret, max_shares=args.count)):
|
|
1192
1393
|
_SaveObj(sss_share, f'{args.key_path}.share.{i + 1}', args.protect or None)
|
|
1193
1394
|
print(f'SSS {args.count} individual (private) shares saved to '
|
|
1194
1395
|
f'{args.key_path + ".share.1…" + str(args.count)!r}')
|
|
1195
|
-
case '
|
|
1396
|
+
case 'rawrecover':
|
|
1196
1397
|
sss_pub = _LoadObj(args.key_path + '.pub', args.protect or None, sss.ShamirSharedSecretPublic)
|
|
1197
|
-
subset
|
|
1398
|
+
subset = []
|
|
1198
1399
|
for fname in glob.glob(args.key_path + '.share.*'):
|
|
1199
1400
|
sss_share = _LoadObj(fname, args.protect or None, sss.ShamirSharePrivate)
|
|
1200
1401
|
subset.append(sss_share)
|
|
1201
1402
|
print(f'Loaded SSS share: {fname!r}')
|
|
1202
1403
|
print('Secret:')
|
|
1203
|
-
print(sss_pub.
|
|
1204
|
-
case '
|
|
1404
|
+
print(sss_pub.RawRecoverSecret(subset))
|
|
1405
|
+
case 'rawverify':
|
|
1205
1406
|
sss_priv = _LoadObj(
|
|
1206
1407
|
args.key_path + '.priv', args.protect or None, sss.ShamirSharedSecretPrivate)
|
|
1207
1408
|
secret = _ParseInt(args.secret)
|
|
1208
1409
|
for fname in glob.glob(args.key_path + '.share.*'):
|
|
1209
1410
|
sss_share = _LoadObj(fname, args.protect or None, sss.ShamirSharePrivate)
|
|
1210
1411
|
print(f'SSS share {fname!r} verification: '
|
|
1211
|
-
f'{"OK" if sss_priv.
|
|
1412
|
+
f'{"OK" if sss_priv.RawVerifyShare(secret, sss_share) else "INVALID"}')
|
|
1413
|
+
case 'shares':
|
|
1414
|
+
sss_priv = _LoadObj(
|
|
1415
|
+
args.key_path + '.priv', args.protect or None, sss.ShamirSharedSecretPrivate)
|
|
1416
|
+
pt = _BytesFromText(args.secret, in_format)
|
|
1417
|
+
for i, data_share in enumerate(sss_priv.MakeDataShares(pt, args.count)):
|
|
1418
|
+
_SaveObj(data_share, f'{args.key_path}.share.{i + 1}', args.protect or None)
|
|
1419
|
+
print(f'SSS {args.count} individual (private) shares saved to '
|
|
1420
|
+
f'{args.key_path + ".share.1…" + str(args.count)!r}')
|
|
1421
|
+
case 'recover':
|
|
1422
|
+
sss_pub = _LoadObj(args.key_path + '.pub', args.protect or None, sss.ShamirSharedSecretPublic)
|
|
1423
|
+
subset, data_share = [], None
|
|
1424
|
+
for fname in glob.glob(args.key_path + '.share.*'):
|
|
1425
|
+
sss_share = _LoadObj(fname, args.protect or None, sss.ShamirSharePrivate)
|
|
1426
|
+
subset.append(sss_share)
|
|
1427
|
+
if isinstance(sss_share, sss.ShamirShareData):
|
|
1428
|
+
data_share = sss_share
|
|
1429
|
+
print(f'Loaded SSS share: {fname!r}')
|
|
1430
|
+
if data_share is None:
|
|
1431
|
+
raise base.InputError('no data share found among the available shares')
|
|
1432
|
+
pt = data_share.RecoverData(subset)
|
|
1433
|
+
print('Secret:')
|
|
1434
|
+
print(_BytesToText(pt, out_format))
|
|
1212
1435
|
case _:
|
|
1213
1436
|
raise NotImplementedError()
|
|
1214
1437
|
|
|
@@ -1346,19 +1569,19 @@ def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-na
|
|
|
1346
1569
|
AESCommand(args, in_format, out_format)
|
|
1347
1570
|
|
|
1348
1571
|
case 'rsa':
|
|
1349
|
-
RSACommand(args)
|
|
1572
|
+
RSACommand(args, in_format, out_format)
|
|
1350
1573
|
|
|
1351
1574
|
case 'elgamal':
|
|
1352
|
-
ElGamalCommand(args)
|
|
1575
|
+
ElGamalCommand(args, in_format, out_format)
|
|
1353
1576
|
|
|
1354
1577
|
case 'dsa':
|
|
1355
|
-
DSACommand(args)
|
|
1578
|
+
DSACommand(args, in_format, out_format)
|
|
1356
1579
|
|
|
1357
1580
|
case 'bid':
|
|
1358
1581
|
BidCommand(args, in_format, out_format)
|
|
1359
1582
|
|
|
1360
1583
|
case 'sss':
|
|
1361
|
-
SSSCommand(args)
|
|
1584
|
+
SSSCommand(args, in_format, out_format)
|
|
1362
1585
|
|
|
1363
1586
|
# -------- Documentation ----------
|
|
1364
1587
|
case 'doc':
|