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.
@@ -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 (integer messages)
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|verify
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()[0]).strip() # type:ignore
252
- short: str = (sp.help if hasattr(sp, 'help') else '') or '' # type:ignore
253
- help_text = short or help_text # type:ignore
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 encrypt <plaintext>\n'
332
- ' poetry run transcrypto -p rsa-key.priv rsa decrypt <ciphertext>\n'
333
- ' poetry run transcrypto -p rsa-key.priv rsa sign <message>\n'
334
- ' poetry run transcrypto -p rsa-key.pub rsa verify <message> <signature>\n\n'
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 encrypt <plaintext>\n'
339
- ' poetry run transcrypto -p eg-key.priv elgamal decrypt <c1:c2>\n'
340
- ' poetry run transcrypto -p eg-key.priv elgamal sign <message>\n'
341
- ' poetry run transcrypto-p eg-key.pub elgamal verify <message> <s1:s2>\n\n'
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 sign <message>\n'
346
- ' poetry run transcrypto -p dsa-key.pub dsa verify <message> <s1:s2>\n\n'
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 shares <secret> 5\n'
353
- ' poetry run transcrypto -p sss-key sss recover\n'
354
- ' poetry run transcrypto -p sss-key sss verify <secret>'
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=('Raw RSA (Rivest-Shamir-Adleman) asymmetric cryptography over *integers* '
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 integer with public key
698
- p_rsa_enc: argparse.ArgumentParser = rsa_sub.add_parser(
699
- 'encrypt',
700
- help='Encrypt integer `message` with public key.',
701
- epilog='-p rsa-key.pub rsa encrypt 999\n6354905961171348600')
702
- p_rsa_enc.add_argument(
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 integer ciphertext with private key
706
- p_rsa_dec: argparse.ArgumentParser = rsa_sub.add_parser(
707
- 'decrypt',
708
- help='Decrypt integer `ciphertext` with private key.',
709
- epilog='-p rsa-key.priv rsa decrypt 6354905961171348600\n999')
710
- p_rsa_dec.add_argument(
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 integer message with private key
714
- p_rsa_sig: argparse.ArgumentParser = rsa_sub.add_parser(
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 integer `message` with private key.',
717
- epilog='-p rsa-key.priv rsa sign 999\n7632909108672871784')
718
- p_rsa_sig.add_argument('message', type=str, help='Integer message to sign, 1≤`message`<*modulus*')
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 integer signature with public key
721
- p_rsa_ver: argparse.ArgumentParser = rsa_sub.add_parser(
722
- 'verify',
723
- help='Verify integer `signature` for integer `message` with public key.',
724
- epilog=('-p rsa-key.pub rsa verify 999 7632909108672871784\nRSA signature: OK $$ '
725
- '-p rsa-key.pub rsa verify 999 7632909108672871785\nRSA signature: INVALID'))
726
- p_rsa_ver.add_argument(
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
- p_rsa_ver.add_argument(
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=('Raw El-Gamal asymmetric cryptography over *integers* '
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 integer with public key
765
- p_eg_enc: argparse.ArgumentParser = eg_sub.add_parser(
766
- 'encrypt',
767
- help='Encrypt integer `message` with public key.',
768
- epilog='-p eg-key.pub elgamal encrypt 999\n2948854810728206041:15945988196340032688')
769
- p_eg_enc.add_argument(
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
- p_eg_dec: argparse.ArgumentParser = eg_sub.add_parser(
774
- 'decrypt',
775
- help='Decrypt integer `ciphertext` with private key.',
776
- epilog='-p eg-key.priv elgamal decrypt 2948854810728206041:15945988196340032688\n999')
777
- p_eg_dec.add_argument(
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 integer message with private key
783
- p_eg_sig: argparse.ArgumentParser = eg_sub.add_parser(
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 integer message with private key. Output will 2 integers in a `s1:s2` format.',
786
- epilog='-p eg-key.priv elgamal sign 999\n4674885853217269088:14532144906178302633')
787
- p_eg_sig.add_argument('message', type=str, help='Integer message to sign, 1≤`message`<*modulus*')
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
- p_eg_ver: argparse.ArgumentParser = eg_sub.add_parser(
791
- 'verify',
792
- help='Verify integer `signature` for integer `message` with public key.',
793
- epilog=('-p eg-key.pub elgamal verify 999 4674885853217269088:14532144906178302633\n'
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 verify 999 4674885853217269088:14532144906178302632\n'
869
+ '-p eg-key.pub elgamal rawverify 999 4674885853217269088:14532144906178302632\n'
796
870
  'El-Gamal signature: INVALID'))
797
- p_eg_ver.add_argument(
871
+ p_eg_ver_raw.add_argument(
798
872
  'message', type=str, help='Integer message that was signed earlier, 1≤`message`<*modulus*')
799
- p_eg_ver.add_argument(
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=('Raw DSA (Digital Signature Algorithm) asymmetric signing over *integers* '
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. 3584/256). '
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=3584,
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=256,
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 integer m with private key
843
- p_dsa_sign: argparse.ArgumentParser = dsa_sub.add_parser(
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 integer message with private key. Output will 2 integers in a `s1:s2` format.',
846
- epilog='-p dsa-key.priv dsa sign 999\n2395961484:3435572290')
847
- p_dsa_sign.add_argument('message', type=str, help='Integer message to sign, 1≤`message`<`q`')
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
- p_dsa_verify: argparse.ArgumentParser = dsa_sub.add_parser(
851
- 'verify',
852
- help='Verify integer `signature` for integer `message` with public key.',
853
- epilog=('-p dsa-key.pub dsa verify 999 2395961484:3435572290\nDSA signature: OK $$ '
854
- '-p dsa-key.pub dsa verify 999 2395961484:3435572291\nDSA signature: INVALID'))
855
- p_dsa_verify.add_argument(
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
- p_dsa_verify.add_argument(
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=('Raw SSS (Shamir Shared Secret) secret sharing crypto scheme over *integers* '
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
- p_sss_shares: argparse.ArgumentParser = sss_sub.add_parser(
920
- 'shares',
921
- help='Issue `count` private shares for an integer `secret`.',
922
- epilog=('-p sss-key sss shares 999 5\n'
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
- p_sss_shares.add_argument(
1032
+ p_sss_shares_raw.add_argument(
927
1033
  'secret', type=str, help='Integer secret to be protected, 1≤`secret`<*modulus*')
928
- p_sss_shares.add_argument(
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:\n999'))
1070
+ 'Secret:\nabcde'))
943
1071
 
944
1072
  # Verify a share against a secret
945
- p_sss_verify: argparse.ArgumentParser = sss_sub.add_parser(
946
- 'verify',
947
- help='Verify shares against a secret (private params).',
948
- epilog=('-p sss-key sss verify 999\n'
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 verify 998\n'
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
- p_sss_verify.add_argument(
957
- 'secret', type=str, help='Integer secret used to generate the shares, 1≤`secret`<*modulus*')
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: aes.AESKey = aes.AESKey.FromStaticPassword(args.password)
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(args: argparse.Namespace, /) -> None:
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: rsa.RSAPrivateKey = rsa.RSAPrivateKey.New(args.bits)
1040
- rsa_pub: rsa.RSAPublicKey = rsa.RSAPublicKey.Copy(rsa_priv)
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 'encrypt':
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.Encrypt(m))
1049
- case 'decrypt':
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.Decrypt(c))
1053
- case 'sign':
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.Sign(m))
1057
- case 'verify':
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.VerifySignature(m, sig) else 'INVALID'))
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(args: argparse.Namespace, /) -> None:
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: elgamal.ElGamalPrivateKey = elgamal.ElGamalPrivateKey.New(
1238
+ eg_priv = elgamal.ElGamalPrivateKey.New(
1082
1239
  _LoadObj(args.key_path + '.shared', args.protect or None, elgamal.ElGamalSharedPublicKey))
1083
- eg_pub: elgamal.ElGamalPublicKey = elgamal.ElGamalPublicKey.Copy(eg_priv)
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 'encrypt':
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.Encrypt(m)
1248
+ ss = eg_pub.RawEncrypt(m)
1092
1249
  print(f'{ss[0]}:{ss[1]}')
1093
- case 'decrypt':
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.Decrypt(ss))
1098
- case 'sign':
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.Sign(m)
1258
+ ss = eg_priv.RawSign(m)
1102
1259
  print(f'{ss[0]}:{ss[1]}')
1103
- case 'verify':
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.VerifySignature(m, ss) else 'INVALID'))
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(args: argparse.Namespace, /) -> None:
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: dsa.DSAPrivateKey = dsa.DSAPrivateKey.New(
1314
+ dsa_priv = dsa.DSAPrivateKey.New(
1129
1315
  _LoadObj(args.key_path + '.shared', args.protect or None, dsa.DSASharedPublicKey))
1130
- dsa_pub: dsa.DSAPublicKey = dsa.DSAPublicKey.Copy(dsa_priv)
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 'sign':
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.Sign(m)
1323
+ ss = dsa_priv.RawSign(m)
1138
1324
  print(f'{ss[0]}:{ss[1]}')
1139
- case 'verify':
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.VerifySignature(m, ss) else 'INVALID'))
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.PrivateBid = base.PrivateBid.New(secret)
1158
- bid_pub: base.PublicBid = base.PublicBid.Copy(bid_priv)
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.PrivateBid)
1164
- bid_pub = _LoadObj(args.key_path + '.pub', args.protect or None, base.PublicBid)
1165
- bid_pub_expect: base.PublicBid = base.PublicBid.Copy(bid_priv)
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(args: argparse.Namespace, /) -> None:
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 'shares':
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: sss.ShamirSharePrivate
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 'recover':
1396
+ case 'rawrecover':
1196
1397
  sss_pub = _LoadObj(args.key_path + '.pub', args.protect or None, sss.ShamirSharedSecretPublic)
1197
- subset: list[sss.ShamirSharePrivate] = []
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.RecoverSecret(subset))
1204
- case 'verify':
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.VerifyShare(secret, sss_share) else "INVALID"}')
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':