passagemath-modules 10.6.31rc3__cp314-cp314-musllinux_1_2_aarch64.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.
Potentially problematic release.
This version of passagemath-modules might be problematic. Click here for more details.
- passagemath_modules-10.6.31rc3.dist-info/METADATA +281 -0
- passagemath_modules-10.6.31rc3.dist-info/RECORD +807 -0
- passagemath_modules-10.6.31rc3.dist-info/WHEEL +5 -0
- passagemath_modules-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_modules.libs/libgcc_s-2d945d6c.so.1 +0 -0
- passagemath_modules.libs/libgfortran-67378ab2.so.5.0.0 +0 -0
- passagemath_modules.libs/libgmp-28992bcb.so.10.5.0 +0 -0
- passagemath_modules.libs/libgsl-23768756.so.28.0.0 +0 -0
- passagemath_modules.libs/libmpc-7897025b.so.3.3.1 +0 -0
- passagemath_modules.libs/libmpfr-e34bb864.so.6.2.1 +0 -0
- passagemath_modules.libs/libopenblasp-r0-503f0c35.3.29.so +0 -0
- sage/algebras/all__sagemath_modules.py +20 -0
- sage/algebras/catalog.py +148 -0
- sage/algebras/clifford_algebra.py +3107 -0
- sage/algebras/clifford_algebra_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/algebras/clifford_algebra_element.pxd +16 -0
- sage/algebras/clifford_algebra_element.pyx +997 -0
- sage/algebras/commutative_dga.py +4252 -0
- sage/algebras/exterior_algebra_groebner.cpython-314-aarch64-linux-musl.so +0 -0
- sage/algebras/exterior_algebra_groebner.pxd +55 -0
- sage/algebras/exterior_algebra_groebner.pyx +727 -0
- sage/algebras/finite_dimensional_algebras/all.py +2 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra.py +1029 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd +12 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pyx +706 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_ideal.py +196 -0
- sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_morphism.py +255 -0
- sage/algebras/finite_gca.py +528 -0
- sage/algebras/group_algebra.py +232 -0
- sage/algebras/lie_algebras/abelian.py +197 -0
- sage/algebras/lie_algebras/affine_lie_algebra.py +1213 -0
- sage/algebras/lie_algebras/all.py +25 -0
- sage/algebras/lie_algebras/all__sagemath_modules.py +1 -0
- sage/algebras/lie_algebras/bch.py +177 -0
- sage/algebras/lie_algebras/bgg_dual_module.py +1184 -0
- sage/algebras/lie_algebras/bgg_resolution.py +232 -0
- sage/algebras/lie_algebras/center_uea.py +767 -0
- sage/algebras/lie_algebras/classical_lie_algebra.py +2516 -0
- sage/algebras/lie_algebras/examples.py +683 -0
- sage/algebras/lie_algebras/free_lie_algebra.py +973 -0
- sage/algebras/lie_algebras/heisenberg.py +820 -0
- sage/algebras/lie_algebras/lie_algebra.py +1562 -0
- sage/algebras/lie_algebras/lie_algebra_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/algebras/lie_algebras/lie_algebra_element.pxd +68 -0
- sage/algebras/lie_algebras/lie_algebra_element.pyx +2122 -0
- sage/algebras/lie_algebras/morphism.py +661 -0
- sage/algebras/lie_algebras/nilpotent_lie_algebra.py +457 -0
- sage/algebras/lie_algebras/onsager.py +1324 -0
- sage/algebras/lie_algebras/poincare_birkhoff_witt.py +816 -0
- sage/algebras/lie_algebras/quotient.py +462 -0
- sage/algebras/lie_algebras/rank_two_heisenberg_virasoro.py +355 -0
- sage/algebras/lie_algebras/representation.py +1040 -0
- sage/algebras/lie_algebras/structure_coefficients.py +459 -0
- sage/algebras/lie_algebras/subalgebra.py +967 -0
- sage/algebras/lie_algebras/symplectic_derivation.py +289 -0
- sage/algebras/lie_algebras/verma_module.py +1630 -0
- sage/algebras/lie_algebras/virasoro.py +1186 -0
- sage/algebras/octonion_algebra.cpython-314-aarch64-linux-musl.so +0 -0
- sage/algebras/octonion_algebra.pxd +20 -0
- sage/algebras/octonion_algebra.pyx +987 -0
- sage/algebras/orlik_solomon.py +907 -0
- sage/algebras/orlik_terao.py +779 -0
- sage/algebras/steenrod/all.py +7 -0
- sage/algebras/steenrod/steenrod_algebra.py +4258 -0
- sage/algebras/steenrod/steenrod_algebra_bases.py +1179 -0
- sage/algebras/steenrod/steenrod_algebra_misc.py +1167 -0
- sage/algebras/steenrod/steenrod_algebra_mult.py +954 -0
- sage/algebras/weyl_algebra.py +1126 -0
- sage/all__sagemath_modules.py +62 -0
- sage/calculus/all__sagemath_modules.py +19 -0
- sage/calculus/expr.py +205 -0
- sage/calculus/integration.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/integration.pyx +698 -0
- sage/calculus/interpolation.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/interpolation.pxd +13 -0
- sage/calculus/interpolation.pyx +387 -0
- sage/calculus/interpolators.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/interpolators.pyx +326 -0
- sage/calculus/ode.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/ode.pxd +5 -0
- sage/calculus/ode.pyx +610 -0
- sage/calculus/riemann.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/riemann.pyx +1521 -0
- sage/calculus/test_sympy.py +201 -0
- sage/calculus/transforms/all.py +7 -0
- sage/calculus/transforms/dft.py +844 -0
- sage/calculus/transforms/dwt.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/transforms/dwt.pxd +7 -0
- sage/calculus/transforms/dwt.pyx +160 -0
- sage/calculus/transforms/fft.cpython-314-aarch64-linux-musl.so +0 -0
- sage/calculus/transforms/fft.pxd +12 -0
- sage/calculus/transforms/fft.pyx +487 -0
- sage/calculus/wester.py +662 -0
- sage/coding/abstract_code.py +1108 -0
- sage/coding/ag_code.py +868 -0
- sage/coding/ag_code_decoders.cpython-314-aarch64-linux-musl.so +0 -0
- sage/coding/ag_code_decoders.pyx +2639 -0
- sage/coding/all.py +15 -0
- sage/coding/bch_code.py +494 -0
- sage/coding/binary_code.cpython-314-aarch64-linux-musl.so +0 -0
- sage/coding/binary_code.pxd +124 -0
- sage/coding/binary_code.pyx +4139 -0
- sage/coding/bounds_catalog.py +43 -0
- sage/coding/channel.py +819 -0
- sage/coding/channels_catalog.py +29 -0
- sage/coding/code_bounds.py +755 -0
- sage/coding/code_constructions.py +804 -0
- sage/coding/codes_catalog.py +111 -0
- sage/coding/cyclic_code.py +1329 -0
- sage/coding/databases.py +316 -0
- sage/coding/decoder.py +373 -0
- sage/coding/decoders_catalog.py +88 -0
- sage/coding/delsarte_bounds.py +709 -0
- sage/coding/encoder.py +390 -0
- sage/coding/encoders_catalog.py +64 -0
- sage/coding/extended_code.py +468 -0
- sage/coding/gabidulin_code.py +1058 -0
- sage/coding/golay_code.py +404 -0
- sage/coding/goppa_code.py +441 -0
- sage/coding/grs_code.py +2371 -0
- sage/coding/guava.py +107 -0
- sage/coding/guruswami_sudan/all.py +1 -0
- sage/coding/guruswami_sudan/gs_decoder.py +897 -0
- sage/coding/guruswami_sudan/interpolation.py +409 -0
- sage/coding/guruswami_sudan/utils.py +176 -0
- sage/coding/hamming_code.py +176 -0
- sage/coding/information_set_decoder.py +1032 -0
- sage/coding/kasami_codes.cpython-314-aarch64-linux-musl.so +0 -0
- sage/coding/kasami_codes.pyx +351 -0
- sage/coding/linear_code.py +3067 -0
- sage/coding/linear_code_no_metric.py +1354 -0
- sage/coding/linear_rank_metric.py +961 -0
- sage/coding/parity_check_code.py +353 -0
- sage/coding/punctured_code.py +719 -0
- sage/coding/reed_muller_code.py +999 -0
- sage/coding/self_dual_codes.py +942 -0
- sage/coding/source_coding/all.py +2 -0
- sage/coding/source_coding/huffman.py +553 -0
- sage/coding/subfield_subcode.py +423 -0
- sage/coding/two_weight_db.py +399 -0
- sage/combinat/all__sagemath_modules.py +7 -0
- sage/combinat/cartesian_product.py +347 -0
- sage/combinat/family.py +11 -0
- sage/combinat/free_module.py +1977 -0
- sage/combinat/root_system/all.py +147 -0
- sage/combinat/root_system/ambient_space.py +527 -0
- sage/combinat/root_system/associahedron.py +471 -0
- sage/combinat/root_system/braid_move_calculator.py +143 -0
- sage/combinat/root_system/braid_orbit.cpython-314-aarch64-linux-musl.so +0 -0
- sage/combinat/root_system/braid_orbit.pyx +144 -0
- sage/combinat/root_system/branching_rules.py +2301 -0
- sage/combinat/root_system/cartan_matrix.py +1245 -0
- sage/combinat/root_system/cartan_type.py +3069 -0
- sage/combinat/root_system/coxeter_group.py +162 -0
- sage/combinat/root_system/coxeter_matrix.py +1261 -0
- sage/combinat/root_system/coxeter_type.py +681 -0
- sage/combinat/root_system/dynkin_diagram.py +900 -0
- sage/combinat/root_system/extended_affine_weyl_group.py +2993 -0
- sage/combinat/root_system/fundamental_group.py +795 -0
- sage/combinat/root_system/hecke_algebra_representation.py +1203 -0
- sage/combinat/root_system/integrable_representations.py +1227 -0
- sage/combinat/root_system/non_symmetric_macdonald_polynomials.py +1965 -0
- sage/combinat/root_system/pieri_factors.py +1147 -0
- sage/combinat/root_system/plot.py +1615 -0
- sage/combinat/root_system/root_lattice_realization_algebras.py +1214 -0
- sage/combinat/root_system/root_lattice_realizations.py +4628 -0
- sage/combinat/root_system/root_space.py +487 -0
- sage/combinat/root_system/root_system.py +882 -0
- sage/combinat/root_system/type_A.py +348 -0
- sage/combinat/root_system/type_A_affine.py +227 -0
- sage/combinat/root_system/type_A_infinity.py +241 -0
- sage/combinat/root_system/type_B.py +347 -0
- sage/combinat/root_system/type_BC_affine.py +287 -0
- sage/combinat/root_system/type_B_affine.py +216 -0
- sage/combinat/root_system/type_C.py +317 -0
- sage/combinat/root_system/type_C_affine.py +188 -0
- sage/combinat/root_system/type_D.py +357 -0
- sage/combinat/root_system/type_D_affine.py +208 -0
- sage/combinat/root_system/type_E.py +641 -0
- sage/combinat/root_system/type_E_affine.py +231 -0
- sage/combinat/root_system/type_F.py +387 -0
- sage/combinat/root_system/type_F_affine.py +137 -0
- sage/combinat/root_system/type_G.py +293 -0
- sage/combinat/root_system/type_G_affine.py +132 -0
- sage/combinat/root_system/type_H.py +105 -0
- sage/combinat/root_system/type_I.py +110 -0
- sage/combinat/root_system/type_Q.py +150 -0
- sage/combinat/root_system/type_affine.py +509 -0
- sage/combinat/root_system/type_dual.py +704 -0
- sage/combinat/root_system/type_folded.py +301 -0
- sage/combinat/root_system/type_marked.py +748 -0
- sage/combinat/root_system/type_reducible.py +601 -0
- sage/combinat/root_system/type_relabel.py +730 -0
- sage/combinat/root_system/type_super_A.py +837 -0
- sage/combinat/root_system/weight_lattice_realizations.py +1188 -0
- sage/combinat/root_system/weight_space.py +639 -0
- sage/combinat/root_system/weyl_characters.py +2238 -0
- sage/crypto/__init__.py +4 -0
- sage/crypto/all.py +28 -0
- sage/crypto/block_cipher/all.py +7 -0
- sage/crypto/block_cipher/des.py +1065 -0
- sage/crypto/block_cipher/miniaes.py +2171 -0
- sage/crypto/block_cipher/present.py +909 -0
- sage/crypto/block_cipher/sdes.py +1527 -0
- sage/crypto/boolean_function.cpython-314-aarch64-linux-musl.so +0 -0
- sage/crypto/boolean_function.pxd +10 -0
- sage/crypto/boolean_function.pyx +1487 -0
- sage/crypto/cipher.py +78 -0
- sage/crypto/classical.py +3668 -0
- sage/crypto/classical_cipher.py +569 -0
- sage/crypto/cryptosystem.py +387 -0
- sage/crypto/key_exchange/all.py +7 -0
- sage/crypto/key_exchange/catalog.py +24 -0
- sage/crypto/key_exchange/diffie_hellman.py +323 -0
- sage/crypto/key_exchange/key_exchange_scheme.py +107 -0
- sage/crypto/lattice.py +312 -0
- sage/crypto/lfsr.py +295 -0
- sage/crypto/lwe.py +840 -0
- sage/crypto/mq/__init__.py +4 -0
- sage/crypto/mq/mpolynomialsystemgenerator.py +204 -0
- sage/crypto/mq/rijndael_gf.py +2345 -0
- sage/crypto/mq/sbox.py +7 -0
- sage/crypto/mq/sr.py +3344 -0
- sage/crypto/public_key/all.py +5 -0
- sage/crypto/public_key/blum_goldwasser.py +776 -0
- sage/crypto/sbox.cpython-314-aarch64-linux-musl.so +0 -0
- sage/crypto/sbox.pyx +2090 -0
- sage/crypto/sboxes.py +2090 -0
- sage/crypto/stream.py +390 -0
- sage/crypto/stream_cipher.py +297 -0
- sage/crypto/util.py +519 -0
- sage/ext/all__sagemath_modules.py +1 -0
- sage/ext/interpreters/__init__.py +1 -0
- sage/ext/interpreters/all__sagemath_modules.py +2 -0
- sage/ext/interpreters/wrapper_cc.cpython-314-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_cc.pxd +30 -0
- sage/ext/interpreters/wrapper_cc.pyx +252 -0
- sage/ext/interpreters/wrapper_cdf.cpython-314-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_cdf.pxd +26 -0
- sage/ext/interpreters/wrapper_cdf.pyx +245 -0
- sage/ext/interpreters/wrapper_rdf.cpython-314-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_rdf.pxd +23 -0
- sage/ext/interpreters/wrapper_rdf.pyx +221 -0
- sage/ext/interpreters/wrapper_rr.cpython-314-aarch64-linux-musl.so +0 -0
- sage/ext/interpreters/wrapper_rr.pxd +28 -0
- sage/ext/interpreters/wrapper_rr.pyx +335 -0
- sage/geometry/all__sagemath_modules.py +5 -0
- sage/geometry/toric_lattice.py +1745 -0
- sage/geometry/toric_lattice_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/geometry/toric_lattice_element.pyx +432 -0
- sage/groups/abelian_gps/abelian_group.py +1925 -0
- sage/groups/abelian_gps/abelian_group_element.py +164 -0
- sage/groups/abelian_gps/all__sagemath_modules.py +5 -0
- sage/groups/abelian_gps/dual_abelian_group.py +421 -0
- sage/groups/abelian_gps/dual_abelian_group_element.py +179 -0
- sage/groups/abelian_gps/element_base.py +341 -0
- sage/groups/abelian_gps/values.py +488 -0
- sage/groups/additive_abelian/additive_abelian_group.py +476 -0
- sage/groups/additive_abelian/additive_abelian_wrapper.py +857 -0
- sage/groups/additive_abelian/all.py +4 -0
- sage/groups/additive_abelian/qmodnz.py +231 -0
- sage/groups/additive_abelian/qmodnz_element.py +349 -0
- sage/groups/affine_gps/affine_group.py +535 -0
- sage/groups/affine_gps/all.py +1 -0
- sage/groups/affine_gps/catalog.py +17 -0
- sage/groups/affine_gps/euclidean_group.py +246 -0
- sage/groups/affine_gps/group_element.py +562 -0
- sage/groups/all__sagemath_modules.py +12 -0
- sage/groups/galois_group.py +479 -0
- sage/groups/matrix_gps/all.py +4 -0
- sage/groups/matrix_gps/all__sagemath_modules.py +13 -0
- sage/groups/matrix_gps/catalog.py +26 -0
- sage/groups/matrix_gps/coxeter_group.py +927 -0
- sage/groups/matrix_gps/finitely_generated.py +487 -0
- sage/groups/matrix_gps/group_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/groups/matrix_gps/group_element.pxd +11 -0
- sage/groups/matrix_gps/group_element.pyx +431 -0
- sage/groups/matrix_gps/linear.py +440 -0
- sage/groups/matrix_gps/matrix_group.py +617 -0
- sage/groups/matrix_gps/named_group.py +296 -0
- sage/groups/matrix_gps/orthogonal.py +544 -0
- sage/groups/matrix_gps/symplectic.py +251 -0
- sage/groups/matrix_gps/unitary.py +436 -0
- sage/groups/misc_gps/all__sagemath_modules.py +1 -0
- sage/groups/misc_gps/argument_groups.py +1905 -0
- sage/groups/misc_gps/imaginary_groups.py +479 -0
- sage/groups/perm_gps/all__sagemath_modules.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_modules.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_binary.cpython-314-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_binary.pxd +41 -0
- sage/groups/perm_gps/partn_ref/refinement_binary.pyx +1167 -0
- sage/groups/perm_gps/partn_ref/refinement_matrices.cpython-314-aarch64-linux-musl.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_matrices.pxd +31 -0
- sage/groups/perm_gps/partn_ref/refinement_matrices.pyx +385 -0
- sage/homology/algebraic_topological_model.py +595 -0
- sage/homology/all.py +2 -0
- sage/homology/all__sagemath_modules.py +8 -0
- sage/homology/chain_complex.py +2148 -0
- sage/homology/chain_complex_homspace.py +165 -0
- sage/homology/chain_complex_morphism.py +629 -0
- sage/homology/chain_homotopy.py +604 -0
- sage/homology/chains.py +653 -0
- sage/homology/free_resolution.py +923 -0
- sage/homology/graded_resolution.py +567 -0
- sage/homology/hochschild_complex.py +756 -0
- sage/homology/homology_group.py +188 -0
- sage/homology/homology_morphism.py +422 -0
- sage/homology/homology_vector_space_with_basis.py +1454 -0
- sage/homology/koszul_complex.py +169 -0
- sage/homology/matrix_utils.py +205 -0
- sage/libs/all__sagemath_modules.py +1 -0
- sage/libs/gsl/__init__.py +1 -0
- sage/libs/gsl/airy.pxd +56 -0
- sage/libs/gsl/all.pxd +66 -0
- sage/libs/gsl/array.cpython-314-aarch64-linux-musl.so +0 -0
- sage/libs/gsl/array.pxd +5 -0
- sage/libs/gsl/array.pyx +102 -0
- sage/libs/gsl/bessel.pxd +208 -0
- sage/libs/gsl/blas.pxd +116 -0
- sage/libs/gsl/blas_types.pxd +34 -0
- sage/libs/gsl/block.pxd +52 -0
- sage/libs/gsl/chebyshev.pxd +37 -0
- sage/libs/gsl/clausen.pxd +12 -0
- sage/libs/gsl/combination.pxd +47 -0
- sage/libs/gsl/complex.pxd +151 -0
- sage/libs/gsl/coulomb.pxd +30 -0
- sage/libs/gsl/coupling.pxd +21 -0
- sage/libs/gsl/dawson.pxd +12 -0
- sage/libs/gsl/debye.pxd +24 -0
- sage/libs/gsl/dilog.pxd +14 -0
- sage/libs/gsl/eigen.pxd +46 -0
- sage/libs/gsl/elementary.pxd +12 -0
- sage/libs/gsl/ellint.pxd +48 -0
- sage/libs/gsl/elljac.pxd +8 -0
- sage/libs/gsl/erf.pxd +32 -0
- sage/libs/gsl/errno.pxd +26 -0
- sage/libs/gsl/exp.pxd +44 -0
- sage/libs/gsl/expint.pxd +44 -0
- sage/libs/gsl/fermi_dirac.pxd +44 -0
- sage/libs/gsl/fft.pxd +121 -0
- sage/libs/gsl/fit.pxd +50 -0
- sage/libs/gsl/gamma.pxd +94 -0
- sage/libs/gsl/gegenbauer.pxd +26 -0
- sage/libs/gsl/histogram.pxd +176 -0
- sage/libs/gsl/hyperg.pxd +52 -0
- sage/libs/gsl/integration.pxd +69 -0
- sage/libs/gsl/interp.pxd +109 -0
- sage/libs/gsl/laguerre.pxd +24 -0
- sage/libs/gsl/lambert.pxd +16 -0
- sage/libs/gsl/legendre.pxd +90 -0
- sage/libs/gsl/linalg.pxd +185 -0
- sage/libs/gsl/log.pxd +26 -0
- sage/libs/gsl/math.pxd +43 -0
- sage/libs/gsl/matrix.pxd +143 -0
- sage/libs/gsl/matrix_complex.pxd +130 -0
- sage/libs/gsl/min.pxd +67 -0
- sage/libs/gsl/monte.pxd +56 -0
- sage/libs/gsl/ntuple.pxd +32 -0
- sage/libs/gsl/odeiv.pxd +70 -0
- sage/libs/gsl/permutation.pxd +78 -0
- sage/libs/gsl/poly.pxd +40 -0
- sage/libs/gsl/pow_int.pxd +12 -0
- sage/libs/gsl/psi.pxd +28 -0
- sage/libs/gsl/qrng.pxd +29 -0
- sage/libs/gsl/random.pxd +257 -0
- sage/libs/gsl/rng.pxd +100 -0
- sage/libs/gsl/roots.pxd +72 -0
- sage/libs/gsl/sort.pxd +36 -0
- sage/libs/gsl/statistics.pxd +59 -0
- sage/libs/gsl/sum.pxd +55 -0
- sage/libs/gsl/synchrotron.pxd +16 -0
- sage/libs/gsl/transport.pxd +24 -0
- sage/libs/gsl/trig.pxd +58 -0
- sage/libs/gsl/types.pxd +137 -0
- sage/libs/gsl/vector.pxd +101 -0
- sage/libs/gsl/vector_complex.pxd +83 -0
- sage/libs/gsl/wavelet.pxd +49 -0
- sage/libs/gsl/zeta.pxd +28 -0
- sage/libs/mpc/__init__.pxd +114 -0
- sage/libs/mpc/types.pxd +28 -0
- sage/libs/mpfr/__init__.pxd +299 -0
- sage/libs/mpfr/types.pxd +26 -0
- sage/libs/mpmath/__init__.py +1 -0
- sage/libs/mpmath/all.py +27 -0
- sage/libs/mpmath/all__sagemath_modules.py +1 -0
- sage/libs/mpmath/utils.cpython-314-aarch64-linux-musl.so +0 -0
- sage/libs/mpmath/utils.pxd +4 -0
- sage/libs/mpmath/utils.pyx +319 -0
- sage/matrix/action.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/action.pxd +26 -0
- sage/matrix/action.pyx +596 -0
- sage/matrix/all.py +9 -0
- sage/matrix/args.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/args.pxd +144 -0
- sage/matrix/args.pyx +1668 -0
- sage/matrix/benchmark.py +1258 -0
- sage/matrix/berlekamp_massey.py +95 -0
- sage/matrix/compute_J_ideal.py +926 -0
- sage/matrix/constructor.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/constructor.pyx +750 -0
- sage/matrix/docs.py +430 -0
- sage/matrix/echelon_matrix.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/echelon_matrix.pyx +155 -0
- sage/matrix/matrix.pxd +2 -0
- sage/matrix/matrix0.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix0.pxd +68 -0
- sage/matrix/matrix0.pyx +6324 -0
- sage/matrix/matrix1.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix1.pxd +8 -0
- sage/matrix/matrix1.pyx +2851 -0
- sage/matrix/matrix2.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix2.pxd +25 -0
- sage/matrix/matrix2.pyx +20181 -0
- sage/matrix/matrix_cdv.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_cdv.pxd +4 -0
- sage/matrix/matrix_cdv.pyx +93 -0
- sage/matrix/matrix_complex_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_complex_double_dense.pxd +5 -0
- sage/matrix/matrix_complex_double_dense.pyx +98 -0
- sage/matrix/matrix_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_dense.pxd +5 -0
- sage/matrix/matrix_dense.pyx +343 -0
- sage/matrix/matrix_domain_dense.pxd +5 -0
- sage/matrix/matrix_domain_sparse.pxd +5 -0
- sage/matrix/matrix_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_double_dense.pxd +7 -0
- sage/matrix/matrix_double_dense.pyx +3906 -0
- sage/matrix/matrix_double_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_double_sparse.pxd +6 -0
- sage/matrix/matrix_double_sparse.pyx +248 -0
- sage/matrix/matrix_generic_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_generic_dense.pxd +7 -0
- sage/matrix/matrix_generic_dense.pyx +354 -0
- sage/matrix/matrix_generic_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_generic_sparse.pxd +7 -0
- sage/matrix/matrix_generic_sparse.pyx +461 -0
- sage/matrix/matrix_laurent_mpolynomial_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_laurent_mpolynomial_dense.pxd +5 -0
- sage/matrix/matrix_laurent_mpolynomial_dense.pyx +115 -0
- sage/matrix/matrix_misc.py +313 -0
- sage/matrix/matrix_numpy_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_numpy_dense.pxd +14 -0
- sage/matrix/matrix_numpy_dense.pyx +450 -0
- sage/matrix/matrix_numpy_integer_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_numpy_integer_dense.pxd +7 -0
- sage/matrix/matrix_numpy_integer_dense.pyx +59 -0
- sage/matrix/matrix_polynomial_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_polynomial_dense.pxd +5 -0
- sage/matrix/matrix_polynomial_dense.pyx +5341 -0
- sage/matrix/matrix_real_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_real_double_dense.pxd +7 -0
- sage/matrix/matrix_real_double_dense.pyx +122 -0
- sage/matrix/matrix_space.py +2848 -0
- sage/matrix/matrix_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_sparse.pxd +5 -0
- sage/matrix/matrix_sparse.pyx +1222 -0
- sage/matrix/matrix_window.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_window.pxd +37 -0
- sage/matrix/matrix_window.pyx +242 -0
- sage/matrix/misc_mpfr.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/misc_mpfr.pyx +80 -0
- sage/matrix/operation_table.py +1182 -0
- sage/matrix/special.py +3666 -0
- sage/matrix/strassen.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matrix/strassen.pyx +851 -0
- sage/matrix/symplectic_basis.py +541 -0
- sage/matrix/template.pxd +6 -0
- sage/matrix/tests.py +71 -0
- sage/matroids/advanced.py +77 -0
- sage/matroids/all.py +13 -0
- sage/matroids/basis_exchange_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/basis_exchange_matroid.pxd +96 -0
- sage/matroids/basis_exchange_matroid.pyx +2344 -0
- sage/matroids/basis_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/basis_matroid.pxd +45 -0
- sage/matroids/basis_matroid.pyx +1217 -0
- sage/matroids/catalog.py +44 -0
- sage/matroids/chow_ring.py +473 -0
- sage/matroids/chow_ring_ideal.py +849 -0
- sage/matroids/circuit_closures_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/circuit_closures_matroid.pxd +16 -0
- sage/matroids/circuit_closures_matroid.pyx +559 -0
- sage/matroids/circuits_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/circuits_matroid.pxd +38 -0
- sage/matroids/circuits_matroid.pyx +947 -0
- sage/matroids/constructor.py +1086 -0
- sage/matroids/database_collections.py +365 -0
- sage/matroids/database_matroids.py +5338 -0
- sage/matroids/dual_matroid.py +583 -0
- sage/matroids/extension.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/extension.pxd +34 -0
- sage/matroids/extension.pyx +519 -0
- sage/matroids/flats_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/flats_matroid.pxd +28 -0
- sage/matroids/flats_matroid.pyx +715 -0
- sage/matroids/gammoid.py +600 -0
- sage/matroids/graphic_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/graphic_matroid.pxd +39 -0
- sage/matroids/graphic_matroid.pyx +2024 -0
- sage/matroids/lean_matrix.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/lean_matrix.pxd +126 -0
- sage/matroids/lean_matrix.pyx +3667 -0
- sage/matroids/linear_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/linear_matroid.pxd +180 -0
- sage/matroids/linear_matroid.pyx +6649 -0
- sage/matroids/matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/matroid.pxd +243 -0
- sage/matroids/matroid.pyx +8759 -0
- sage/matroids/matroids_catalog.py +190 -0
- sage/matroids/matroids_plot_helpers.py +890 -0
- sage/matroids/minor_matroid.py +480 -0
- sage/matroids/minorfix.h +9 -0
- sage/matroids/named_matroids.py +5 -0
- sage/matroids/rank_matroid.py +268 -0
- sage/matroids/set_system.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/set_system.pxd +38 -0
- sage/matroids/set_system.pyx +800 -0
- sage/matroids/transversal_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/transversal_matroid.pxd +14 -0
- sage/matroids/transversal_matroid.pyx +893 -0
- sage/matroids/union_matroid.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/union_matroid.pxd +20 -0
- sage/matroids/union_matroid.pyx +331 -0
- sage/matroids/unpickling.cpython-314-aarch64-linux-musl.so +0 -0
- sage/matroids/unpickling.pyx +843 -0
- sage/matroids/utilities.py +809 -0
- sage/misc/all__sagemath_modules.py +20 -0
- sage/misc/c3.cpython-314-aarch64-linux-musl.so +0 -0
- sage/misc/c3.pyx +238 -0
- sage/misc/compat.py +87 -0
- sage/misc/element_with_label.py +173 -0
- sage/misc/func_persist.py +79 -0
- sage/misc/pickle_old.cpython-314-aarch64-linux-musl.so +0 -0
- sage/misc/pickle_old.pyx +19 -0
- sage/misc/proof.py +7 -0
- sage/misc/replace_dot_all.py +472 -0
- sage/misc/sagedoc_conf.py +168 -0
- sage/misc/sphinxify.py +167 -0
- sage/misc/test_class_pickling.py +85 -0
- sage/modules/all.py +42 -0
- sage/modules/complex_double_vector.py +25 -0
- sage/modules/diamond_cutting.py +380 -0
- sage/modules/fg_pid/all.py +1 -0
- sage/modules/fg_pid/fgp_element.py +456 -0
- sage/modules/fg_pid/fgp_module.py +2091 -0
- sage/modules/fg_pid/fgp_morphism.py +550 -0
- sage/modules/filtered_vector_space.py +1271 -0
- sage/modules/finite_submodule_iter.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/finite_submodule_iter.pxd +27 -0
- sage/modules/finite_submodule_iter.pyx +452 -0
- sage/modules/fp_graded/all.py +1 -0
- sage/modules/fp_graded/element.py +346 -0
- sage/modules/fp_graded/free_element.py +298 -0
- sage/modules/fp_graded/free_homspace.py +53 -0
- sage/modules/fp_graded/free_module.py +1060 -0
- sage/modules/fp_graded/free_morphism.py +217 -0
- sage/modules/fp_graded/homspace.py +563 -0
- sage/modules/fp_graded/module.py +1340 -0
- sage/modules/fp_graded/morphism.py +1990 -0
- sage/modules/fp_graded/steenrod/all.py +1 -0
- sage/modules/fp_graded/steenrod/homspace.py +65 -0
- sage/modules/fp_graded/steenrod/module.py +477 -0
- sage/modules/fp_graded/steenrod/morphism.py +404 -0
- sage/modules/fp_graded/steenrod/profile.py +241 -0
- sage/modules/free_module.py +8447 -0
- sage/modules/free_module_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/free_module_element.pxd +22 -0
- sage/modules/free_module_element.pyx +5445 -0
- sage/modules/free_module_homspace.py +369 -0
- sage/modules/free_module_integer.py +896 -0
- sage/modules/free_module_morphism.py +823 -0
- sage/modules/free_module_pseudohomspace.py +352 -0
- sage/modules/free_module_pseudomorphism.py +578 -0
- sage/modules/free_quadratic_module.py +1706 -0
- sage/modules/free_quadratic_module_integer_symmetric.py +1790 -0
- sage/modules/matrix_morphism.py +1745 -0
- sage/modules/misc.py +103 -0
- sage/modules/module_functors.py +192 -0
- sage/modules/multi_filtered_vector_space.py +719 -0
- sage/modules/ore_module.py +2208 -0
- sage/modules/ore_module_element.py +178 -0
- sage/modules/ore_module_homspace.py +147 -0
- sage/modules/ore_module_morphism.py +968 -0
- sage/modules/quotient_module.py +699 -0
- sage/modules/real_double_vector.py +22 -0
- sage/modules/submodule.py +255 -0
- sage/modules/tensor_operations.py +567 -0
- sage/modules/torsion_quadratic_module.py +1352 -0
- sage/modules/tutorial_free_modules.py +248 -0
- sage/modules/vector_complex_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_complex_double_dense.pxd +6 -0
- sage/modules/vector_complex_double_dense.pyx +117 -0
- sage/modules/vector_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_double_dense.pxd +6 -0
- sage/modules/vector_double_dense.pyx +604 -0
- sage/modules/vector_integer_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_integer_dense.pxd +15 -0
- sage/modules/vector_integer_dense.pyx +361 -0
- sage/modules/vector_integer_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_integer_sparse.pxd +29 -0
- sage/modules/vector_integer_sparse.pyx +406 -0
- sage/modules/vector_modn_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_modn_dense.pxd +12 -0
- sage/modules/vector_modn_dense.pyx +394 -0
- sage/modules/vector_modn_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_modn_sparse.pxd +21 -0
- sage/modules/vector_modn_sparse.pyx +298 -0
- sage/modules/vector_numpy_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_numpy_dense.pxd +15 -0
- sage/modules/vector_numpy_dense.pyx +304 -0
- sage/modules/vector_numpy_integer_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_numpy_integer_dense.pxd +7 -0
- sage/modules/vector_numpy_integer_dense.pyx +54 -0
- sage/modules/vector_rational_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_rational_dense.pxd +15 -0
- sage/modules/vector_rational_dense.pyx +387 -0
- sage/modules/vector_rational_sparse.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_rational_sparse.pxd +30 -0
- sage/modules/vector_rational_sparse.pyx +413 -0
- sage/modules/vector_real_double_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/vector_real_double_dense.pxd +6 -0
- sage/modules/vector_real_double_dense.pyx +126 -0
- sage/modules/vector_space_homspace.py +430 -0
- sage/modules/vector_space_morphism.py +989 -0
- sage/modules/with_basis/all.py +15 -0
- sage/modules/with_basis/cell_module.py +494 -0
- sage/modules/with_basis/indexed_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/modules/with_basis/indexed_element.pxd +13 -0
- sage/modules/with_basis/indexed_element.pyx +1058 -0
- sage/modules/with_basis/invariant.py +1075 -0
- sage/modules/with_basis/morphism.py +1636 -0
- sage/modules/with_basis/representation.py +2939 -0
- sage/modules/with_basis/subquotient.py +685 -0
- sage/numerical/all__sagemath_modules.py +6 -0
- sage/numerical/gauss_legendre.cpython-314-aarch64-linux-musl.so +0 -0
- sage/numerical/gauss_legendre.pyx +381 -0
- sage/numerical/optimize.py +910 -0
- sage/probability/all.py +10 -0
- sage/probability/probability_distribution.cpython-314-aarch64-linux-musl.so +0 -0
- sage/probability/probability_distribution.pyx +1242 -0
- sage/probability/random_variable.py +411 -0
- sage/quadratic_forms/all.py +4 -0
- sage/quadratic_forms/all__sagemath_modules.py +15 -0
- sage/quadratic_forms/binary_qf.py +2042 -0
- sage/quadratic_forms/bqf_class_group.py +748 -0
- sage/quadratic_forms/constructions.py +93 -0
- sage/quadratic_forms/count_local_2.cpython-314-aarch64-linux-musl.so +0 -0
- sage/quadratic_forms/count_local_2.pyx +365 -0
- sage/quadratic_forms/extras.py +195 -0
- sage/quadratic_forms/quadratic_form.py +1753 -0
- sage/quadratic_forms/quadratic_form__count_local_2.py +221 -0
- sage/quadratic_forms/quadratic_form__equivalence_testing.py +708 -0
- sage/quadratic_forms/quadratic_form__evaluate.cpython-314-aarch64-linux-musl.so +0 -0
- sage/quadratic_forms/quadratic_form__evaluate.pyx +139 -0
- sage/quadratic_forms/quadratic_form__local_density_congruence.py +977 -0
- sage/quadratic_forms/quadratic_form__local_field_invariants.py +1072 -0
- sage/quadratic_forms/quadratic_form__neighbors.py +424 -0
- sage/quadratic_forms/quadratic_form__reduction_theory.py +488 -0
- sage/quadratic_forms/quadratic_form__split_local_covering.py +416 -0
- sage/quadratic_forms/quadratic_form__ternary_Tornaria.py +657 -0
- sage/quadratic_forms/quadratic_form__theta.py +352 -0
- sage/quadratic_forms/quadratic_form__variable_substitutions.py +370 -0
- sage/quadratic_forms/random_quadraticform.py +209 -0
- sage/quadratic_forms/ternary.cpython-314-aarch64-linux-musl.so +0 -0
- sage/quadratic_forms/ternary.pyx +1154 -0
- sage/quadratic_forms/ternary_qf.py +2027 -0
- sage/rings/all__sagemath_modules.py +28 -0
- sage/rings/asymptotic/all__sagemath_modules.py +1 -0
- sage/rings/asymptotic/misc.py +1252 -0
- sage/rings/cc.py +4 -0
- sage/rings/cfinite_sequence.py +1306 -0
- sage/rings/complex_conversion.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/complex_conversion.pxd +8 -0
- sage/rings/complex_conversion.pyx +23 -0
- sage/rings/complex_double.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/complex_double.pxd +21 -0
- sage/rings/complex_double.pyx +2654 -0
- sage/rings/complex_mpc.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/complex_mpc.pxd +21 -0
- sage/rings/complex_mpc.pyx +2576 -0
- sage/rings/complex_mpfr.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/complex_mpfr.pxd +18 -0
- sage/rings/complex_mpfr.pyx +3602 -0
- sage/rings/derivation.py +2334 -0
- sage/rings/finite_rings/all__sagemath_modules.py +1 -0
- sage/rings/finite_rings/maps_finite_field.py +191 -0
- sage/rings/function_field/all__sagemath_modules.py +8 -0
- sage/rings/function_field/derivations.py +102 -0
- sage/rings/function_field/derivations_rational.py +132 -0
- sage/rings/function_field/differential.py +853 -0
- sage/rings/function_field/divisor.py +1107 -0
- sage/rings/function_field/drinfeld_modules/action.py +199 -0
- sage/rings/function_field/drinfeld_modules/all.py +1 -0
- sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +673 -0
- sage/rings/function_field/drinfeld_modules/drinfeld_module.py +2087 -0
- sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +1131 -0
- sage/rings/function_field/drinfeld_modules/homset.py +420 -0
- sage/rings/function_field/drinfeld_modules/morphism.py +820 -0
- sage/rings/function_field/hermite_form_polynomial.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/hermite_form_polynomial.pyx +188 -0
- sage/rings/function_field/khuri_makdisi.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/function_field/khuri_makdisi.pyx +935 -0
- sage/rings/invariants/all.py +4 -0
- sage/rings/invariants/invariant_theory.py +4597 -0
- sage/rings/invariants/reconstruction.py +395 -0
- sage/rings/polynomial/all__sagemath_modules.py +17 -0
- sage/rings/polynomial/integer_valued_polynomials.py +1230 -0
- sage/rings/polynomial/laurent_polynomial_mpair.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/laurent_polynomial_mpair.pxd +15 -0
- sage/rings/polynomial/laurent_polynomial_mpair.pyx +2023 -0
- sage/rings/polynomial/ore_function_element.py +952 -0
- sage/rings/polynomial/ore_function_field.py +1028 -0
- sage/rings/polynomial/ore_polynomial_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/ore_polynomial_element.pxd +48 -0
- sage/rings/polynomial/ore_polynomial_element.pyx +3145 -0
- sage/rings/polynomial/ore_polynomial_ring.py +1334 -0
- sage/rings/polynomial/polynomial_real_mpfr_dense.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/polynomial_real_mpfr_dense.pyx +788 -0
- sage/rings/polynomial/q_integer_valued_polynomials.py +1264 -0
- sage/rings/polynomial/skew_polynomial_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/skew_polynomial_element.pxd +9 -0
- sage/rings/polynomial/skew_polynomial_element.pyx +684 -0
- sage/rings/polynomial/skew_polynomial_finite_field.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/skew_polynomial_finite_field.pxd +19 -0
- sage/rings/polynomial/skew_polynomial_finite_field.pyx +1093 -0
- sage/rings/polynomial/skew_polynomial_finite_order.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/polynomial/skew_polynomial_finite_order.pxd +10 -0
- sage/rings/polynomial/skew_polynomial_finite_order.pyx +567 -0
- sage/rings/polynomial/skew_polynomial_ring.py +908 -0
- sage/rings/real_double_element_gsl.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/real_double_element_gsl.pxd +8 -0
- sage/rings/real_double_element_gsl.pyx +794 -0
- sage/rings/real_field.py +58 -0
- sage/rings/real_mpfr.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/real_mpfr.pxd +29 -0
- sage/rings/real_mpfr.pyx +6122 -0
- sage/rings/ring_extension.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/ring_extension.pxd +42 -0
- sage/rings/ring_extension.pyx +2779 -0
- sage/rings/ring_extension_conversion.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/ring_extension_conversion.pxd +16 -0
- sage/rings/ring_extension_conversion.pyx +462 -0
- sage/rings/ring_extension_element.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/ring_extension_element.pxd +21 -0
- sage/rings/ring_extension_element.pyx +1635 -0
- sage/rings/ring_extension_homset.py +64 -0
- sage/rings/ring_extension_morphism.cpython-314-aarch64-linux-musl.so +0 -0
- sage/rings/ring_extension_morphism.pxd +35 -0
- sage/rings/ring_extension_morphism.pyx +920 -0
- sage/schemes/all__sagemath_modules.py +1 -0
- sage/schemes/projective/all__sagemath_modules.py +1 -0
- sage/schemes/projective/coherent_sheaf.py +300 -0
- sage/schemes/projective/cohomology.py +510 -0
- sage/stats/all.py +15 -0
- sage/stats/basic_stats.py +489 -0
- sage/stats/distributions/all.py +7 -0
- sage/stats/distributions/catalog.py +34 -0
- sage/stats/distributions/dgs.h +50 -0
- sage/stats/distributions/dgs.pxd +111 -0
- sage/stats/distributions/dgs_bern.h +400 -0
- sage/stats/distributions/dgs_gauss.h +614 -0
- sage/stats/distributions/dgs_misc.h +104 -0
- sage/stats/distributions/discrete_gaussian_integer.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/distributions/discrete_gaussian_integer.pxd +14 -0
- sage/stats/distributions/discrete_gaussian_integer.pyx +498 -0
- sage/stats/distributions/discrete_gaussian_lattice.py +908 -0
- sage/stats/distributions/discrete_gaussian_polynomial.py +141 -0
- sage/stats/hmm/all.py +15 -0
- sage/stats/hmm/chmm.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/hmm/chmm.pyx +1595 -0
- sage/stats/hmm/distributions.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/hmm/distributions.pxd +29 -0
- sage/stats/hmm/distributions.pyx +531 -0
- sage/stats/hmm/hmm.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/hmm/hmm.pxd +17 -0
- sage/stats/hmm/hmm.pyx +1388 -0
- sage/stats/hmm/util.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/hmm/util.pxd +7 -0
- sage/stats/hmm/util.pyx +165 -0
- sage/stats/intlist.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/intlist.pxd +14 -0
- sage/stats/intlist.pyx +588 -0
- sage/stats/r.py +49 -0
- sage/stats/time_series.cpython-314-aarch64-linux-musl.so +0 -0
- sage/stats/time_series.pxd +6 -0
- sage/stats/time_series.pyx +2546 -0
- sage/tensor/all.py +2 -0
- sage/tensor/modules/all.py +8 -0
- sage/tensor/modules/alternating_contr_tensor.py +761 -0
- sage/tensor/modules/comp.py +5598 -0
- sage/tensor/modules/ext_pow_free_module.py +824 -0
- sage/tensor/modules/finite_rank_free_module.py +3589 -0
- sage/tensor/modules/format_utilities.py +333 -0
- sage/tensor/modules/free_module_alt_form.py +858 -0
- sage/tensor/modules/free_module_automorphism.py +1207 -0
- sage/tensor/modules/free_module_basis.py +1074 -0
- sage/tensor/modules/free_module_element.py +284 -0
- sage/tensor/modules/free_module_homset.py +652 -0
- sage/tensor/modules/free_module_linear_group.py +564 -0
- sage/tensor/modules/free_module_morphism.py +1581 -0
- sage/tensor/modules/free_module_tensor.py +3289 -0
- sage/tensor/modules/reflexive_module.py +386 -0
- sage/tensor/modules/tensor_free_module.py +780 -0
- sage/tensor/modules/tensor_free_submodule.py +538 -0
- sage/tensor/modules/tensor_free_submodule_basis.py +140 -0
- sage/tensor/modules/tensor_with_indices.py +1043 -0
sage/crypto/classical.py
ADDED
|
@@ -0,0 +1,3668 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-modules
|
|
2
|
+
# sage.doctest: needs sage.combinat
|
|
3
|
+
r"""
|
|
4
|
+
Classical Cryptosystems
|
|
5
|
+
|
|
6
|
+
A convenient user interface to various classical ciphers. These include:
|
|
7
|
+
|
|
8
|
+
- affine cipher; see :class:`AffineCryptosystem`
|
|
9
|
+
- Hill or matrix cipher; see :class:`HillCryptosystem`
|
|
10
|
+
- shift cipher; see :class:`ShiftCryptosystem`
|
|
11
|
+
- substitution cipher; see :class:`SubstitutionCryptosystem`
|
|
12
|
+
- transposition cipher; see :class:`TranspositionCryptosystem`
|
|
13
|
+
- Vigenere cipher; see :class:`VigenereCryptosystem`
|
|
14
|
+
|
|
15
|
+
These classical cryptosystems support alphabets such as:
|
|
16
|
+
|
|
17
|
+
- the capital letters of the English alphabet; see
|
|
18
|
+
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
|
|
19
|
+
- the hexadecimal number system; see
|
|
20
|
+
:func:`HexadecimalStrings() <sage.monoids.string_monoid.HexadecimalStrings>`
|
|
21
|
+
- the binary number system; see
|
|
22
|
+
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
|
|
23
|
+
- the octal number system; see
|
|
24
|
+
:func:`OctalStrings() <sage.monoids.string_monoid.OctalStrings>`
|
|
25
|
+
- the radix-64 number system; see
|
|
26
|
+
:func:`Radix64Strings() <sage.monoids.string_monoid.Radix64Strings>`
|
|
27
|
+
|
|
28
|
+
AUTHORS:
|
|
29
|
+
|
|
30
|
+
- David Kohel (2007): initial version with the Hill, substitution,
|
|
31
|
+
transposition, and Vigenere cryptosystems.
|
|
32
|
+
|
|
33
|
+
- Minh Van Nguyen (2009-08): shift cipher, affine cipher
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
#*****************************************************************************
|
|
37
|
+
# Copyright (C) 2007 David Kohel <kohel@maths.usyd.edu.au>
|
|
38
|
+
#
|
|
39
|
+
# This program is free software: you can redistribute it and/or modify
|
|
40
|
+
# it under the terms of the GNU General Public License as published by
|
|
41
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
42
|
+
# (at your option) any later version.
|
|
43
|
+
# http://www.gnu.org/licenses/
|
|
44
|
+
#*****************************************************************************
|
|
45
|
+
|
|
46
|
+
# TODO: check off this todo list:
|
|
47
|
+
# - methods to cryptanalyze the Hill, substitution, transposition, and
|
|
48
|
+
# Vigenere ciphers
|
|
49
|
+
|
|
50
|
+
from random import randint
|
|
51
|
+
|
|
52
|
+
from sage.arith.misc import inverse_mod, xgcd
|
|
53
|
+
from sage.misc.lazy_import import lazy_import
|
|
54
|
+
from sage.monoids.string_monoid import (
|
|
55
|
+
StringMonoid_class,
|
|
56
|
+
AlphabeticStringMonoid)
|
|
57
|
+
from sage.monoids.string_monoid_element import StringMonoidElement
|
|
58
|
+
from sage.monoids.string_ops import strip_encoding
|
|
59
|
+
from sage.rings.integer import Integer
|
|
60
|
+
from sage.rings.integer_ring import ZZ
|
|
61
|
+
from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
|
|
62
|
+
|
|
63
|
+
lazy_import('sage.groups.perm_gps.permgroup_named', 'SymmetricGroup')
|
|
64
|
+
lazy_import('sage.groups.perm_gps.permgroup_element', 'PermutationGroupElement')
|
|
65
|
+
lazy_import('sage.matrix.matrix_space', 'MatrixSpace')
|
|
66
|
+
|
|
67
|
+
from .cryptosystem import SymmetricKeyCryptosystem
|
|
68
|
+
from .classical_cipher import (
|
|
69
|
+
AffineCipher,
|
|
70
|
+
HillCipher,
|
|
71
|
+
ShiftCipher,
|
|
72
|
+
SubstitutionCipher,
|
|
73
|
+
TranspositionCipher,
|
|
74
|
+
VigenereCipher)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AffineCryptosystem(SymmetricKeyCryptosystem):
|
|
78
|
+
r"""
|
|
79
|
+
Create an affine cryptosystem.
|
|
80
|
+
|
|
81
|
+
Let `A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}` be a non-empty alphabet
|
|
82
|
+
consisting of `n` unique elements. Define a mapping
|
|
83
|
+
`f : A \longrightarrow \ZZ / n\ZZ` from the alphabet `A` to
|
|
84
|
+
the set `\ZZ / n\ZZ` of integers modulo `n`, given by
|
|
85
|
+
`f(a_i) = i`. Thus we can identify each element of the alphabet `A`
|
|
86
|
+
with a unique integer `0 \leq i < n`. A key of the affine cipher is an
|
|
87
|
+
ordered pair of integers `(a, b) \in \ZZ / n\ZZ \times \ZZ / n\ZZ` such
|
|
88
|
+
that `\gcd(a, n) = 1`. Therefore the key space is
|
|
89
|
+
`\ZZ / n\ZZ \times \ZZ / n\ZZ`. Since we assume that `A` does not have
|
|
90
|
+
repeated elements, the mapping `f : A \longrightarrow \ZZ/ n\ZZ` is
|
|
91
|
+
bijective. Encryption and decryption functions are both affine functions.
|
|
92
|
+
Let `(a,b)` be a secret key, i.e. an element of the key space, and let
|
|
93
|
+
`p` be a plaintext character and consequently `p \in \ZZ / n\ZZ`. Then
|
|
94
|
+
the ciphertext character `c` corresponding to `p` is given by
|
|
95
|
+
|
|
96
|
+
.. MATH::
|
|
97
|
+
|
|
98
|
+
c \equiv ap + b \pmod{n}
|
|
99
|
+
|
|
100
|
+
Similarly, given a ciphertext character `c \in \ZZ / n\ZZ` and a secret
|
|
101
|
+
key `(a,b)`, we can recover the corresponding plaintext character as
|
|
102
|
+
follows:
|
|
103
|
+
|
|
104
|
+
.. MATH::
|
|
105
|
+
|
|
106
|
+
p \equiv a^{-1} (c - b) \pmod{n}
|
|
107
|
+
|
|
108
|
+
where `a^{-1}` is the inverse of `a` modulo `n`. Use the bijection
|
|
109
|
+
`f : A \longrightarrow \ZZ / n\ZZ` to convert `c` and `p` back to
|
|
110
|
+
elements of the alphabet `A`. Currently, only the following alphabet is
|
|
111
|
+
supported for the affine cipher:
|
|
112
|
+
|
|
113
|
+
- capital letters of the English alphabet as implemented in
|
|
114
|
+
:func:`AlphabeticStrings()
|
|
115
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`
|
|
116
|
+
|
|
117
|
+
EXAMPLES:
|
|
118
|
+
|
|
119
|
+
Encryption and decryption over the capital letters of the English
|
|
120
|
+
alphabet::
|
|
121
|
+
|
|
122
|
+
sage: A = AffineCryptosystem(AlphabeticStrings()); A
|
|
123
|
+
Affine cryptosystem on Free alphabetic string monoid on A-Z
|
|
124
|
+
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
|
|
125
|
+
sage: P
|
|
126
|
+
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
|
|
127
|
+
sage: a, b = (9, 13)
|
|
128
|
+
sage: C = A.enciphering(a, b, P); C
|
|
129
|
+
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
|
|
130
|
+
sage: A.deciphering(a, b, C)
|
|
131
|
+
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
|
|
132
|
+
sage: A.deciphering(a, b, C) == P
|
|
133
|
+
True
|
|
134
|
+
|
|
135
|
+
We can also use functional notation to work through the previous
|
|
136
|
+
example::
|
|
137
|
+
|
|
138
|
+
sage: A = AffineCryptosystem(AlphabeticStrings()); A
|
|
139
|
+
Affine cryptosystem on Free alphabetic string monoid on A-Z
|
|
140
|
+
sage: P = A.encoding("The affine cryptosystem generalizes the shift cipher.")
|
|
141
|
+
sage: P
|
|
142
|
+
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
|
|
143
|
+
sage: a, b = (9, 13)
|
|
144
|
+
sage: E = A(a, b); E
|
|
145
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
146
|
+
sage: C = E(P); C
|
|
147
|
+
CYXNGGHAXFKVSCJTVTCXRPXAXKNIHEXTCYXTYHGCFHSYXK
|
|
148
|
+
sage: aInv, bInv = A.inverse_key(a, b)
|
|
149
|
+
sage: D = A(aInv, bInv); D
|
|
150
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
151
|
+
sage: D(C)
|
|
152
|
+
THEAFFINECRYPTOSYSTEMGENERALIZESTHESHIFTCIPHER
|
|
153
|
+
sage: D(C) == P
|
|
154
|
+
True
|
|
155
|
+
sage: D(C) == P == D(E(P))
|
|
156
|
+
True
|
|
157
|
+
|
|
158
|
+
Encrypting the ciphertext with the inverse key also produces the
|
|
159
|
+
plaintext::
|
|
160
|
+
|
|
161
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
162
|
+
sage: P = A.encoding("Encrypt with inverse key.")
|
|
163
|
+
sage: a, b = (11, 8)
|
|
164
|
+
sage: C = A.enciphering(a, b, P)
|
|
165
|
+
sage: P; C
|
|
166
|
+
ENCRYPTWITHINVERSEKEY
|
|
167
|
+
AVENMRJQSJHSVFANYAOAM
|
|
168
|
+
sage: aInv, bInv = A.inverse_key(a, b)
|
|
169
|
+
sage: A.enciphering(aInv, bInv, C)
|
|
170
|
+
ENCRYPTWITHINVERSEKEY
|
|
171
|
+
sage: A.enciphering(aInv, bInv, C) == P
|
|
172
|
+
True
|
|
173
|
+
|
|
174
|
+
For a secret key `(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ`, if `a = 1` then
|
|
175
|
+
any affine cryptosystem with key `(1, b)` for any `b \in \ZZ/n\ZZ` is
|
|
176
|
+
a shift cryptosystem. Here is how we can create a Caesar cipher using
|
|
177
|
+
an affine cipher::
|
|
178
|
+
|
|
179
|
+
sage: caesar = AffineCryptosystem(AlphabeticStrings())
|
|
180
|
+
sage: a, b = (1, 3)
|
|
181
|
+
sage: P = caesar.encoding("abcdef"); P
|
|
182
|
+
ABCDEF
|
|
183
|
+
sage: C = caesar.enciphering(a, b, P); C
|
|
184
|
+
DEFGHI
|
|
185
|
+
sage: caesar.deciphering(a, b, C) == P
|
|
186
|
+
True
|
|
187
|
+
|
|
188
|
+
Any affine cipher with keys of the form
|
|
189
|
+
`(a,0) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is called a decimation cipher on
|
|
190
|
+
the Roman alphabet, or decimation cipher for short::
|
|
191
|
+
|
|
192
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
193
|
+
sage: P = A.encoding("A decimation cipher is a specialized affine cipher.")
|
|
194
|
+
sage: a, b = (17, 0)
|
|
195
|
+
sage: C = A.enciphering(a, b, P)
|
|
196
|
+
sage: P; C
|
|
197
|
+
ADECIMATIONCIPHERISASPECIALIZEDAFFINECIPHER
|
|
198
|
+
AZQIGWALGENIGVPQDGUAUVQIGAFGJQZAHHGNQIGVPQD
|
|
199
|
+
sage: A.deciphering(a, b, C) == P
|
|
200
|
+
True
|
|
201
|
+
|
|
202
|
+
Generate a random key for encryption and decryption::
|
|
203
|
+
|
|
204
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
205
|
+
sage: P = A.encoding("An affine cipher with a random key.")
|
|
206
|
+
sage: a, b = A.random_key()
|
|
207
|
+
sage: C = A.enciphering(a, b, P)
|
|
208
|
+
sage: A.deciphering(a, b, C) == P
|
|
209
|
+
True
|
|
210
|
+
|
|
211
|
+
TESTS:
|
|
212
|
+
|
|
213
|
+
The binary number system is currently not a supported alphabet of
|
|
214
|
+
this affine cryptosystem::
|
|
215
|
+
|
|
216
|
+
sage: AffineCryptosystem(BinaryStrings())
|
|
217
|
+
Traceback (most recent call last):
|
|
218
|
+
...
|
|
219
|
+
TypeError: A (= Free binary string monoid) is not supported as a cipher domain of this affine cryptosystem.
|
|
220
|
+
|
|
221
|
+
Nor are the octal, hexadecimal, and radix-64 number systems supported::
|
|
222
|
+
|
|
223
|
+
sage: AffineCryptosystem(OctalStrings())
|
|
224
|
+
Traceback (most recent call last):
|
|
225
|
+
...
|
|
226
|
+
TypeError: A (= Free octal string monoid) is not supported as a cipher domain of this affine cryptosystem.
|
|
227
|
+
sage: AffineCryptosystem(HexadecimalStrings())
|
|
228
|
+
Traceback (most recent call last):
|
|
229
|
+
...
|
|
230
|
+
TypeError: A (= Free hexadecimal string monoid) is not supported as a cipher domain of this affine cryptosystem.
|
|
231
|
+
sage: AffineCryptosystem(Radix64Strings())
|
|
232
|
+
Traceback (most recent call last):
|
|
233
|
+
...
|
|
234
|
+
TypeError: A (= Free radix 64 string monoid) is not supported as a cipher domain of this affine cryptosystem.
|
|
235
|
+
|
|
236
|
+
A secret key `(a,b)` must be an element of `\ZZ/n\ZZ \times \ZZ/n\ZZ` with
|
|
237
|
+
`\gcd(a,n) = 1`. This rules out the case `a = 0` irrespective of the
|
|
238
|
+
value of `b`. For the upper-case letters of the English alphabet, where
|
|
239
|
+
the alphabet size is `n = 26`, `a` cannot take on any even value::
|
|
240
|
+
|
|
241
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
242
|
+
sage: A(0, 1)
|
|
243
|
+
Traceback (most recent call last):
|
|
244
|
+
...
|
|
245
|
+
ValueError: (a, b) = (0, 1) is outside the range of acceptable values for a key of this affine cryptosystem.
|
|
246
|
+
sage: A(2, 1)
|
|
247
|
+
Traceback (most recent call last):
|
|
248
|
+
...
|
|
249
|
+
ValueError: (a, b) = (2, 1) is outside the range of acceptable values for a key of this affine cryptosystem.
|
|
250
|
+
|
|
251
|
+
REFERENCES:
|
|
252
|
+
|
|
253
|
+
- [Sti2006]_
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def __init__(self, A):
|
|
257
|
+
r"""
|
|
258
|
+
See ``AffineCryptosystem`` for full documentation.
|
|
259
|
+
|
|
260
|
+
INPUT:
|
|
261
|
+
|
|
262
|
+
- ``A`` -- string monoid over some alphabet; this is the non-empty
|
|
263
|
+
alphabet over which the plaintext and ciphertext spaces
|
|
264
|
+
are defined
|
|
265
|
+
|
|
266
|
+
OUTPUT: an affine cryptosystem over the alphabet ``A``
|
|
267
|
+
|
|
268
|
+
EXAMPLES:
|
|
269
|
+
|
|
270
|
+
Testing of dumping and loading objects::
|
|
271
|
+
|
|
272
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
273
|
+
sage: A == loads(dumps(A))
|
|
274
|
+
True
|
|
275
|
+
"""
|
|
276
|
+
# sanity check
|
|
277
|
+
if not isinstance(A, AlphabeticStringMonoid):
|
|
278
|
+
raise TypeError("A (= %s) is not supported as a cipher domain of this affine cryptosystem." % A)
|
|
279
|
+
# List L of invertible linear coefficients modulo n, where n is the
|
|
280
|
+
# alphabet size. Each e in L satisfies gcd(e, n) = 1.
|
|
281
|
+
n = Integer(A.ngens())
|
|
282
|
+
self._invertible_A = n.coprime_integers(n)
|
|
283
|
+
# Initialize the affine cryptosystem with the plaintext, ciphertext,
|
|
284
|
+
# and key spaces.
|
|
285
|
+
SymmetricKeyCryptosystem.__init__(
|
|
286
|
+
self, A, A,
|
|
287
|
+
key_space=(IntegerModRing(A.ngens()), IntegerModRing(A.ngens())))
|
|
288
|
+
|
|
289
|
+
def __call__(self, a, b):
|
|
290
|
+
r"""
|
|
291
|
+
Create an affine cipher with secret key ``(a,b)``.
|
|
292
|
+
|
|
293
|
+
INPUT:
|
|
294
|
+
|
|
295
|
+
- ``(a, b)`` -- a secret key; this key is used for both encryption and
|
|
296
|
+
decryption. For the affine cryptosystem whose plaintext and
|
|
297
|
+
ciphertext spaces are `A`, a key is an ordered pair
|
|
298
|
+
`(a,b) \in \ZZ / n\ZZ \times \ZZ / n\ZZ` where `n` is the size or
|
|
299
|
+
cardinality of the set `A` and `\gcd(a,n) = 1`.
|
|
300
|
+
|
|
301
|
+
OUTPUT:
|
|
302
|
+
|
|
303
|
+
- An affine cipher with secret key ``(a,b)``.
|
|
304
|
+
|
|
305
|
+
EXAMPLES::
|
|
306
|
+
|
|
307
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
308
|
+
sage: P = A.encoding("Fine here, fine there."); P
|
|
309
|
+
FINEHEREFINETHERE
|
|
310
|
+
sage: a, b = (17, 3)
|
|
311
|
+
sage: E = A(a, b); E
|
|
312
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
313
|
+
sage: E(P)
|
|
314
|
+
KJQTSTGTKJQTOSTGT
|
|
315
|
+
sage: C = E(P)
|
|
316
|
+
sage: C
|
|
317
|
+
KJQTSTGTKJQTOSTGT
|
|
318
|
+
sage: aInv, bInv = A.inverse_key(a, b)
|
|
319
|
+
sage: D = A(aInv, bInv); D
|
|
320
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
321
|
+
sage: P == D(C)
|
|
322
|
+
True
|
|
323
|
+
sage: D(E(P))
|
|
324
|
+
FINEHEREFINETHERE
|
|
325
|
+
|
|
326
|
+
TESTS:
|
|
327
|
+
|
|
328
|
+
The key must be an ordered pair
|
|
329
|
+
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
|
|
330
|
+
plaintext and ciphertext spaces. Furthermore, `a` must be
|
|
331
|
+
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
|
|
332
|
+
|
|
333
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
334
|
+
sage: A(2, 3)
|
|
335
|
+
Traceback (most recent call last):
|
|
336
|
+
...
|
|
337
|
+
ValueError: (a, b) = (2, 3) is outside the range of acceptable values for a key of this affine cryptosystem.
|
|
338
|
+
"""
|
|
339
|
+
# Sanity check: the key K = (a,b) must be an element of
|
|
340
|
+
# ZZ/nZZ x ZZ/nZZ where n is the size of the plaintext and ciphertext
|
|
341
|
+
# spaces. For the affine cryptosystem, these two spaces are the
|
|
342
|
+
# same alphabet.
|
|
343
|
+
try:
|
|
344
|
+
n = self.alphabet_size()
|
|
345
|
+
# If a is an element of the multiplicative group G of ZZ/nZZ, then
|
|
346
|
+
# gcd(a,n) = 1. So here we don't need to explicitly test that
|
|
347
|
+
# a is coprime to n since we assume that the list
|
|
348
|
+
# self._invertible_A contains all the elements of G.
|
|
349
|
+
if (a in self._invertible_A) and (0 <= b < n):
|
|
350
|
+
return AffineCipher(self, key=(a,b))
|
|
351
|
+
else:
|
|
352
|
+
raise ValueError
|
|
353
|
+
except Exception:
|
|
354
|
+
raise ValueError("(a, b) = (%s, %s) is outside the range of acceptable values for a key of this affine cryptosystem." % (a, b))
|
|
355
|
+
|
|
356
|
+
def _repr_(self):
|
|
357
|
+
r"""
|
|
358
|
+
Return the string representation of ``self``.
|
|
359
|
+
|
|
360
|
+
EXAMPLES::
|
|
361
|
+
|
|
362
|
+
sage: A = AffineCryptosystem(AlphabeticStrings()); A
|
|
363
|
+
Affine cryptosystem on Free alphabetic string monoid on A-Z
|
|
364
|
+
"""
|
|
365
|
+
# The affine cipher has the plaintext and ciphertext spaces defined
|
|
366
|
+
# over the same non-empty alphabet. The cipher domain is the same
|
|
367
|
+
# as the alphabet used for the plaintext and ciphertext spaces.
|
|
368
|
+
return "Affine cryptosystem on %s" % self.cipher_domain()
|
|
369
|
+
|
|
370
|
+
def rank_by_chi_square(self, C, pdict):
|
|
371
|
+
r"""
|
|
372
|
+
Use the chi-square statistic to rank all possible keys. Currently,
|
|
373
|
+
this method only applies to the capital letters of the English
|
|
374
|
+
alphabet.
|
|
375
|
+
|
|
376
|
+
ALGORITHM:
|
|
377
|
+
|
|
378
|
+
Consider a non-empty alphabet `A` consisting of `n`
|
|
379
|
+
elements, and let `C` be a ciphertext encoded using elements of
|
|
380
|
+
`A`. The plaintext `P` corresponding to `C` is also encoded using
|
|
381
|
+
elements of `A`. Let `M` be a candidate decipherment of `C`,
|
|
382
|
+
i.e. `M` is the result of attempting to decrypt `C` using a key
|
|
383
|
+
`(a,b)` which is not necessarily the same key used to encrypt `P`.
|
|
384
|
+
Suppose `F_A(e)` is the characteristic frequency probability of
|
|
385
|
+
`e \in A` and let `F_M(e)` be the message frequency probability with
|
|
386
|
+
respect to `M`. The characteristic frequency probability
|
|
387
|
+
distribution of an alphabet is the expected frequency probability
|
|
388
|
+
distribution for that alphabet. The message frequency probability
|
|
389
|
+
distribution of `M` provides a distribution of the ratio of character
|
|
390
|
+
occurrences over message length. One can interpret the
|
|
391
|
+
characteristic frequency probability `F_A(e)` as the expected
|
|
392
|
+
probability, while the message frequency probability `F_M(e)` is
|
|
393
|
+
the observed probability. If `M` is of length `L`, then the observed
|
|
394
|
+
frequency of `e \in A` is
|
|
395
|
+
|
|
396
|
+
.. MATH::
|
|
397
|
+
|
|
398
|
+
O_M(e)
|
|
399
|
+
=
|
|
400
|
+
F_M(e) \cdot L
|
|
401
|
+
|
|
402
|
+
and the expected frequency of `e \in A` is
|
|
403
|
+
|
|
404
|
+
.. MATH::
|
|
405
|
+
|
|
406
|
+
E_A(e)
|
|
407
|
+
=
|
|
408
|
+
F_A(e) \cdot L
|
|
409
|
+
|
|
410
|
+
The chi-square rank `R_{\chi^2}(M)` of `M` corresponding to a key
|
|
411
|
+
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is given by
|
|
412
|
+
|
|
413
|
+
.. MATH::
|
|
414
|
+
|
|
415
|
+
R_{\chi^2}(M)
|
|
416
|
+
=
|
|
417
|
+
\sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2}
|
|
418
|
+
{E_A(e)}
|
|
419
|
+
|
|
420
|
+
Cryptanalysis by exhaustive key search produces a candidate
|
|
421
|
+
decipherment `M_{a,b}` for each possible key `(a,b)`. For a set
|
|
422
|
+
`D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}`
|
|
423
|
+
of all candidate decipherments corresponding to a ciphertext `C`,
|
|
424
|
+
the smaller is the rank `R_{\chi^2}(M_{a_i,b_i})` the more likely
|
|
425
|
+
that `(a_i,b_i)` is the secret key. This key ranking method is
|
|
426
|
+
based on the Pearson chi-square test [PearsonTest]_.
|
|
427
|
+
|
|
428
|
+
INPUT:
|
|
429
|
+
|
|
430
|
+
- ``C`` -- the ciphertext, a non-empty string. The ciphertext
|
|
431
|
+
must be encoded using the upper-case letters of the English
|
|
432
|
+
alphabet.
|
|
433
|
+
|
|
434
|
+
- ``pdict`` -- dictionary of key, possible plaintext
|
|
435
|
+
pairs. This should be the output of :func:`brute_force` with
|
|
436
|
+
``ranking="none"``.
|
|
437
|
+
|
|
438
|
+
OUTPUT:
|
|
439
|
+
|
|
440
|
+
- A list ranking the most likely keys first. Each element of the
|
|
441
|
+
list is a tuple of key, possible plaintext pairs.
|
|
442
|
+
|
|
443
|
+
EXAMPLES:
|
|
444
|
+
|
|
445
|
+
Use the chi-square statistic to rank all possible keys and their
|
|
446
|
+
corresponding decipherment::
|
|
447
|
+
|
|
448
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
449
|
+
sage: a, b = (3, 7)
|
|
450
|
+
sage: P = A.encoding("Line.")
|
|
451
|
+
sage: C = A.enciphering(a, b, P)
|
|
452
|
+
sage: Plist = A.brute_force(C)
|
|
453
|
+
sage: Rank = A.rank_by_chi_square(C, Plist)
|
|
454
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
455
|
+
<BLANKLINE>
|
|
456
|
+
[((1, 1), NETS),
|
|
457
|
+
((3, 7), LINE),
|
|
458
|
+
((17, 20), STAD),
|
|
459
|
+
((5, 2), SLOT),
|
|
460
|
+
((5, 5), HADI),
|
|
461
|
+
((9, 25), TSLI),
|
|
462
|
+
((17, 15), DELO),
|
|
463
|
+
((15, 6), ETUN),
|
|
464
|
+
((21, 8), ELID),
|
|
465
|
+
((7, 17), HCTE)]
|
|
466
|
+
|
|
467
|
+
As more ciphertext is available, the reliability of the chi-square
|
|
468
|
+
ranking function increases::
|
|
469
|
+
|
|
470
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
471
|
+
sage: a, b = (11, 24)
|
|
472
|
+
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
|
|
473
|
+
sage: C = A.enciphering(a, b, P)
|
|
474
|
+
sage: Plist = A.brute_force(C)
|
|
475
|
+
sage: Rank = A.rank_by_chi_square(C, Plist)
|
|
476
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
477
|
+
<BLANKLINE>
|
|
478
|
+
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
|
|
479
|
+
((17, 9), INURFSBFLLHRFDLBNSFDUYNSBHEDNUYNSTSVGEHUHIVLDL),
|
|
480
|
+
((9, 18), RMFIUHYUOOSIUWOYMHUWFBMHYSVWMFBMHGHETVSFSREOWO),
|
|
481
|
+
((15, 12), VSTACPUCOOGACYOUSPCYTBSPUGNYSTBSPEPIRNGTGVIOYO),
|
|
482
|
+
((3, 22), PAFOYLKYGGSOYEGKALYEFTALKSBEAFTALILCVBSFSPCGEG),
|
|
483
|
+
((25, 3), OHSRNADNPPFRNVPDHANVSCHADFEVHSCHAJABWEFSFOBPVP),
|
|
484
|
+
((7, 25), GHYNVIPVRRLNVFRPHIVFYEHIPLAFHYEHIDITQALYLGTRFR),
|
|
485
|
+
((5, 2), NEHCIVKISSUCIWSKEVIWHFEVKUPWEHFEVOVABPUHUNASWS),
|
|
486
|
+
((15, 25), IFGNPCHPBBTNPLBHFCPLGOFCHTALFGOFCRCVEATGTIVBLB),
|
|
487
|
+
((9, 6), BWPSERIEYYCSEGYIWREGPLWRICFGWPLWRQRODFCPCBOYGY)]
|
|
488
|
+
|
|
489
|
+
TESTS:
|
|
490
|
+
|
|
491
|
+
The ciphertext cannot be an empty string::
|
|
492
|
+
|
|
493
|
+
sage: A.rank_by_chi_square("", Plist)
|
|
494
|
+
Traceback (most recent call last):
|
|
495
|
+
...
|
|
496
|
+
AttributeError: 'str' object has no attribute 'parent'...
|
|
497
|
+
sage: A.rank_by_chi_square(A.encoding(""), Plist)
|
|
498
|
+
Traceback (most recent call last):
|
|
499
|
+
...
|
|
500
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
501
|
+
sage: A.rank_by_chi_square(A.encoding(" "), Plist)
|
|
502
|
+
Traceback (most recent call last):
|
|
503
|
+
...
|
|
504
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
505
|
+
|
|
506
|
+
The ciphertext must be encoded using the capital letters of the
|
|
507
|
+
English alphabet as implemented in
|
|
508
|
+
:func:`AlphabeticStrings()
|
|
509
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`::
|
|
510
|
+
|
|
511
|
+
sage: H = HexadecimalStrings()
|
|
512
|
+
sage: A.rank_by_chi_square(H.encoding("shift"), Plist)
|
|
513
|
+
Traceback (most recent call last):
|
|
514
|
+
...
|
|
515
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
516
|
+
sage: B = BinaryStrings()
|
|
517
|
+
sage: A.rank_by_chi_square(B.encoding("shift"), Plist)
|
|
518
|
+
Traceback (most recent call last):
|
|
519
|
+
...
|
|
520
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
521
|
+
|
|
522
|
+
The dictionary ``pdict`` cannot be empty::
|
|
523
|
+
|
|
524
|
+
sage: A.rank_by_chi_square(C, {})
|
|
525
|
+
Traceback (most recent call last):
|
|
526
|
+
...
|
|
527
|
+
KeyError: (1, 0)
|
|
528
|
+
"""
|
|
529
|
+
# NOTE: the code here is very similar to that in the method
|
|
530
|
+
# rank_by_chi_square() of the class ShiftCryptosystem. The most
|
|
531
|
+
# significant change in the code below is in how the secret key (a,b)
|
|
532
|
+
# is processed.
|
|
533
|
+
|
|
534
|
+
# sanity check
|
|
535
|
+
from sage.monoids.string_monoid import AlphabeticStrings
|
|
536
|
+
if not isinstance(C.parent(), AlphabeticStringMonoid):
|
|
537
|
+
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
|
|
538
|
+
if str(C) == "":
|
|
539
|
+
raise ValueError("The ciphertext must be a non-empty string.")
|
|
540
|
+
|
|
541
|
+
# compute the rank of each key
|
|
542
|
+
AS = AlphabeticStrings()
|
|
543
|
+
# the alphabet in question
|
|
544
|
+
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
|
|
545
|
+
StrAlph = str(Alph)
|
|
546
|
+
# message length
|
|
547
|
+
L = len(C)
|
|
548
|
+
# expected frequency tally
|
|
549
|
+
EA = AS.characteristic_frequency()
|
|
550
|
+
for e in EA:
|
|
551
|
+
EA[e] *= L
|
|
552
|
+
# Compute the rank R_{chi^2}(M) of M with secret key (a,b).
|
|
553
|
+
Rank = []
|
|
554
|
+
for a in self._invertible_A:
|
|
555
|
+
for b in range(self.alphabet_size()):
|
|
556
|
+
# observed frequency tally
|
|
557
|
+
OM = pdict[(a, b)].frequency_distribution().function()
|
|
558
|
+
for e in Alph:
|
|
559
|
+
if e in OM:
|
|
560
|
+
OM[e] *= L
|
|
561
|
+
else:
|
|
562
|
+
OM.setdefault(e, 0.0)
|
|
563
|
+
# the rank R_{chi^2}(M) of M with secret key (a,b)
|
|
564
|
+
RMab = [(OM[AS(e)] - EA[e])**2 / EA[e] for e in StrAlph]
|
|
565
|
+
Rank.append((sum(RMab), (a, b)))
|
|
566
|
+
# Sort in non-decreasing order of chi-square statistic. It's
|
|
567
|
+
# possible that two different keys share the same chi-square
|
|
568
|
+
# statistic.
|
|
569
|
+
Rank = sorted(Rank)
|
|
570
|
+
RankedList = []
|
|
571
|
+
# NOTE: each secret key is a tuple (a,b). So key[0] indexes a,
|
|
572
|
+
# and key[1] indexes b. The value of val is not used at all, making
|
|
573
|
+
# it redundant to access val in the first place. The following line
|
|
574
|
+
# of code is written with readability in mind.
|
|
575
|
+
[RankedList.append((key, pdict[(key[0], key[1])]))
|
|
576
|
+
for val, key in Rank]
|
|
577
|
+
return RankedList
|
|
578
|
+
|
|
579
|
+
def rank_by_squared_differences(self, C, pdict):
|
|
580
|
+
r"""
|
|
581
|
+
Use the squared-differences measure to rank all possible keys.
|
|
582
|
+
Currently, this method only applies to the capital letters of
|
|
583
|
+
the English alphabet.
|
|
584
|
+
|
|
585
|
+
ALGORITHM:
|
|
586
|
+
|
|
587
|
+
Consider a non-empty alphabet `A` consisting of `n`
|
|
588
|
+
elements, and let `C` be a ciphertext encoded using elements of
|
|
589
|
+
`A`. The plaintext `P` corresponding to `C` is also encoded using
|
|
590
|
+
elements of `A`. Let `M` be a candidate decipherment of `C`,
|
|
591
|
+
i.e. `M` is the result of attempting to decrypt `C` using a key
|
|
592
|
+
`(a,b)` which is not necessarily the same key used to encrypt `P`.
|
|
593
|
+
Suppose `F_A(e)` is the characteristic frequency probability of
|
|
594
|
+
`e \in A` and let `F_M(e)` be the message frequency probability with
|
|
595
|
+
respect to `M`. The characteristic frequency probability
|
|
596
|
+
distribution of an alphabet is the expected frequency probability
|
|
597
|
+
distribution for that alphabet. The message frequency probability
|
|
598
|
+
distribution of `M` provides a distribution of the ratio of character
|
|
599
|
+
occurrences over message length. One can interpret the
|
|
600
|
+
characteristic frequency probability `F_A(e)` as the expected
|
|
601
|
+
probability, while the message frequency probability `F_M(e)` is
|
|
602
|
+
the observed probability. If `M` is of length `L`, then the observed
|
|
603
|
+
frequency of `e \in A` is
|
|
604
|
+
|
|
605
|
+
.. MATH::
|
|
606
|
+
|
|
607
|
+
O_M(e)
|
|
608
|
+
=
|
|
609
|
+
F_M(e) \cdot L
|
|
610
|
+
|
|
611
|
+
and the expected frequency of `e \in A` is
|
|
612
|
+
|
|
613
|
+
.. MATH::
|
|
614
|
+
|
|
615
|
+
E_A(e)
|
|
616
|
+
=
|
|
617
|
+
F_A(e) \cdot L
|
|
618
|
+
|
|
619
|
+
The squared-differences, or residual sum of squares, rank
|
|
620
|
+
`R_{RSS}(M)` of `M` corresponding to a key
|
|
621
|
+
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` is given by
|
|
622
|
+
|
|
623
|
+
.. MATH::
|
|
624
|
+
|
|
625
|
+
R_{RSS}(M)
|
|
626
|
+
=
|
|
627
|
+
\sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2
|
|
628
|
+
|
|
629
|
+
Cryptanalysis by exhaustive key search produces a candidate
|
|
630
|
+
decipherment `M_{a,b}` for each possible key `(a,b)`. For a set
|
|
631
|
+
`D = \big\{M_{a_1,b_1}, M_{a_2,b_2}, \dots, M_{a_k,b_k} \big\}`
|
|
632
|
+
of all candidate decipherments corresponding to a ciphertext `C`,
|
|
633
|
+
the smaller is the rank `R_{RSS}(M_{a_i,b_i})` the more likely
|
|
634
|
+
that `(a_i,b_i)` is the secret key. This key ranking method is
|
|
635
|
+
based on the residual sum of squares measure [RSS]_.
|
|
636
|
+
|
|
637
|
+
INPUT:
|
|
638
|
+
|
|
639
|
+
- ``C`` -- the ciphertext, a non-empty string. The ciphertext
|
|
640
|
+
must be encoded using the upper-case letters of the English
|
|
641
|
+
alphabet.
|
|
642
|
+
|
|
643
|
+
- ``pdict`` -- dictionary of key, possible plaintext
|
|
644
|
+
pairs. This should be the output of :func:`brute_force` with
|
|
645
|
+
``ranking="none"``.
|
|
646
|
+
|
|
647
|
+
OUTPUT:
|
|
648
|
+
|
|
649
|
+
- A list ranking the most likely keys first. Each element of the
|
|
650
|
+
list is a tuple of key, possible plaintext pairs.
|
|
651
|
+
|
|
652
|
+
EXAMPLES:
|
|
653
|
+
|
|
654
|
+
Use the method of squared differences to rank all possible keys
|
|
655
|
+
and their corresponding decipherment::
|
|
656
|
+
|
|
657
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
658
|
+
sage: a, b = (3, 7)
|
|
659
|
+
sage: P = A.encoding("Line.")
|
|
660
|
+
sage: C = A.enciphering(a, b, P)
|
|
661
|
+
sage: Plist = A.brute_force(C)
|
|
662
|
+
sage: Rank = A.rank_by_squared_differences(C, Plist)
|
|
663
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
664
|
+
<BLANKLINE>
|
|
665
|
+
[((1, 1), NETS),
|
|
666
|
+
((15, 6), ETUN),
|
|
667
|
+
((7, 17), HCTE),
|
|
668
|
+
((3, 7), LINE),
|
|
669
|
+
((17, 15), DELO),
|
|
670
|
+
((9, 4), EDWT),
|
|
671
|
+
((9, 9), POHE),
|
|
672
|
+
((21, 8), ELID),
|
|
673
|
+
((17, 20), STAD),
|
|
674
|
+
((7, 18), SNEP)]
|
|
675
|
+
|
|
676
|
+
As more ciphertext is available, the reliability of the
|
|
677
|
+
squared-differences ranking function increases::
|
|
678
|
+
|
|
679
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
680
|
+
sage: a, b = (11, 24)
|
|
681
|
+
sage: P = A.encoding("Longer message is more information for cryptanalysis.")
|
|
682
|
+
sage: C = A.enciphering(a, b, P)
|
|
683
|
+
sage: Plist = A.brute_force(C)
|
|
684
|
+
sage: Rank = A.rank_by_squared_differences(C, Plist)
|
|
685
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
686
|
+
<BLANKLINE>
|
|
687
|
+
[((11, 24), LONGERMESSAGEISMOREINFORMATIONFORCRYPTANALYSIS),
|
|
688
|
+
((9, 14), DYRUGTKGAAEUGIAKYTGIRNYTKEHIYRNYTSTQFHEREDQAIA),
|
|
689
|
+
((23, 24), DSNEUHIUMMAEUOMISHUONZSHIAROSNZSHKHQXRANADQMOM),
|
|
690
|
+
((23, 1), ETOFVIJVNNBFVPNJTIVPOATIJBSPTOATILIRYSBOBERNPN),
|
|
691
|
+
((21, 16), VEBGANYAQQOGAMQYENAMBDENYOTMEBDENUNIHTOBOVIQMQ),
|
|
692
|
+
((7, 12), TULAIVCIEEYAISECUVISLRUVCYNSULRUVQVGDNYLYTGESE),
|
|
693
|
+
((5, 20), ZQTOUHWUEEGOUIEWQHUITRQHWGBIQTRQHAHMNBGTGZMEIE),
|
|
694
|
+
((21, 8), JSPUOBMOEECUOAEMSBOAPRSBMCHASPRSBIBWVHCPCJWEAE),
|
|
695
|
+
((25, 7), SLWVREHRTTJVRZTHLERZWGLEHJIZLWGLENEFAIJWJSFTZT),
|
|
696
|
+
((25, 15), ATEDZMPZBBRDZHBPTMZHEOTMPRQHTEOTMVMNIQRERANBHB)]
|
|
697
|
+
|
|
698
|
+
TESTS:
|
|
699
|
+
|
|
700
|
+
The ciphertext cannot be an empty string::
|
|
701
|
+
|
|
702
|
+
sage: A.rank_by_squared_differences("", Plist)
|
|
703
|
+
Traceback (most recent call last):
|
|
704
|
+
...
|
|
705
|
+
AttributeError: 'str' object has no attribute 'parent'...
|
|
706
|
+
sage: A.rank_by_squared_differences(A.encoding(""), Plist)
|
|
707
|
+
Traceback (most recent call last):
|
|
708
|
+
...
|
|
709
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
710
|
+
sage: A.rank_by_squared_differences(A.encoding(" "), Plist)
|
|
711
|
+
Traceback (most recent call last):
|
|
712
|
+
...
|
|
713
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
714
|
+
|
|
715
|
+
The ciphertext must be encoded using the capital letters of the
|
|
716
|
+
English alphabet as implemented in
|
|
717
|
+
:func:`AlphabeticStrings()
|
|
718
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`::
|
|
719
|
+
|
|
720
|
+
sage: H = HexadecimalStrings()
|
|
721
|
+
sage: A.rank_by_squared_differences(H.encoding("line"), Plist)
|
|
722
|
+
Traceback (most recent call last):
|
|
723
|
+
...
|
|
724
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
725
|
+
sage: B = BinaryStrings()
|
|
726
|
+
sage: A.rank_by_squared_differences(B.encoding("line"), Plist)
|
|
727
|
+
Traceback (most recent call last):
|
|
728
|
+
...
|
|
729
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
730
|
+
|
|
731
|
+
The dictionary ``pdict`` cannot be empty::
|
|
732
|
+
|
|
733
|
+
sage: A.rank_by_squared_differences(C, {})
|
|
734
|
+
Traceback (most recent call last):
|
|
735
|
+
...
|
|
736
|
+
KeyError: (1, 0)
|
|
737
|
+
"""
|
|
738
|
+
# NOTE: the code here is very similar to that in the method
|
|
739
|
+
# rank_by_squared_differences() of the class ShiftCryptosystem.
|
|
740
|
+
# The most significant change in the code below is in how the
|
|
741
|
+
# secret key (a,b) is processed.
|
|
742
|
+
|
|
743
|
+
# sanity check
|
|
744
|
+
from sage.monoids.string_monoid import AlphabeticStrings
|
|
745
|
+
if not isinstance(C.parent(), AlphabeticStringMonoid):
|
|
746
|
+
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
|
|
747
|
+
if str(C) == "":
|
|
748
|
+
raise ValueError("The ciphertext must be a non-empty string.")
|
|
749
|
+
|
|
750
|
+
# compute the rank of each key
|
|
751
|
+
AS = AlphabeticStrings()
|
|
752
|
+
# the alphabet in question
|
|
753
|
+
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
|
|
754
|
+
StrAlph = str(Alph)
|
|
755
|
+
# message length
|
|
756
|
+
L = len(C)
|
|
757
|
+
# expected frequency tally
|
|
758
|
+
EA = AS.characteristic_frequency()
|
|
759
|
+
for e in EA:
|
|
760
|
+
EA[e] *= L
|
|
761
|
+
# Compute the rank R_{RSS}(M) of M with secret key (a,b).
|
|
762
|
+
Rank = []
|
|
763
|
+
for a in self._invertible_A:
|
|
764
|
+
for b in range(self.alphabet_size()):
|
|
765
|
+
# observed frequency tally
|
|
766
|
+
OM = pdict[(a, b)].frequency_distribution().function()
|
|
767
|
+
for e in Alph:
|
|
768
|
+
if e in OM:
|
|
769
|
+
OM[e] *= L
|
|
770
|
+
else:
|
|
771
|
+
OM.setdefault(e, 0.0)
|
|
772
|
+
# the rank R_{RSS}(M) of M with secret key (a,b)
|
|
773
|
+
RMab = [(OM[AS(e)] - EA[e])**2 for e in StrAlph]
|
|
774
|
+
Rank.append((sum(RMab), (a, b)))
|
|
775
|
+
# Sort in non-decreasing order of squared-differences statistic. It's
|
|
776
|
+
# possible that two different keys share the same squared-differences
|
|
777
|
+
# statistic.
|
|
778
|
+
Rank = sorted(Rank)
|
|
779
|
+
RankedList = []
|
|
780
|
+
# NOTE: each secret key is a tuple (a,b). So key[0] indexes a,
|
|
781
|
+
# and key[1] indexes b. The value of val is not used at all, making
|
|
782
|
+
# it redundant to access val in the first place. The following line
|
|
783
|
+
# of code is written with readability in mind.
|
|
784
|
+
[RankedList.append((key, pdict[(key[0], key[1])]))
|
|
785
|
+
for val, key in Rank]
|
|
786
|
+
return RankedList
|
|
787
|
+
|
|
788
|
+
def brute_force(self, C, ranking='none'):
|
|
789
|
+
r"""
|
|
790
|
+
Attempt a brute force cryptanalysis of the ciphertext ``C``.
|
|
791
|
+
|
|
792
|
+
INPUT:
|
|
793
|
+
|
|
794
|
+
- ``C`` -- a ciphertext over one of the supported alphabets of this
|
|
795
|
+
affine cryptosystem. See the class :class:`AffineCryptosystem` for
|
|
796
|
+
documentation on the supported alphabets.
|
|
797
|
+
|
|
798
|
+
- ``ranking`` -- (default: ``'none'``) the method to use for
|
|
799
|
+
ranking all possible keys. If ``ranking="none"``, then do not
|
|
800
|
+
use any ranking function. The following ranking functions are
|
|
801
|
+
supported:
|
|
802
|
+
|
|
803
|
+
- ``'chi_square'`` -- the chi-square ranking function
|
|
804
|
+
as implemented in the method :func:`rank_by_chi_square`
|
|
805
|
+
|
|
806
|
+
- ``'squared_differences'`` -- the squared differences ranking
|
|
807
|
+
function as implemented in the method
|
|
808
|
+
:func:`rank_by_squared_differences`.
|
|
809
|
+
|
|
810
|
+
OUTPUT:
|
|
811
|
+
|
|
812
|
+
- All the possible plaintext sequences corresponding to the
|
|
813
|
+
ciphertext ``C``. This method effectively uses all the possible
|
|
814
|
+
keys in this affine cryptosystem to decrypt ``C``. The method is
|
|
815
|
+
also referred to as exhaustive key search. The output is a
|
|
816
|
+
dictionary of key, candidate decipherment pairs.
|
|
817
|
+
|
|
818
|
+
EXAMPLES:
|
|
819
|
+
|
|
820
|
+
Cryptanalyze using all possible keys with the option
|
|
821
|
+
``ranking="none"``::
|
|
822
|
+
|
|
823
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
824
|
+
sage: a, b = (3, 7)
|
|
825
|
+
sage: P = A.encoding("Linear"); P
|
|
826
|
+
LINEAR
|
|
827
|
+
sage: C = A.enciphering(a, b, P)
|
|
828
|
+
sage: L = A.brute_force(C)
|
|
829
|
+
sage: sorted(L.items())[:26] # display 26 candidate decipherments
|
|
830
|
+
<BLANKLINE>
|
|
831
|
+
[((1, 0), OFUTHG),
|
|
832
|
+
((1, 1), NETSGF),
|
|
833
|
+
((1, 2), MDSRFE),
|
|
834
|
+
((1, 3), LCRQED),
|
|
835
|
+
((1, 4), KBQPDC),
|
|
836
|
+
((1, 5), JAPOCB),
|
|
837
|
+
((1, 6), IZONBA),
|
|
838
|
+
((1, 7), HYNMAZ),
|
|
839
|
+
((1, 8), GXMLZY),
|
|
840
|
+
((1, 9), FWLKYX),
|
|
841
|
+
((1, 10), EVKJXW),
|
|
842
|
+
((1, 11), DUJIWV),
|
|
843
|
+
((1, 12), CTIHVU),
|
|
844
|
+
((1, 13), BSHGUT),
|
|
845
|
+
((1, 14), ARGFTS),
|
|
846
|
+
((1, 15), ZQFESR),
|
|
847
|
+
((1, 16), YPEDRQ),
|
|
848
|
+
((1, 17), XODCQP),
|
|
849
|
+
((1, 18), WNCBPO),
|
|
850
|
+
((1, 19), VMBAON),
|
|
851
|
+
((1, 20), ULAZNM),
|
|
852
|
+
((1, 21), TKZYML),
|
|
853
|
+
((1, 22), SJYXLK),
|
|
854
|
+
((1, 23), RIXWKJ),
|
|
855
|
+
((1, 24), QHWVJI),
|
|
856
|
+
((1, 25), PGVUIH)]
|
|
857
|
+
|
|
858
|
+
Use the chi-square ranking function, i.e. ``ranking="chisquare"``::
|
|
859
|
+
|
|
860
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
861
|
+
sage: a, b = (3, 7)
|
|
862
|
+
sage: P = A.encoding("Linear functions for encrypting and decrypting."); P
|
|
863
|
+
LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING
|
|
864
|
+
sage: C = A.enciphering(a, b, P)
|
|
865
|
+
sage: Rank = A.brute_force(C, ranking='chisquare')
|
|
866
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
867
|
+
<BLANKLINE>
|
|
868
|
+
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
|
|
869
|
+
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
|
|
870
|
+
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
|
|
871
|
+
((11, 15), HSRYELDAROVSWRQDWLYROLUBVSRIERTTYOLUBVSRI),
|
|
872
|
+
((25, 1), NWHIUVFMHOPWEHSFEVIHOVABPWHCUHLLIOVABPWHC),
|
|
873
|
+
((25, 7), TCNOABLSNUVCKNYLKBONUBGHVCNIANRROUBGHVCNI),
|
|
874
|
+
((15, 4), SHIBVOWZILEHDIJWDOBILOFYEHIRVIGGBLOFYEHIR),
|
|
875
|
+
((15, 23), PEFYSLTWFIBEAFGTALYFILCVBEFOSFDDYILCVBEFO),
|
|
876
|
+
((7, 10), IDUFHSYXUTEDNULYNSFUTSVGEDURHUMMFTSVGEDUR),
|
|
877
|
+
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH)]
|
|
878
|
+
|
|
879
|
+
Use the squared differences ranking function, i.e.
|
|
880
|
+
``ranking="squared_differences"``::
|
|
881
|
+
|
|
882
|
+
sage: Rank = A.brute_force(C, ranking='squared_differences')
|
|
883
|
+
sage: Rank[:10] # display only the top 10 candidate keys
|
|
884
|
+
<BLANKLINE>
|
|
885
|
+
[((3, 7), LINEARFUNCTIONSFORENCRYPTINGANDDECRYPTING),
|
|
886
|
+
((23, 6), GJENRAMXEPYJDEZMDANEPATCYJELREOONPATCYJEL),
|
|
887
|
+
((23, 25), VYTCGPBMTENYSTOBSPCTEPIRNYTAGTDDCEPIRNYTA),
|
|
888
|
+
((19, 22), QVETRGABEFUVLENALGTEFGDSUVEHREMMTFGDSUVEH),
|
|
889
|
+
((19, 9), DIRGETNORSHIYRANYTGRSTQFHIRUERZZGSTQFHIRU),
|
|
890
|
+
((23, 18), KNIRVEQBITCNHIDQHERITEXGCNIPVISSRTEXGCNIP),
|
|
891
|
+
((17, 16), GHORBEIDOJMHFOVIFEROJETWMHOZBOAARJETWMHOZ),
|
|
892
|
+
((21, 14), AHEZRMOFEVQHTEBOTMZEVMNIQHEDREKKZVMNIQHED),
|
|
893
|
+
((1, 12), CTIHVUKDIBATLIXKLUHIBUPOATINVIEEHBUPOATIN),
|
|
894
|
+
((7, 18), SNEPRCIHEDONXEVIXCPEDCFQONEBREWWPDCFQONEB)]
|
|
895
|
+
|
|
896
|
+
TESTS:
|
|
897
|
+
|
|
898
|
+
Currently, the binary number system is not supported as an
|
|
899
|
+
alphabet of this affine cryptosystem::
|
|
900
|
+
|
|
901
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
902
|
+
sage: BinStr = BinaryStrings()
|
|
903
|
+
sage: C = BinStr.encoding("abc")
|
|
904
|
+
sage: A.brute_force(C)
|
|
905
|
+
Traceback (most recent call last):
|
|
906
|
+
...
|
|
907
|
+
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
|
|
908
|
+
|
|
909
|
+
Nor are the octal, hexadecimal, and radix-64 number systems
|
|
910
|
+
supported::
|
|
911
|
+
|
|
912
|
+
sage: OctStr = OctalStrings()
|
|
913
|
+
sage: C = OctStr([1, 2, 3])
|
|
914
|
+
sage: A.brute_force(C)
|
|
915
|
+
Traceback (most recent call last):
|
|
916
|
+
...
|
|
917
|
+
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
|
|
918
|
+
sage: HexStr = HexadecimalStrings()
|
|
919
|
+
sage: C = HexStr.encoding("abc")
|
|
920
|
+
sage: A.brute_force(C)
|
|
921
|
+
Traceback (most recent call last):
|
|
922
|
+
...
|
|
923
|
+
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
|
|
924
|
+
sage: RadStr = Radix64Strings()
|
|
925
|
+
sage: C = RadStr([1, 2, 3])
|
|
926
|
+
sage: A.brute_force(C)
|
|
927
|
+
Traceback (most recent call last):
|
|
928
|
+
...
|
|
929
|
+
TypeError: Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.
|
|
930
|
+
|
|
931
|
+
Only the chi-square and squared-differences ranking functions are
|
|
932
|
+
currently supported. The keyword ``ranking`` must take on either
|
|
933
|
+
of the values ``'none'``, ``'chisquare'`` or
|
|
934
|
+
``'squared_differences'``::
|
|
935
|
+
|
|
936
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
937
|
+
sage: a, b = (3, 7)
|
|
938
|
+
sage: P = A.encoding("Linear")
|
|
939
|
+
sage: C = A.enciphering(a, b, P)
|
|
940
|
+
sage: A.brute_force(C, ranking='chi')
|
|
941
|
+
Traceback (most recent call last):
|
|
942
|
+
...
|
|
943
|
+
ValueError: Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.
|
|
944
|
+
sage: A.brute_force(C, ranking="")
|
|
945
|
+
Traceback (most recent call last):
|
|
946
|
+
...
|
|
947
|
+
ValueError: Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.
|
|
948
|
+
"""
|
|
949
|
+
# Sanity check: ensure that C is encoded using one of the
|
|
950
|
+
# supported alphabets of this affine cryptosystem.
|
|
951
|
+
if not isinstance(C.parent(), AlphabeticStringMonoid):
|
|
952
|
+
raise TypeError("Ciphertext must be encoded using one of the supported cipher domains of this affine cryptosystem.")
|
|
953
|
+
ranking_functions = ["none", "chisquare", "squared_differences"]
|
|
954
|
+
if ranking not in ranking_functions:
|
|
955
|
+
raise ValueError("Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.")
|
|
956
|
+
|
|
957
|
+
# Now do the actual task of cryptanalysis by means of exhaustive
|
|
958
|
+
# key search, also known as the brute force method. Let D be a
|
|
959
|
+
# dictionary of key/plaintext pairs.
|
|
960
|
+
D = {}
|
|
961
|
+
|
|
962
|
+
# NOTE: This is a good candidate for loop unrolling and
|
|
963
|
+
# further optimization. Unless we can justify that this block of
|
|
964
|
+
# code is a bottleneck on the runtime of the method, we should
|
|
965
|
+
# leave it as is.
|
|
966
|
+
[D.setdefault((a, b), self.deciphering(a, b, C))
|
|
967
|
+
for a in self._invertible_A
|
|
968
|
+
for b in range(self.alphabet_size())]
|
|
969
|
+
|
|
970
|
+
if ranking == "none":
|
|
971
|
+
return D
|
|
972
|
+
if ranking == "chisquare":
|
|
973
|
+
return self.rank_by_chi_square(C, D)
|
|
974
|
+
if ranking == "squared_differences":
|
|
975
|
+
return self.rank_by_squared_differences(C, D)
|
|
976
|
+
|
|
977
|
+
def deciphering(self, a, b, C):
|
|
978
|
+
r"""
|
|
979
|
+
Decrypt the ciphertext ``C`` with the key ``(a, b)`` using affine
|
|
980
|
+
cipher decryption.
|
|
981
|
+
|
|
982
|
+
INPUT:
|
|
983
|
+
|
|
984
|
+
- ``a``, ``b`` -- a secret key belonging to the key space of this affine
|
|
985
|
+
cipher. This key must be an element of
|
|
986
|
+
`\ZZ/n\ZZ \times \ZZ/n\ZZ` such that `\gcd(a,n) = 1` with `n`
|
|
987
|
+
being the size of the ciphertext and plaintext spaces.
|
|
988
|
+
|
|
989
|
+
- ``C`` -- string of ciphertext; possibly an empty string.
|
|
990
|
+
Characters in this string must be encoded using one of the
|
|
991
|
+
supported alphabets. See the method :func:`encoding()` for more
|
|
992
|
+
information.
|
|
993
|
+
|
|
994
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
995
|
+
|
|
996
|
+
EXAMPLES:
|
|
997
|
+
|
|
998
|
+
Decryption over the capital letters of the English alphabet::
|
|
999
|
+
|
|
1000
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1001
|
+
sage: a, b = (5, 2)
|
|
1002
|
+
sage: P = A.encoding("Affine functions are linear functions.")
|
|
1003
|
+
sage: C = A.enciphering(a, b, P); C
|
|
1004
|
+
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
|
|
1005
|
+
sage: P == A.deciphering(a, b, C)
|
|
1006
|
+
True
|
|
1007
|
+
|
|
1008
|
+
The previous example can also be worked through using functional
|
|
1009
|
+
notation::
|
|
1010
|
+
|
|
1011
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1012
|
+
sage: a, b = (5, 2)
|
|
1013
|
+
sage: P = A.encoding("Affine functions are linear functions.")
|
|
1014
|
+
sage: E = A(a, b); E
|
|
1015
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
1016
|
+
sage: C = E(P); C
|
|
1017
|
+
CBBQPWBYPMTQUPOCJWFQPWCJBYPMTQUPO
|
|
1018
|
+
sage: aInv, bInv = A.inverse_key(a, b)
|
|
1019
|
+
sage: D = A(aInv, bInv); D
|
|
1020
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
1021
|
+
sage: D(C) == P
|
|
1022
|
+
True
|
|
1023
|
+
|
|
1024
|
+
If the ciphertext is an empty string, then the plaintext is also
|
|
1025
|
+
an empty string regardless of the value of the secret key::
|
|
1026
|
+
|
|
1027
|
+
sage: a, b = A.random_key()
|
|
1028
|
+
sage: A.deciphering(a, b, A.encoding(""))
|
|
1029
|
+
<BLANKLINE>
|
|
1030
|
+
sage: A.deciphering(a, b, A.encoding(" "))
|
|
1031
|
+
<BLANKLINE>
|
|
1032
|
+
|
|
1033
|
+
TESTS:
|
|
1034
|
+
|
|
1035
|
+
The key must be an ordered pair
|
|
1036
|
+
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
|
|
1037
|
+
plaintext and ciphertext spaces. Furthermore, `a` must be
|
|
1038
|
+
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
|
|
1039
|
+
|
|
1040
|
+
sage: A.deciphering(2, 6, P)
|
|
1041
|
+
Traceback (most recent call last):
|
|
1042
|
+
...
|
|
1043
|
+
ValueError: (a, b) = (2, 6) is outside the range of acceptable values for a key of this affine cipher.
|
|
1044
|
+
"""
|
|
1045
|
+
aInv, bInv = self.inverse_key(a, b)
|
|
1046
|
+
D = self(aInv, bInv)
|
|
1047
|
+
return D(C)
|
|
1048
|
+
|
|
1049
|
+
def enciphering(self, a, b, P):
|
|
1050
|
+
r"""
|
|
1051
|
+
Encrypt the plaintext ``P`` with the key ``(a, b)`` using affine cipher
|
|
1052
|
+
encryption.
|
|
1053
|
+
|
|
1054
|
+
INPUT:
|
|
1055
|
+
|
|
1056
|
+
- ``a``, ``b`` -- a secret key belonging to the key space of this affine
|
|
1057
|
+
cipher. This key must be an element of
|
|
1058
|
+
`\ZZ/n\ZZ \times \ZZ/n\ZZ` such that `\gcd(a,n) = 1` with `n`
|
|
1059
|
+
being the size of the ciphertext and plaintext spaces.
|
|
1060
|
+
|
|
1061
|
+
- ``P`` -- string of plaintext; possibly an empty string.
|
|
1062
|
+
Characters in this string must be encoded using one of the
|
|
1063
|
+
supported alphabets. See the method :func:`encoding()` for more
|
|
1064
|
+
information.
|
|
1065
|
+
|
|
1066
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``P``
|
|
1067
|
+
|
|
1068
|
+
EXAMPLES:
|
|
1069
|
+
|
|
1070
|
+
Encryption over the capital letters of the English alphabet::
|
|
1071
|
+
|
|
1072
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1073
|
+
sage: a, b = (3, 6)
|
|
1074
|
+
sage: P = A.encoding("Affine ciphers work with linear functions.")
|
|
1075
|
+
sage: A.enciphering(a, b, P)
|
|
1076
|
+
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
|
|
1077
|
+
|
|
1078
|
+
Now work through the previous example using functional notation::
|
|
1079
|
+
|
|
1080
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1081
|
+
sage: a, b = (3, 6)
|
|
1082
|
+
sage: P = A.encoding("Affine ciphers work with linear functions.")
|
|
1083
|
+
sage: E = A(a, b); E
|
|
1084
|
+
Affine cipher on Free alphabetic string monoid on A-Z
|
|
1085
|
+
sage: E(P)
|
|
1086
|
+
GVVETSMEZBSFIUWFKUELBNETSGFVOTMLEWTI
|
|
1087
|
+
|
|
1088
|
+
If the plaintext is an empty string, then the ciphertext is also
|
|
1089
|
+
an empty string regardless of the value of the secret key::
|
|
1090
|
+
|
|
1091
|
+
sage: a, b = A.random_key()
|
|
1092
|
+
sage: A.enciphering(a, b, A.encoding(""))
|
|
1093
|
+
<BLANKLINE>
|
|
1094
|
+
sage: A.enciphering(a, b, A.encoding(" "))
|
|
1095
|
+
<BLANKLINE>
|
|
1096
|
+
|
|
1097
|
+
TESTS:
|
|
1098
|
+
|
|
1099
|
+
The key must be an ordered pair
|
|
1100
|
+
`(a,b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of the
|
|
1101
|
+
plaintext and ciphertext spaces. Furthermore, `a` must be
|
|
1102
|
+
relatively prime to `n`, i.e. `\gcd(a,n) = 1`::
|
|
1103
|
+
|
|
1104
|
+
sage: A.enciphering(2, 6, P)
|
|
1105
|
+
Traceback (most recent call last):
|
|
1106
|
+
...
|
|
1107
|
+
ValueError: (a, b) = (2, 6) is outside the range of acceptable values for a key of this affine cryptosystem.
|
|
1108
|
+
"""
|
|
1109
|
+
E = self(a, b)
|
|
1110
|
+
return E(P)
|
|
1111
|
+
|
|
1112
|
+
def encoding(self, S):
|
|
1113
|
+
r"""
|
|
1114
|
+
The encoding of the string ``S`` over the string monoid of this
|
|
1115
|
+
affine cipher. For example, if the string monoid of this cryptosystem
|
|
1116
|
+
is
|
|
1117
|
+
:class:`AlphabeticStringMonoid <sage.monoids.string_monoid.AlphabeticStringMonoid>`,
|
|
1118
|
+
then the encoding of ``S`` would be its upper-case equivalent
|
|
1119
|
+
stripped of all non-alphabetic characters. Only the following alphabet
|
|
1120
|
+
is supported for the affine cipher:
|
|
1121
|
+
|
|
1122
|
+
- capital letters of the English alphabet as implemented in
|
|
1123
|
+
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
|
|
1124
|
+
|
|
1125
|
+
INPUT:
|
|
1126
|
+
|
|
1127
|
+
- ``S`` -- string, possibly empty
|
|
1128
|
+
|
|
1129
|
+
OUTPUT: the encoding of ``S`` over the string monoid of this
|
|
1130
|
+
cryptosystem; if ``S`` is an empty string, return an empty string
|
|
1131
|
+
|
|
1132
|
+
EXAMPLES:
|
|
1133
|
+
|
|
1134
|
+
Encoding over the upper-case letters of the English alphabet::
|
|
1135
|
+
|
|
1136
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1137
|
+
sage: A.encoding("Affine cipher over capital letters of the English alphabet.")
|
|
1138
|
+
AFFINECIPHEROVERCAPITALLETTERSOFTHEENGLISHALPHABET
|
|
1139
|
+
|
|
1140
|
+
The argument ``S`` can be an empty string, in which case an empty
|
|
1141
|
+
string is returned::
|
|
1142
|
+
|
|
1143
|
+
sage: AffineCryptosystem(AlphabeticStrings()).encoding("")
|
|
1144
|
+
<BLANKLINE>
|
|
1145
|
+
"""
|
|
1146
|
+
D = self.cipher_domain()
|
|
1147
|
+
if isinstance(D, AlphabeticStringMonoid):
|
|
1148
|
+
return D(strip_encoding(S))
|
|
1149
|
+
try:
|
|
1150
|
+
return D.encoding(S)
|
|
1151
|
+
except Exception:
|
|
1152
|
+
raise TypeError("Argument S = %s does not encode in the cipher domain" % S)
|
|
1153
|
+
|
|
1154
|
+
def inverse_key(self, a, b):
|
|
1155
|
+
r"""
|
|
1156
|
+
The inverse key corresponding to the secret key `(a,b)`. If `p` is
|
|
1157
|
+
a plaintext character so that `p \in \ZZ/n\ZZ` and `n` is the
|
|
1158
|
+
alphabet size, then the ciphertext `c` corresponding to `p` is
|
|
1159
|
+
|
|
1160
|
+
.. MATH::
|
|
1161
|
+
|
|
1162
|
+
c \equiv ap + b \pmod{n}
|
|
1163
|
+
|
|
1164
|
+
As `(a,b)` is a key, then the multiplicative inverse `a^{-1}`
|
|
1165
|
+
exists and the original plaintext can be recovered as follows
|
|
1166
|
+
|
|
1167
|
+
.. MATH::
|
|
1168
|
+
|
|
1169
|
+
p \equiv a^{-1} (c - b) \pmod{n}
|
|
1170
|
+
\equiv a^{-1}c + a^{-1}(-b) \pmod{n}
|
|
1171
|
+
|
|
1172
|
+
Therefore the ordered pair `(a^{-1}, -ba^{-1})` is the inverse key
|
|
1173
|
+
corresponding to `(a,b)`.
|
|
1174
|
+
|
|
1175
|
+
INPUT:
|
|
1176
|
+
|
|
1177
|
+
- ``a``, ``b`` -- a secret key for this affine cipher. The ordered pair
|
|
1178
|
+
`(a,b)` must be an element of `\ZZ/n\ZZ \times \ZZ/n\ZZ` such that
|
|
1179
|
+
`\gcd(a,n) = 1`.
|
|
1180
|
+
|
|
1181
|
+
OUTPUT:
|
|
1182
|
+
|
|
1183
|
+
- The inverse key `(a^{-1}, -ba^{-1})` corresponding to `(a,b)`.
|
|
1184
|
+
|
|
1185
|
+
EXAMPLES::
|
|
1186
|
+
|
|
1187
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1188
|
+
sage: a, b = (1, 2)
|
|
1189
|
+
sage: A.inverse_key(a, b)
|
|
1190
|
+
(1, 24)
|
|
1191
|
+
sage: A.inverse_key(3, 2)
|
|
1192
|
+
(9, 8)
|
|
1193
|
+
|
|
1194
|
+
Suppose that the plaintext and ciphertext spaces are the capital
|
|
1195
|
+
letters of the English alphabet so that `n = 26`. If `\varphi(n)`
|
|
1196
|
+
is the Euler phi function of `n`, then there are `\varphi(n)`
|
|
1197
|
+
integers `0 \leq a < n` that are relatively prime to `n`. For the
|
|
1198
|
+
capital letters of the English alphabet, there are 12 such integers
|
|
1199
|
+
relatively prime to `n`::
|
|
1200
|
+
|
|
1201
|
+
sage: euler_phi(A.alphabet_size()) # needs sage.libs.pari
|
|
1202
|
+
12
|
|
1203
|
+
|
|
1204
|
+
And here is a list of those integers::
|
|
1205
|
+
|
|
1206
|
+
sage: n = A.alphabet_size()
|
|
1207
|
+
sage: L = [i for i in range(n) if gcd(i, n) == 1]; L
|
|
1208
|
+
[1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
|
|
1209
|
+
|
|
1210
|
+
Then a secret key `(a,b)` of this shift cryptosystem is
|
|
1211
|
+
such that `a` is an element of the list ``L`` in the last example.
|
|
1212
|
+
Any inverse key `(A, B)` corresponding to `(a,b)` is such that
|
|
1213
|
+
`A` is also in the list ``L`` above::
|
|
1214
|
+
|
|
1215
|
+
sage: a, b = (3, 9)
|
|
1216
|
+
sage: a in L
|
|
1217
|
+
True
|
|
1218
|
+
sage: aInv, bInv = A.inverse_key(a, b)
|
|
1219
|
+
sage: aInv, bInv
|
|
1220
|
+
(9, 23)
|
|
1221
|
+
sage: aInv in L
|
|
1222
|
+
True
|
|
1223
|
+
|
|
1224
|
+
TESTS:
|
|
1225
|
+
|
|
1226
|
+
Any ordered pair of the form `(0, b)` for any integer `b` cannot be
|
|
1227
|
+
a secret key of this affine cipher. Hence `(0, b)` does not have
|
|
1228
|
+
a corresponding inverse key::
|
|
1229
|
+
|
|
1230
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1231
|
+
sage: A.inverse_key(0, 1)
|
|
1232
|
+
Traceback (most recent call last):
|
|
1233
|
+
...
|
|
1234
|
+
ValueError: (a, b) = (0, 1) is outside the range of acceptable values for a key of this affine cipher.
|
|
1235
|
+
"""
|
|
1236
|
+
try:
|
|
1237
|
+
from sage.rings.finite_rings.integer_mod import Mod
|
|
1238
|
+
n = self.alphabet_size()
|
|
1239
|
+
aInv = inverse_mod(a, n)
|
|
1240
|
+
bInv = Mod(-b * aInv, n).lift()
|
|
1241
|
+
return (aInv, bInv)
|
|
1242
|
+
except Exception:
|
|
1243
|
+
raise ValueError("(a, b) = (%s, %s) is outside the range of acceptable values for a key of this affine cipher." % (a, b))
|
|
1244
|
+
|
|
1245
|
+
def random_key(self):
|
|
1246
|
+
r"""
|
|
1247
|
+
Generate a random key within the key space of this affine cipher.
|
|
1248
|
+
The generated secret key is an ordered pair
|
|
1249
|
+
`(a, b) \in \ZZ/n\ZZ \times \ZZ/n\ZZ` with `n` being the size of
|
|
1250
|
+
the cipher domain and `\gcd(a, n) = 1`. Let `\varphi(n)` denote
|
|
1251
|
+
the Euler phi function of `n`. Then the affine cipher has
|
|
1252
|
+
`n \cdot \varphi(n)` possible keys (see page 10 of [Sti2006]_).
|
|
1253
|
+
|
|
1254
|
+
OUTPUT:
|
|
1255
|
+
|
|
1256
|
+
- A random key within the key space of this affine cryptosystem.
|
|
1257
|
+
The output key is an ordered pair `(a,b)`.
|
|
1258
|
+
|
|
1259
|
+
EXAMPLES::
|
|
1260
|
+
|
|
1261
|
+
sage: A = AffineCryptosystem(AlphabeticStrings())
|
|
1262
|
+
sage: A.random_key() # random
|
|
1263
|
+
(17, 25)
|
|
1264
|
+
|
|
1265
|
+
If `(a,b)` is a secret key and `n` is the size of the plaintext and
|
|
1266
|
+
ciphertext alphabets, then `\gcd(a, n) = 1`::
|
|
1267
|
+
|
|
1268
|
+
sage: a, b = A.random_key()
|
|
1269
|
+
sage: n = A.alphabet_size()
|
|
1270
|
+
sage: gcd(a, n)
|
|
1271
|
+
1
|
|
1272
|
+
"""
|
|
1273
|
+
# Return a random element in ZZ/nZZ x ZZ/nZZ where n is the number
|
|
1274
|
+
# of elements in the plaintext/ciphertext alphabet.
|
|
1275
|
+
from sage.misc.prandom import randint
|
|
1276
|
+
n = self.alphabet_size()
|
|
1277
|
+
L = len(self._invertible_A)
|
|
1278
|
+
a = Integer(self._invertible_A[randint(0, L - 1)])
|
|
1279
|
+
b = Integer(randint(0, n - 1))
|
|
1280
|
+
return (a, b)
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
class HillCryptosystem(SymmetricKeyCryptosystem):
|
|
1284
|
+
r"""
|
|
1285
|
+
Create a Hill cryptosystem defined by the `m \times m` matrix space
|
|
1286
|
+
over `\ZZ / N \ZZ`, where `N` is the alphabet size of
|
|
1287
|
+
the string monoid ``S``.
|
|
1288
|
+
|
|
1289
|
+
INPUT:
|
|
1290
|
+
|
|
1291
|
+
- ``S`` -- string monoid over some alphabet
|
|
1292
|
+
|
|
1293
|
+
- ``m`` -- integer `> 0`; the block length of matrices that specify
|
|
1294
|
+
block permutations
|
|
1295
|
+
|
|
1296
|
+
OUTPUT: a Hill cryptosystem of block length ``m`` over the alphabet ``S``
|
|
1297
|
+
|
|
1298
|
+
EXAMPLES::
|
|
1299
|
+
|
|
1300
|
+
sage: # needs sage.modules
|
|
1301
|
+
sage: S = AlphabeticStrings()
|
|
1302
|
+
sage: E = HillCryptosystem(S, 3); E
|
|
1303
|
+
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
|
|
1304
|
+
sage: R = IntegerModRing(26)
|
|
1305
|
+
sage: M = MatrixSpace(R,3,3)
|
|
1306
|
+
sage: A = M([[1,0,1],[0,1,1],[2,2,3]]); A
|
|
1307
|
+
[1 0 1]
|
|
1308
|
+
[0 1 1]
|
|
1309
|
+
[2 2 3]
|
|
1310
|
+
sage: e = E(A); e
|
|
1311
|
+
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
|
|
1312
|
+
sage: e(S("LAMAISONBLANCHE"))
|
|
1313
|
+
JYVKSKQPELAYKPV
|
|
1314
|
+
|
|
1315
|
+
TESTS::
|
|
1316
|
+
|
|
1317
|
+
sage: S = AlphabeticStrings()
|
|
1318
|
+
sage: E = HillCryptosystem(S, 3) # needs sage.modules
|
|
1319
|
+
sage: E == loads(dumps(E)) # needs sage.modules
|
|
1320
|
+
True
|
|
1321
|
+
"""
|
|
1322
|
+
|
|
1323
|
+
def __init__(self, S, m):
|
|
1324
|
+
r"""
|
|
1325
|
+
See ``HillCryptosystem`` for full documentation.
|
|
1326
|
+
|
|
1327
|
+
Create a Hill cryptosystem defined by the `m \times m` matrix space
|
|
1328
|
+
over `\ZZ / N \ZZ`, where `N` is the alphabet size of
|
|
1329
|
+
the string monoid ``S``.
|
|
1330
|
+
|
|
1331
|
+
INPUT:
|
|
1332
|
+
|
|
1333
|
+
- ``S`` -- string monoid over some alphabet
|
|
1334
|
+
|
|
1335
|
+
- ``m`` -- integer `> 0`; the block length of matrices that specify
|
|
1336
|
+
block permutations
|
|
1337
|
+
|
|
1338
|
+
OUTPUT: a Hill cryptosystem of block length ``m`` over the alphabet ``S``
|
|
1339
|
+
|
|
1340
|
+
EXAMPLES::
|
|
1341
|
+
|
|
1342
|
+
sage: S = AlphabeticStrings()
|
|
1343
|
+
sage: E = HillCryptosystem(S, 3); E # needs sage.modules
|
|
1344
|
+
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
|
|
1345
|
+
"""
|
|
1346
|
+
if not isinstance(S, StringMonoid_class):
|
|
1347
|
+
raise TypeError("S (= %s) must be a string monoid." % S)
|
|
1348
|
+
R = IntegerModRing(S.ngens())
|
|
1349
|
+
M = MatrixSpace(R, m, m)
|
|
1350
|
+
SymmetricKeyCryptosystem.__init__(self, S, S, M, block_length=m)
|
|
1351
|
+
|
|
1352
|
+
def __call__(self, A):
|
|
1353
|
+
"""
|
|
1354
|
+
Create a Hill cipher.
|
|
1355
|
+
|
|
1356
|
+
INPUT:
|
|
1357
|
+
|
|
1358
|
+
- ``A`` -- a matrix which specifies a block permutation
|
|
1359
|
+
|
|
1360
|
+
EXAMPLES::
|
|
1361
|
+
|
|
1362
|
+
sage: # needs sage.modules
|
|
1363
|
+
sage: S = AlphabeticStrings()
|
|
1364
|
+
sage: E = HillCryptosystem(S,3); E
|
|
1365
|
+
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
|
|
1366
|
+
sage: M = E.key_space()
|
|
1367
|
+
sage: A = M([[1,0,1],[0,1,1],[2,2,3]]); A
|
|
1368
|
+
[1 0 1]
|
|
1369
|
+
[0 1 1]
|
|
1370
|
+
[2 2 3]
|
|
1371
|
+
sage: e = E(A); e
|
|
1372
|
+
Hill cipher on Free alphabetic string monoid on A-Z of block length 3
|
|
1373
|
+
sage: m = S("LAMAISONBLANCHE")
|
|
1374
|
+
sage: e(m)
|
|
1375
|
+
JYVKSKQPELAYKPV
|
|
1376
|
+
sage: c = e.inverse()
|
|
1377
|
+
sage: c(e(m))
|
|
1378
|
+
LAMAISONBLANCHE
|
|
1379
|
+
"""
|
|
1380
|
+
M = self.key_space()
|
|
1381
|
+
m = self.block_length()
|
|
1382
|
+
if isinstance(A, list):
|
|
1383
|
+
try:
|
|
1384
|
+
A = M(A)
|
|
1385
|
+
except Exception:
|
|
1386
|
+
raise TypeError("A (= %s) must specify a square matrix of degree %s." % (A, m))
|
|
1387
|
+
return HillCipher(self, A)
|
|
1388
|
+
|
|
1389
|
+
def _repr_(self):
|
|
1390
|
+
"""
|
|
1391
|
+
Return a string representation of ``self``.
|
|
1392
|
+
|
|
1393
|
+
EXAMPLES::
|
|
1394
|
+
|
|
1395
|
+
sage: A = AlphabeticStrings()
|
|
1396
|
+
sage: H = HillCryptosystem(A, 3); H # needs sage.modules
|
|
1397
|
+
Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3
|
|
1398
|
+
sage: H._repr_() # needs sage.modules
|
|
1399
|
+
'Hill cryptosystem on Free alphabetic string monoid on A-Z of block length 3'
|
|
1400
|
+
"""
|
|
1401
|
+
return "Hill cryptosystem on %s of block length %s" % (
|
|
1402
|
+
self.cipher_domain(), self.block_length())
|
|
1403
|
+
|
|
1404
|
+
def block_length(self):
|
|
1405
|
+
"""
|
|
1406
|
+
The row or column dimension of a matrix specifying a block
|
|
1407
|
+
permutation. Encryption and decryption keys of a Hill cipher are
|
|
1408
|
+
square matrices, i.e. the row and column dimensions of an encryption
|
|
1409
|
+
or decryption key are the same. This row/column dimension is referred
|
|
1410
|
+
to as the *block length*.
|
|
1411
|
+
|
|
1412
|
+
OUTPUT: the block length of an encryption/decryption key
|
|
1413
|
+
|
|
1414
|
+
EXAMPLES::
|
|
1415
|
+
|
|
1416
|
+
sage: A = AlphabeticStrings()
|
|
1417
|
+
sage: n = randint(1, A.ngens() - 1)
|
|
1418
|
+
sage: H = HillCryptosystem(A, n) # needs sage.modules
|
|
1419
|
+
sage: H.block_length() == n # needs sage.modules
|
|
1420
|
+
True
|
|
1421
|
+
"""
|
|
1422
|
+
return self.key_space().nrows()
|
|
1423
|
+
|
|
1424
|
+
def random_key(self):
|
|
1425
|
+
r"""
|
|
1426
|
+
A random key within the key space of this Hill cipher. That is,
|
|
1427
|
+
generate a random `m \times m` matrix to be used as a block
|
|
1428
|
+
permutation, where `m` is the block length of this Hill cipher. If
|
|
1429
|
+
`n` is the size of the cryptosystem alphabet, then there are
|
|
1430
|
+
`n^{m^2}` possible keys. However the number of valid keys,
|
|
1431
|
+
i.e. invertible `m \times m` square matrices, is smaller than
|
|
1432
|
+
`n^{m^2}`.
|
|
1433
|
+
|
|
1434
|
+
OUTPUT: a random key within the key space of this Hill cipher
|
|
1435
|
+
|
|
1436
|
+
EXAMPLES::
|
|
1437
|
+
|
|
1438
|
+
sage: # needs sage.modules
|
|
1439
|
+
sage: A = AlphabeticStrings()
|
|
1440
|
+
sage: n = 3
|
|
1441
|
+
sage: H = HillCryptosystem(A, n)
|
|
1442
|
+
sage: K = H.random_key()
|
|
1443
|
+
sage: Ki = H.inverse_key(K)
|
|
1444
|
+
sage: M = "LAMAISONBLANCHE"
|
|
1445
|
+
sage: e = H(K)
|
|
1446
|
+
sage: d = H(Ki)
|
|
1447
|
+
sage: d(e(A(M))) == A(M)
|
|
1448
|
+
True
|
|
1449
|
+
"""
|
|
1450
|
+
M = self.key_space()
|
|
1451
|
+
m = M.nrows()
|
|
1452
|
+
N = Integer(self.cipher_domain().ngens())
|
|
1453
|
+
while True:
|
|
1454
|
+
A = M([randint(0, N-1) for i in range(m**2)])
|
|
1455
|
+
if N.gcd(A.det().lift()) == 1:
|
|
1456
|
+
break
|
|
1457
|
+
return A
|
|
1458
|
+
|
|
1459
|
+
def inverse_key(self, A):
|
|
1460
|
+
"""
|
|
1461
|
+
The inverse key corresponding to the key ``A``.
|
|
1462
|
+
|
|
1463
|
+
INPUT:
|
|
1464
|
+
|
|
1465
|
+
- ``A`` -- an invertible matrix of the key space of this Hill cipher
|
|
1466
|
+
|
|
1467
|
+
OUTPUT: the inverse matrix of ``A``
|
|
1468
|
+
|
|
1469
|
+
EXAMPLES::
|
|
1470
|
+
|
|
1471
|
+
sage: # needs sage.modules
|
|
1472
|
+
sage: S = AlphabeticStrings()
|
|
1473
|
+
sage: E = HillCryptosystem(S, 3)
|
|
1474
|
+
sage: A = E.random_key()
|
|
1475
|
+
sage: B = E.inverse_key(A)
|
|
1476
|
+
sage: M = S("LAMAISONBLANCHE")
|
|
1477
|
+
sage: e = E(A)
|
|
1478
|
+
sage: c = E(B)
|
|
1479
|
+
sage: c(e(M))
|
|
1480
|
+
LAMAISONBLANCHE
|
|
1481
|
+
"""
|
|
1482
|
+
S = self.plaintext_space()
|
|
1483
|
+
M = self.key_space()
|
|
1484
|
+
if A not in M:
|
|
1485
|
+
raise TypeError("A (= %s) must be a matrix in the key space of %s." % (A, self))
|
|
1486
|
+
m = self.block_length()
|
|
1487
|
+
MatZZ = MatrixSpace(ZZ, m)
|
|
1488
|
+
AZ = MatZZ([[A[i, j].lift() for j in range(m)] for i in range(m)])
|
|
1489
|
+
AZ_adj = AZ.adjugate()
|
|
1490
|
+
u, r, s = xgcd(A.det().lift(), S.ngens())
|
|
1491
|
+
if u != 1:
|
|
1492
|
+
raise ValueError("Argument:\n\n%s\n\nis not invertible." % (A))
|
|
1493
|
+
return r * A.parent()(AZ_adj)
|
|
1494
|
+
|
|
1495
|
+
def encoding(self, M):
|
|
1496
|
+
"""
|
|
1497
|
+
The encoding of the string ``M`` over the string monoid of this
|
|
1498
|
+
Hill cipher. For example, if the string monoid of this Hill cipher
|
|
1499
|
+
is :class:`AlphabeticStringMonoid`, then the encoding of ``M`` would
|
|
1500
|
+
be its upper-case equivalent stripped of all non-alphabetic
|
|
1501
|
+
characters.
|
|
1502
|
+
|
|
1503
|
+
INPUT:
|
|
1504
|
+
|
|
1505
|
+
- ``M`` -- string, possibly empty
|
|
1506
|
+
|
|
1507
|
+
OUTPUT: the encoding of ``M`` over the string monoid of this Hill
|
|
1508
|
+
cipher
|
|
1509
|
+
|
|
1510
|
+
EXAMPLES::
|
|
1511
|
+
|
|
1512
|
+
sage: M = "The matrix cipher by Lester S. Hill."
|
|
1513
|
+
sage: A = AlphabeticStrings()
|
|
1514
|
+
sage: H = HillCryptosystem(A, 7) # needs sage.modules
|
|
1515
|
+
sage: H.encoding(M) == A.encoding(M) # needs sage.modules
|
|
1516
|
+
True
|
|
1517
|
+
"""
|
|
1518
|
+
S = self.cipher_domain()
|
|
1519
|
+
if isinstance(S, AlphabeticStringMonoid):
|
|
1520
|
+
return S(strip_encoding(M))
|
|
1521
|
+
try:
|
|
1522
|
+
return S.encoding(M)
|
|
1523
|
+
except Exception:
|
|
1524
|
+
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
|
|
1525
|
+
|
|
1526
|
+
def deciphering(self, A, C):
|
|
1527
|
+
"""
|
|
1528
|
+
Decrypt the ciphertext ``C`` using the key ``A``.
|
|
1529
|
+
|
|
1530
|
+
INPUT:
|
|
1531
|
+
|
|
1532
|
+
- ``A`` -- a key within the key space of this Hill cipher
|
|
1533
|
+
|
|
1534
|
+
- ``C`` -- string (possibly empty) over the string monoid of this
|
|
1535
|
+
Hill cipher
|
|
1536
|
+
|
|
1537
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
1538
|
+
|
|
1539
|
+
EXAMPLES::
|
|
1540
|
+
|
|
1541
|
+
sage: # needs sage.modules
|
|
1542
|
+
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
|
|
1543
|
+
sage: K = H.random_key()
|
|
1544
|
+
sage: M = H.encoding("Good day, mate! How ya going?")
|
|
1545
|
+
sage: H.deciphering(K, H.enciphering(K, M)) == M
|
|
1546
|
+
True
|
|
1547
|
+
"""
|
|
1548
|
+
# TODO: some type checking that A is invertible hence a valid key
|
|
1549
|
+
i = self(self.inverse_key(A))
|
|
1550
|
+
return i(C)
|
|
1551
|
+
|
|
1552
|
+
def enciphering(self, A, M):
|
|
1553
|
+
"""
|
|
1554
|
+
Encrypt the plaintext ``M`` using the key ``A``.
|
|
1555
|
+
|
|
1556
|
+
INPUT:
|
|
1557
|
+
|
|
1558
|
+
- ``A`` -- a key within the key space of this Hill cipher
|
|
1559
|
+
|
|
1560
|
+
- ``M`` -- string (possibly empty) over the string monoid of this
|
|
1561
|
+
Hill cipher
|
|
1562
|
+
|
|
1563
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``M``
|
|
1564
|
+
|
|
1565
|
+
EXAMPLES::
|
|
1566
|
+
|
|
1567
|
+
sage: # needs sage.modules
|
|
1568
|
+
sage: H = HillCryptosystem(AlphabeticStrings(), 3)
|
|
1569
|
+
sage: K = H.random_key()
|
|
1570
|
+
sage: M = H.encoding("Good day, mate! How ya going?")
|
|
1571
|
+
sage: H.deciphering(K, H.enciphering(K, M)) == M
|
|
1572
|
+
True
|
|
1573
|
+
"""
|
|
1574
|
+
# TODO: some type checking that A is invertible hence a valid key
|
|
1575
|
+
e = self(A)
|
|
1576
|
+
return e(M)
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
class ShiftCryptosystem(SymmetricKeyCryptosystem):
|
|
1580
|
+
r"""
|
|
1581
|
+
Create a shift cryptosystem.
|
|
1582
|
+
|
|
1583
|
+
Let `A = \{ a_0, a_1, a_2, \dots, a_{n-1} \}` be a non-empty alphabet
|
|
1584
|
+
consisting of `n` unique elements. Define a mapping
|
|
1585
|
+
`f : A \longrightarrow \ZZ/ n\ZZ` from the alphabet `A` to
|
|
1586
|
+
the set `\ZZ / n\ZZ` of integers modulo `n`, given by
|
|
1587
|
+
`f(a_i) = i`. Thus we can identify each element of the alphabet `A`
|
|
1588
|
+
with a unique integer `0 \leq i < n`. A key of the shift cipher is an
|
|
1589
|
+
integer `0 \leq k < n`. Therefore the key space is `\ZZ / n\ZZ`. Since
|
|
1590
|
+
we assume that `A` does not have repeated elements, the mapping
|
|
1591
|
+
`f : A \longrightarrow \ZZ/ n\ZZ` is bijective.
|
|
1592
|
+
Encryption works by moving along the alphabet by `k` positions, with
|
|
1593
|
+
wrap around. Decryption reverses the process by moving backwards by
|
|
1594
|
+
`k` positions, with wrap around. More generally, let `k` be a secret key,
|
|
1595
|
+
i.e. an element of the key space, and let `p` be a plaintext
|
|
1596
|
+
character and consequently `p \in \ZZ / n\ZZ`. Then the ciphertext
|
|
1597
|
+
character `c` corresponding to `p` is given by
|
|
1598
|
+
|
|
1599
|
+
.. MATH::
|
|
1600
|
+
|
|
1601
|
+
c \equiv p + k \pmod{n}
|
|
1602
|
+
|
|
1603
|
+
Similarly, given a ciphertext character `c \in \ZZ / n\ZZ` and a secret
|
|
1604
|
+
key `k`, we can recover the corresponding plaintext character as follows:
|
|
1605
|
+
|
|
1606
|
+
.. MATH::
|
|
1607
|
+
|
|
1608
|
+
p \equiv c - k \pmod{n}
|
|
1609
|
+
|
|
1610
|
+
Use the bijection `f : A \longrightarrow \ZZ/ n\ZZ` to convert `c`
|
|
1611
|
+
and `p` back to elements of the alphabet `A`. Currently, the following
|
|
1612
|
+
alphabets are supported for the shift cipher:
|
|
1613
|
+
|
|
1614
|
+
- capital letters of the English alphabet as implemented in
|
|
1615
|
+
:func:`AlphabeticStrings()
|
|
1616
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`
|
|
1617
|
+
|
|
1618
|
+
- the alphabet consisting of the hexadecimal number system as
|
|
1619
|
+
implemented in
|
|
1620
|
+
:func:`HexadecimalStrings()
|
|
1621
|
+
<sage.monoids.string_monoid.HexadecimalStrings>`
|
|
1622
|
+
|
|
1623
|
+
- the alphabet consisting of the binary number system as implemented in
|
|
1624
|
+
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
|
|
1625
|
+
|
|
1626
|
+
EXAMPLES:
|
|
1627
|
+
|
|
1628
|
+
Some examples illustrating encryption and decryption over various
|
|
1629
|
+
alphabets. Here is an example over the upper-case letters of the English
|
|
1630
|
+
alphabet::
|
|
1631
|
+
|
|
1632
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings()); S
|
|
1633
|
+
Shift cryptosystem on Free alphabetic string monoid on A-Z
|
|
1634
|
+
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
|
|
1635
|
+
sage: P
|
|
1636
|
+
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
|
|
1637
|
+
sage: K = 7
|
|
1638
|
+
sage: C = S.enciphering(K, P); C
|
|
1639
|
+
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
|
|
1640
|
+
sage: S.deciphering(K, C)
|
|
1641
|
+
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
|
|
1642
|
+
sage: S.deciphering(K, C) == P
|
|
1643
|
+
True
|
|
1644
|
+
|
|
1645
|
+
The previous example can also be done as follows::
|
|
1646
|
+
|
|
1647
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
1648
|
+
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
|
|
1649
|
+
sage: K = 7
|
|
1650
|
+
sage: E = S(K); E
|
|
1651
|
+
Shift cipher on Free alphabetic string monoid on A-Z
|
|
1652
|
+
sage: C = E(P); C
|
|
1653
|
+
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
|
|
1654
|
+
sage: D = S(S.inverse_key(K)); D
|
|
1655
|
+
Shift cipher on Free alphabetic string monoid on A-Z
|
|
1656
|
+
sage: D(C) == P
|
|
1657
|
+
True
|
|
1658
|
+
sage: D(C) == P == D(E(P))
|
|
1659
|
+
True
|
|
1660
|
+
|
|
1661
|
+
Over the hexadecimal number system::
|
|
1662
|
+
|
|
1663
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings()); S
|
|
1664
|
+
Shift cryptosystem on Free hexadecimal string monoid
|
|
1665
|
+
sage: P = S.encoding("Encryption & decryption shifts along the alphabet."); P
|
|
1666
|
+
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
|
|
1667
|
+
sage: K = 5
|
|
1668
|
+
sage: C = S.enciphering(K, P); C
|
|
1669
|
+
9ab3b8c7cec5c9beb4b3757b75b9bab8c7cec5c9beb4b375c8bdbebbc9c875b6b1b4b3bc75c9bdba75b6b1c5bdb6b7bac973
|
|
1670
|
+
sage: S.deciphering(K, C)
|
|
1671
|
+
456e6372797074696f6e20262064656372797074696f6e2073686966747320616c6f6e672074686520616c7068616265742e
|
|
1672
|
+
sage: S.deciphering(K, C) == P
|
|
1673
|
+
True
|
|
1674
|
+
|
|
1675
|
+
And over the binary number system::
|
|
1676
|
+
|
|
1677
|
+
sage: S = ShiftCryptosystem(BinaryStrings()); S
|
|
1678
|
+
Shift cryptosystem on Free binary string monoid
|
|
1679
|
+
sage: P = S.encoding("The binary alphabet is very insecure."); P
|
|
1680
|
+
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
|
|
1681
|
+
sage: K = 1
|
|
1682
|
+
sage: C = S.enciphering(K, P); C
|
|
1683
|
+
10101011100101111001101011011111100111011001011010010001100111101000110110000110110111111001111010010011100011111001011110011110100111011001101010001011110111111001011010001100110111111000100110011010100011011000011011011111100101101001000110001100100110101001110010001010100011011001101011010001
|
|
1684
|
+
sage: S.deciphering(K, C)
|
|
1685
|
+
01010100011010000110010100100000011000100110100101101110011000010111001001111001001000000110000101101100011100000110100001100001011000100110010101110100001000000110100101110011001000000111011001100101011100100111100100100000011010010110111001110011011001010110001101110101011100100110010100101110
|
|
1686
|
+
sage: S.deciphering(K, C) == P
|
|
1687
|
+
True
|
|
1688
|
+
|
|
1689
|
+
A shift cryptosystem with key `k = 3` is commonly referred to as the
|
|
1690
|
+
Caesar cipher. Create a Caesar cipher over the upper-case letters of the
|
|
1691
|
+
English alphabet::
|
|
1692
|
+
|
|
1693
|
+
sage: caesar = ShiftCryptosystem(AlphabeticStrings())
|
|
1694
|
+
sage: K = 3
|
|
1695
|
+
sage: P = caesar.encoding("abcdef"); P
|
|
1696
|
+
ABCDEF
|
|
1697
|
+
sage: C = caesar.enciphering(K, P); C
|
|
1698
|
+
DEFGHI
|
|
1699
|
+
sage: caesar.deciphering(K, C) == P
|
|
1700
|
+
True
|
|
1701
|
+
|
|
1702
|
+
Generate a random key for encryption and decryption::
|
|
1703
|
+
|
|
1704
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
1705
|
+
sage: P = S.encoding("Shift cipher with a random key.")
|
|
1706
|
+
sage: K = S.random_key()
|
|
1707
|
+
sage: C = S.enciphering(K, P)
|
|
1708
|
+
sage: S.deciphering(K, C) == P
|
|
1709
|
+
True
|
|
1710
|
+
|
|
1711
|
+
Decrypting with the key ``K`` is equivalent to encrypting with its
|
|
1712
|
+
corresponding inverse key::
|
|
1713
|
+
|
|
1714
|
+
sage: S.enciphering(S.inverse_key(K), C) == P
|
|
1715
|
+
True
|
|
1716
|
+
|
|
1717
|
+
TESTS:
|
|
1718
|
+
|
|
1719
|
+
Currently, the octal number system is not supported as an alphabet for
|
|
1720
|
+
this shift cryptosystem::
|
|
1721
|
+
|
|
1722
|
+
sage: ShiftCryptosystem(OctalStrings())
|
|
1723
|
+
Traceback (most recent call last):
|
|
1724
|
+
...
|
|
1725
|
+
TypeError: A (= Free octal string monoid) is not supported as a cipher domain of this shift cryptosystem.
|
|
1726
|
+
|
|
1727
|
+
Nor is the radix-64 number system supported::
|
|
1728
|
+
|
|
1729
|
+
sage: ShiftCryptosystem(Radix64Strings())
|
|
1730
|
+
Traceback (most recent call last):
|
|
1731
|
+
...
|
|
1732
|
+
TypeError: A (= Free radix 64 string monoid) is not supported as a cipher domain of this shift cryptosystem.
|
|
1733
|
+
|
|
1734
|
+
Testing of dumping and loading objects::
|
|
1735
|
+
|
|
1736
|
+
sage: SA = ShiftCryptosystem(AlphabeticStrings())
|
|
1737
|
+
sage: SA == loads(dumps(SA))
|
|
1738
|
+
True
|
|
1739
|
+
sage: SH = ShiftCryptosystem(HexadecimalStrings())
|
|
1740
|
+
sage: SH == loads(dumps(SH))
|
|
1741
|
+
True
|
|
1742
|
+
sage: SB = ShiftCryptosystem(BinaryStrings())
|
|
1743
|
+
sage: SB == loads(dumps(SB))
|
|
1744
|
+
True
|
|
1745
|
+
|
|
1746
|
+
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
|
|
1747
|
+
being the size of the plaintext, ciphertext, and key spaces. For the
|
|
1748
|
+
shift cryptosystem, all these spaces are the same alphabet. This
|
|
1749
|
+
inequality must be satisfied for each of the supported alphabets.
|
|
1750
|
+
The capital letters of the English alphabet::
|
|
1751
|
+
|
|
1752
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
1753
|
+
sage: S(2 + S.alphabet_size())
|
|
1754
|
+
Traceback (most recent call last):
|
|
1755
|
+
...
|
|
1756
|
+
ValueError: K (=28) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1757
|
+
sage: S(-2)
|
|
1758
|
+
Traceback (most recent call last):
|
|
1759
|
+
...
|
|
1760
|
+
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1761
|
+
|
|
1762
|
+
The hexadecimal number system::
|
|
1763
|
+
|
|
1764
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
1765
|
+
sage: S(1 + S.alphabet_size())
|
|
1766
|
+
Traceback (most recent call last):
|
|
1767
|
+
...
|
|
1768
|
+
ValueError: K (=17) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1769
|
+
sage: S(-1)
|
|
1770
|
+
Traceback (most recent call last):
|
|
1771
|
+
...
|
|
1772
|
+
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1773
|
+
|
|
1774
|
+
The binary number system::
|
|
1775
|
+
|
|
1776
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
1777
|
+
sage: S(1 + S.alphabet_size())
|
|
1778
|
+
Traceback (most recent call last):
|
|
1779
|
+
...
|
|
1780
|
+
ValueError: K (=3) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1781
|
+
sage: S(-2)
|
|
1782
|
+
Traceback (most recent call last):
|
|
1783
|
+
...
|
|
1784
|
+
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1785
|
+
"""
|
|
1786
|
+
|
|
1787
|
+
def __init__(self, A):
|
|
1788
|
+
r"""
|
|
1789
|
+
See ``ShiftCryptosystem`` for full documentation.
|
|
1790
|
+
|
|
1791
|
+
Create a shift cryptosystem defined over the alphabet ``A``.
|
|
1792
|
+
|
|
1793
|
+
INPUT:
|
|
1794
|
+
|
|
1795
|
+
- ``A`` -- string monoid over some alphabet; this is the non-empty
|
|
1796
|
+
alphabet over which the plaintext and ciphertext spaces
|
|
1797
|
+
are defined
|
|
1798
|
+
|
|
1799
|
+
OUTPUT: a shift cryptosystem over the alphabet ``A``
|
|
1800
|
+
|
|
1801
|
+
EXAMPLES::
|
|
1802
|
+
|
|
1803
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings()); S
|
|
1804
|
+
Shift cryptosystem on Free alphabetic string monoid on A-Z
|
|
1805
|
+
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
|
|
1806
|
+
sage: P
|
|
1807
|
+
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
|
|
1808
|
+
sage: K = 7
|
|
1809
|
+
sage: C = S.enciphering(K, P); C
|
|
1810
|
+
AOLZOPMAJYFWAVZFZALTNLULYHSPGLZAOLJHLZHYJPWOLY
|
|
1811
|
+
sage: S.deciphering(K, C)
|
|
1812
|
+
THESHIFTCRYPTOSYSTEMGENERALIZESTHECAESARCIPHER
|
|
1813
|
+
sage: S.deciphering(K, C) == P
|
|
1814
|
+
True
|
|
1815
|
+
"""
|
|
1816
|
+
# NOTE: the code here is very similar to that in the method
|
|
1817
|
+
# rank_by_chi_square() of the class AffineCryptosystem. The most
|
|
1818
|
+
# significant change in the code below is in how the secret key k
|
|
1819
|
+
# is processed.
|
|
1820
|
+
|
|
1821
|
+
# sanity check
|
|
1822
|
+
from sage.monoids.string_monoid import (
|
|
1823
|
+
AlphabeticStringMonoid,
|
|
1824
|
+
BinaryStringMonoid,
|
|
1825
|
+
HexadecimalStringMonoid)
|
|
1826
|
+
if not isinstance(A, ( AlphabeticStringMonoid,
|
|
1827
|
+
BinaryStringMonoid,
|
|
1828
|
+
HexadecimalStringMonoid )):
|
|
1829
|
+
raise TypeError("A (= %s) is not supported as a cipher domain of this shift cryptosystem." % A)
|
|
1830
|
+
# Initialize the shift cryptosystem with the plaintext, ciphertext,
|
|
1831
|
+
# and key spaces.
|
|
1832
|
+
SymmetricKeyCryptosystem.__init__(self, A, A, IntegerModRing(A.ngens()))
|
|
1833
|
+
|
|
1834
|
+
def __call__(self, K):
|
|
1835
|
+
r"""
|
|
1836
|
+
Create a shift cipher with key ``K``.
|
|
1837
|
+
|
|
1838
|
+
INPUT:
|
|
1839
|
+
|
|
1840
|
+
- ``K`` -- a secret key; this key is used for both encryption and
|
|
1841
|
+
decryption. For the shift cryptosystem whose plaintext and
|
|
1842
|
+
ciphertext spaces are `A`, a key is any integer `k` such that
|
|
1843
|
+
`0 \leq k < n` where `n` is the size or cardinality of the set
|
|
1844
|
+
`A`.
|
|
1845
|
+
|
|
1846
|
+
OUTPUT: a shift cipher with secret key ``K``
|
|
1847
|
+
|
|
1848
|
+
EXAMPLES::
|
|
1849
|
+
|
|
1850
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
1851
|
+
sage: P = S.encoding("Shifting sand."); P
|
|
1852
|
+
SHIFTINGSAND
|
|
1853
|
+
sage: K = 3
|
|
1854
|
+
sage: E = S(K); E
|
|
1855
|
+
Shift cipher on Free alphabetic string monoid on A-Z
|
|
1856
|
+
sage: E(P)
|
|
1857
|
+
VKLIWLQJVDQG
|
|
1858
|
+
sage: D = S(S.inverse_key(K)); D
|
|
1859
|
+
Shift cipher on Free alphabetic string monoid on A-Z
|
|
1860
|
+
sage: D(E(P))
|
|
1861
|
+
SHIFTINGSAND
|
|
1862
|
+
|
|
1863
|
+
TESTS:
|
|
1864
|
+
|
|
1865
|
+
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
|
|
1866
|
+
being the size of the plaintext, ciphertext, and key spaces. For the
|
|
1867
|
+
shift cryptosystem, all these spaces are the same alphabet. This
|
|
1868
|
+
inequality must be satisfied for each of the supported alphabets.
|
|
1869
|
+
The capital letters of the English alphabet::
|
|
1870
|
+
|
|
1871
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
1872
|
+
sage: S(2 + S.alphabet_size())
|
|
1873
|
+
Traceback (most recent call last):
|
|
1874
|
+
...
|
|
1875
|
+
ValueError: K (=28) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1876
|
+
sage: S(-2)
|
|
1877
|
+
Traceback (most recent call last):
|
|
1878
|
+
...
|
|
1879
|
+
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1880
|
+
|
|
1881
|
+
The hexadecimal number system::
|
|
1882
|
+
|
|
1883
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
1884
|
+
sage: S(1 + S.alphabet_size())
|
|
1885
|
+
Traceback (most recent call last):
|
|
1886
|
+
...
|
|
1887
|
+
ValueError: K (=17) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1888
|
+
sage: S(-1)
|
|
1889
|
+
Traceback (most recent call last):
|
|
1890
|
+
...
|
|
1891
|
+
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1892
|
+
|
|
1893
|
+
The binary number system::
|
|
1894
|
+
|
|
1895
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
1896
|
+
sage: S(1 + S.alphabet_size())
|
|
1897
|
+
Traceback (most recent call last):
|
|
1898
|
+
...
|
|
1899
|
+
ValueError: K (=3) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1900
|
+
sage: S(-2)
|
|
1901
|
+
Traceback (most recent call last):
|
|
1902
|
+
...
|
|
1903
|
+
ValueError: K (=-2) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
1904
|
+
"""
|
|
1905
|
+
# Sanity check: the key K must satisfy the inequality
|
|
1906
|
+
# 0 <= K < n with n being the size of the plaintext, ciphertext, and
|
|
1907
|
+
# key spaces. For the shift cryptosystem, all these spaces are the
|
|
1908
|
+
# same alphabet.
|
|
1909
|
+
if 0 <= K < self.alphabet_size():
|
|
1910
|
+
return ShiftCipher(self, K)
|
|
1911
|
+
# from sage.rings.finite_rings.integer_mod import Mod
|
|
1912
|
+
# return ShiftCipher(self, Mod(K, self.alphabet_size()).lift())
|
|
1913
|
+
else:
|
|
1914
|
+
raise ValueError("K (=%s) is outside the range of acceptable values for a key of this shift cryptosystem." % K)
|
|
1915
|
+
|
|
1916
|
+
def _repr_(self):
|
|
1917
|
+
r"""
|
|
1918
|
+
Return the string representation of ``self``.
|
|
1919
|
+
|
|
1920
|
+
EXAMPLES::
|
|
1921
|
+
|
|
1922
|
+
sage: ShiftCryptosystem(AlphabeticStrings())
|
|
1923
|
+
Shift cryptosystem on Free alphabetic string monoid on A-Z
|
|
1924
|
+
sage: ShiftCryptosystem(HexadecimalStrings())
|
|
1925
|
+
Shift cryptosystem on Free hexadecimal string monoid
|
|
1926
|
+
sage: ShiftCryptosystem(BinaryStrings())
|
|
1927
|
+
Shift cryptosystem on Free binary string monoid
|
|
1928
|
+
"""
|
|
1929
|
+
# The shift cipher has the plaintext and ciphertext spaces defined
|
|
1930
|
+
# over the same non-empty alphabet. The cipher domain is the same
|
|
1931
|
+
# as the alphabet used for the plaintext and ciphertext spaces.
|
|
1932
|
+
return "Shift cryptosystem on %s" % self.cipher_domain()
|
|
1933
|
+
|
|
1934
|
+
def rank_by_chi_square(self, C, pdict):
|
|
1935
|
+
r"""
|
|
1936
|
+
Use the chi-square statistic to rank all possible keys. Currently,
|
|
1937
|
+
this method only applies to the capital letters of the English
|
|
1938
|
+
alphabet.
|
|
1939
|
+
|
|
1940
|
+
ALGORITHM:
|
|
1941
|
+
|
|
1942
|
+
Consider a non-empty alphabet `A` consisting of `n`
|
|
1943
|
+
elements, and let `C` be a ciphertext encoded using elements of
|
|
1944
|
+
`A`. The plaintext `P` corresponding to `C` is also encoded using
|
|
1945
|
+
elements of `A`. Let `M` be a candidate decipherment of `C`,
|
|
1946
|
+
i.e. `M` is the result of attempting to decrypt `C` using a key
|
|
1947
|
+
`k \in \ZZ/n\ZZ` which is not necessarily the same key used to
|
|
1948
|
+
encrypt `P`. Suppose `F_A(e)` is the characteristic frequency
|
|
1949
|
+
probability of `e \in A` and let `F_M(e)` be the message frequency
|
|
1950
|
+
probability with respect to `M`. The characteristic frequency
|
|
1951
|
+
probability distribution of an alphabet is the expected frequency
|
|
1952
|
+
probability distribution for that alphabet. The message frequency
|
|
1953
|
+
probability distribution of `M` provides a distribution of the ratio
|
|
1954
|
+
of character occurrences over message length. One can interpret the
|
|
1955
|
+
characteristic frequency probability `F_A(e)` as the expected
|
|
1956
|
+
probability, while the message frequency probability `F_M(e)` is
|
|
1957
|
+
the observed probability. If `M` is of length `L`, then the observed
|
|
1958
|
+
frequency of `e \in A` is
|
|
1959
|
+
|
|
1960
|
+
.. MATH::
|
|
1961
|
+
|
|
1962
|
+
O_M(e)
|
|
1963
|
+
=
|
|
1964
|
+
F_M(e) \cdot L
|
|
1965
|
+
|
|
1966
|
+
and the expected frequency of `e \in A` is
|
|
1967
|
+
|
|
1968
|
+
.. MATH::
|
|
1969
|
+
|
|
1970
|
+
E_A(e)
|
|
1971
|
+
=
|
|
1972
|
+
F_A(e) \cdot L
|
|
1973
|
+
|
|
1974
|
+
The chi-square rank `R_{\chi^2}(M)` of `M` corresponding to a key
|
|
1975
|
+
`k \in \ZZ/n\ZZ` is given by
|
|
1976
|
+
|
|
1977
|
+
.. MATH::
|
|
1978
|
+
|
|
1979
|
+
R_{\chi^2}(M)
|
|
1980
|
+
=
|
|
1981
|
+
\sum_{e \in A} \frac {\big( O_M(e) - E_A(e) \big)^2}
|
|
1982
|
+
{E_A(e)}
|
|
1983
|
+
|
|
1984
|
+
Cryptanalysis by exhaustive key search produces a candidate
|
|
1985
|
+
decipherment `M_{k}` for each possible key `k \in \ZZ/n\ZZ`. For
|
|
1986
|
+
a set
|
|
1987
|
+
`D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}`
|
|
1988
|
+
of all candidate decipherments corresponding to a ciphertext `C`,
|
|
1989
|
+
the smaller is the rank `R_{\chi^2}(M_{k_i})` the more likely
|
|
1990
|
+
that `k_i` is the secret key. This key ranking method is based on
|
|
1991
|
+
the Pearson chi-square test [PearsonTest]_.
|
|
1992
|
+
|
|
1993
|
+
INPUT:
|
|
1994
|
+
|
|
1995
|
+
- ``C`` -- the ciphertext, a non-empty string. The ciphertext
|
|
1996
|
+
must be encoded using the upper-case letters of the English
|
|
1997
|
+
alphabet.
|
|
1998
|
+
|
|
1999
|
+
- ``pdict`` -- dictionary of key, possible plaintext pairs.
|
|
2000
|
+
This should be the output of :func:`brute_force` with
|
|
2001
|
+
``ranking="none"``.
|
|
2002
|
+
|
|
2003
|
+
OUTPUT:
|
|
2004
|
+
|
|
2005
|
+
- A list ranking the most likely keys first. Each element of the
|
|
2006
|
+
list is a tuple of key, possible plaintext pairs.
|
|
2007
|
+
|
|
2008
|
+
EXAMPLES:
|
|
2009
|
+
|
|
2010
|
+
Use the chi-square statistic to rank all possible keys and their
|
|
2011
|
+
corresponding decipherment::
|
|
2012
|
+
|
|
2013
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2014
|
+
sage: P = S.encoding("Shi."); P
|
|
2015
|
+
SHI
|
|
2016
|
+
sage: K = 5
|
|
2017
|
+
sage: C = S.enciphering(K, P)
|
|
2018
|
+
sage: Pdict = S.brute_force(C)
|
|
2019
|
+
sage: S.rank_by_chi_square(C, Pdict)
|
|
2020
|
+
<BLANKLINE>
|
|
2021
|
+
[(9, ODE),
|
|
2022
|
+
(5, SHI),
|
|
2023
|
+
(20, DST),
|
|
2024
|
+
(19, ETU),
|
|
2025
|
+
(21, CRS),
|
|
2026
|
+
(10, NCD),
|
|
2027
|
+
(25, YNO),
|
|
2028
|
+
(6, RGH),
|
|
2029
|
+
(12, LAB),
|
|
2030
|
+
(8, PEF),
|
|
2031
|
+
(1, WLM),
|
|
2032
|
+
(11, MBC),
|
|
2033
|
+
(18, FUV),
|
|
2034
|
+
(17, GVW),
|
|
2035
|
+
(2, VKL),
|
|
2036
|
+
(4, TIJ),
|
|
2037
|
+
(3, UJK),
|
|
2038
|
+
(0, XMN),
|
|
2039
|
+
(16, HWX),
|
|
2040
|
+
(15, IXY),
|
|
2041
|
+
(23, APQ),
|
|
2042
|
+
(24, ZOP),
|
|
2043
|
+
(22, BQR),
|
|
2044
|
+
(7, QFG),
|
|
2045
|
+
(13, KZA),
|
|
2046
|
+
(14, JYZ)]
|
|
2047
|
+
|
|
2048
|
+
As more ciphertext is available, the reliability of the chi-square
|
|
2049
|
+
ranking function increases::
|
|
2050
|
+
|
|
2051
|
+
sage: P = S.encoding("Shift cipher."); P
|
|
2052
|
+
SHIFTCIPHER
|
|
2053
|
+
sage: C = S.enciphering(K, P)
|
|
2054
|
+
sage: Pdict = S.brute_force(C)
|
|
2055
|
+
sage: S.rank_by_chi_square(C, Pdict)
|
|
2056
|
+
<BLANKLINE>
|
|
2057
|
+
[(5, SHIFTCIPHER),
|
|
2058
|
+
(9, ODEBPYELDAN),
|
|
2059
|
+
(18, FUVSGPVCURE),
|
|
2060
|
+
(2, VKLIWFLSKHU),
|
|
2061
|
+
(20, DSTQENTASPC),
|
|
2062
|
+
(19, ETURFOUBTQD),
|
|
2063
|
+
(21, CRSPDMSZROB),
|
|
2064
|
+
(6, RGHESBHOGDQ),
|
|
2065
|
+
(7, QFGDRAGNFCP),
|
|
2066
|
+
(12, LABYMVBIAXK),
|
|
2067
|
+
(17, GVWTHQWDVSF),
|
|
2068
|
+
(24, ZOPMAJPWOLY),
|
|
2069
|
+
(1, WLMJXGMTLIV),
|
|
2070
|
+
(0, XMNKYHNUMJW),
|
|
2071
|
+
(11, MBCZNWCJBYL),
|
|
2072
|
+
(8, PEFCQZFMEBO),
|
|
2073
|
+
(25, YNOLZIOVNKX),
|
|
2074
|
+
(10, NCDAOXDKCZM),
|
|
2075
|
+
(3, UJKHVEKRJGT),
|
|
2076
|
+
(4, TIJGUDJQIFS),
|
|
2077
|
+
(22, BQROCLRYQNA),
|
|
2078
|
+
(16, HWXUIRXEWTG),
|
|
2079
|
+
(15, IXYVJSYFXUH),
|
|
2080
|
+
(14, JYZWKTZGYVI),
|
|
2081
|
+
(13, KZAXLUAHZWJ),
|
|
2082
|
+
(23, APQNBKQXPMZ)]
|
|
2083
|
+
|
|
2084
|
+
TESTS:
|
|
2085
|
+
|
|
2086
|
+
The ciphertext cannot be an empty string::
|
|
2087
|
+
|
|
2088
|
+
sage: S.rank_by_chi_square("", Pdict)
|
|
2089
|
+
Traceback (most recent call last):
|
|
2090
|
+
...
|
|
2091
|
+
AttributeError: 'str' object has no attribute 'parent'...
|
|
2092
|
+
sage: S.rank_by_chi_square(S.encoding(""), Pdict)
|
|
2093
|
+
Traceback (most recent call last):
|
|
2094
|
+
...
|
|
2095
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
2096
|
+
sage: S.rank_by_chi_square(S.encoding(" "), Pdict)
|
|
2097
|
+
Traceback (most recent call last):
|
|
2098
|
+
...
|
|
2099
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
2100
|
+
|
|
2101
|
+
The ciphertext must be encoded using the capital letters of the
|
|
2102
|
+
English alphabet as implemented in
|
|
2103
|
+
:func:`AlphabeticStrings()
|
|
2104
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`::
|
|
2105
|
+
|
|
2106
|
+
sage: H = HexadecimalStrings()
|
|
2107
|
+
sage: S.rank_by_chi_square(H.encoding("shift"), Pdict)
|
|
2108
|
+
Traceback (most recent call last):
|
|
2109
|
+
...
|
|
2110
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
2111
|
+
sage: B = BinaryStrings()
|
|
2112
|
+
sage: S.rank_by_chi_square(B.encoding("shift"), Pdict)
|
|
2113
|
+
Traceback (most recent call last):
|
|
2114
|
+
...
|
|
2115
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
2116
|
+
|
|
2117
|
+
The dictionary ``pdict`` cannot be empty::
|
|
2118
|
+
|
|
2119
|
+
sage: S.rank_by_chi_square(C, {})
|
|
2120
|
+
Traceback (most recent call last):
|
|
2121
|
+
...
|
|
2122
|
+
KeyError: 0
|
|
2123
|
+
"""
|
|
2124
|
+
# NOTE: the code here is very similar to that in the method
|
|
2125
|
+
# rank_by_chi_square() of the class AffineCryptosystem. The most
|
|
2126
|
+
# significant change in the code below is in how the secret key k
|
|
2127
|
+
# is processed.
|
|
2128
|
+
|
|
2129
|
+
# sanity check
|
|
2130
|
+
from sage.monoids.string_monoid import AlphabeticStrings
|
|
2131
|
+
if not isinstance(C.parent(), AlphabeticStringMonoid):
|
|
2132
|
+
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
|
|
2133
|
+
if str(C) == "":
|
|
2134
|
+
raise ValueError("The ciphertext must be a non-empty string.")
|
|
2135
|
+
|
|
2136
|
+
# compute the rank of each key
|
|
2137
|
+
AS = AlphabeticStrings()
|
|
2138
|
+
# the alphabet in question
|
|
2139
|
+
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
|
|
2140
|
+
StrAlph = str(Alph)
|
|
2141
|
+
# message length
|
|
2142
|
+
L = len(C)
|
|
2143
|
+
# expected frequency tally
|
|
2144
|
+
EA = AS.characteristic_frequency()
|
|
2145
|
+
for e in EA:
|
|
2146
|
+
EA[e] *= L
|
|
2147
|
+
# the rank R(M, k) of M for each key
|
|
2148
|
+
Rank = []
|
|
2149
|
+
for key in range(self.alphabet_size()):
|
|
2150
|
+
# observed frequency tally
|
|
2151
|
+
OM = pdict[key].frequency_distribution().function()
|
|
2152
|
+
for e in Alph:
|
|
2153
|
+
if e in OM:
|
|
2154
|
+
OM[e] *= L
|
|
2155
|
+
else:
|
|
2156
|
+
OM.setdefault(e, 0.0)
|
|
2157
|
+
# the rank R(M, K) of M with shift key k
|
|
2158
|
+
RMk = [(OM[AS(e)] - EA[e])**2 / EA[e] for e in StrAlph]
|
|
2159
|
+
Rank.append((sum(RMk), key))
|
|
2160
|
+
# Sort in non-decreasing order of squared-differences statistic. It's
|
|
2161
|
+
# possible that two different keys share the same squared-differences
|
|
2162
|
+
# statistic.
|
|
2163
|
+
Rank = sorted(Rank)
|
|
2164
|
+
RankedList = []
|
|
2165
|
+
# In the following line, the value of val is not used at all, making
|
|
2166
|
+
# it redundant to access val in the first place. This line
|
|
2167
|
+
# of code is written with readability in mind.
|
|
2168
|
+
[RankedList.append((key, pdict[key])) for val, key in Rank]
|
|
2169
|
+
return RankedList
|
|
2170
|
+
|
|
2171
|
+
def rank_by_squared_differences(self, C, pdict):
|
|
2172
|
+
r"""
|
|
2173
|
+
Use the squared-differences measure to rank all possible keys.
|
|
2174
|
+
Currently, this method only applies to the capital letters of
|
|
2175
|
+
the English alphabet.
|
|
2176
|
+
|
|
2177
|
+
ALGORITHM:
|
|
2178
|
+
|
|
2179
|
+
Consider a non-empty alphabet `A` consisting of `n`
|
|
2180
|
+
elements, and let `C` be a ciphertext encoded using elements of
|
|
2181
|
+
`A`. The plaintext `P` corresponding to `C` is also encoded using
|
|
2182
|
+
elements of `A`. Let `M` be a candidate decipherment of `C`,
|
|
2183
|
+
i.e. `M` is the result of attempting to decrypt `C` using a key
|
|
2184
|
+
`k \in \ZZ/n\ZZ` which is not necessarily the same key used to
|
|
2185
|
+
encrypt `P`. Suppose `F_A(e)` is the characteristic frequency
|
|
2186
|
+
probability of `e \in A` and let `F_M(e)` be the message
|
|
2187
|
+
frequency probability with respect to `M`. The characteristic
|
|
2188
|
+
frequency probability distribution of an alphabet is the expected
|
|
2189
|
+
frequency probability distribution for that alphabet. The message
|
|
2190
|
+
frequency probability distribution of `M` provides a distribution
|
|
2191
|
+
of the ratio of character occurrences over message length. One can
|
|
2192
|
+
interpret the characteristic frequency probability `F_A(e)` as the
|
|
2193
|
+
expected probability, while the message frequency probability
|
|
2194
|
+
`F_M(e)` is the observed probability. If `M` is of length `L`, then
|
|
2195
|
+
the observed frequency of `e \in A` is
|
|
2196
|
+
|
|
2197
|
+
.. MATH::
|
|
2198
|
+
|
|
2199
|
+
O_M(e)
|
|
2200
|
+
=
|
|
2201
|
+
F_M(e) \cdot L
|
|
2202
|
+
|
|
2203
|
+
and the expected frequency of `e \in A` is
|
|
2204
|
+
|
|
2205
|
+
.. MATH::
|
|
2206
|
+
|
|
2207
|
+
E_A(e)
|
|
2208
|
+
=
|
|
2209
|
+
F_A(e) \cdot L
|
|
2210
|
+
|
|
2211
|
+
The squared-differences, or residual sum of squares, rank
|
|
2212
|
+
`R_{RSS}(M)` of `M` corresponding to a key
|
|
2213
|
+
`k \in \ZZ/n\ZZ` is given by
|
|
2214
|
+
|
|
2215
|
+
.. MATH::
|
|
2216
|
+
|
|
2217
|
+
R_{RSS}(M)
|
|
2218
|
+
=
|
|
2219
|
+
\sum_{e \in A} \big( O_M(e) - E_A(e) \big)^2
|
|
2220
|
+
|
|
2221
|
+
Cryptanalysis by exhaustive key search produces a candidate
|
|
2222
|
+
decipherment `M_{k}` for each possible key `k \in \ZZ/n\ZZ`. For
|
|
2223
|
+
a set
|
|
2224
|
+
`D = \big\{M_{k_1}, M_{k_2}, \dots, M_{k_r} \big\}`
|
|
2225
|
+
of all candidate decipherments corresponding to a ciphertext `C`,
|
|
2226
|
+
the smaller is the rank `R_{RSS}(M_{k_i})` the more likely
|
|
2227
|
+
that `k_i` is the secret key. This key ranking method is based
|
|
2228
|
+
on the residual sum of squares measure [RSS]_.
|
|
2229
|
+
|
|
2230
|
+
INPUT:
|
|
2231
|
+
|
|
2232
|
+
- ``C`` -- the ciphertext, a non-empty string. The ciphertext
|
|
2233
|
+
must be encoded using the upper-case letters of the English
|
|
2234
|
+
alphabet.
|
|
2235
|
+
|
|
2236
|
+
- ``pdict`` -- dictionary of key, possible plaintext pairs.
|
|
2237
|
+
This should be the output of :func:`brute_force` with
|
|
2238
|
+
``ranking="none"``.
|
|
2239
|
+
|
|
2240
|
+
OUTPUT: a list ranking the most likely keys first; each element of the
|
|
2241
|
+
list is a tuple of key, possible plaintext pairs
|
|
2242
|
+
|
|
2243
|
+
EXAMPLES:
|
|
2244
|
+
|
|
2245
|
+
Use the method of squared differences to rank all possible keys
|
|
2246
|
+
and their corresponding decipherment::
|
|
2247
|
+
|
|
2248
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2249
|
+
sage: P = S.encoding("Shi."); P
|
|
2250
|
+
SHI
|
|
2251
|
+
sage: K = 5
|
|
2252
|
+
sage: C = S.enciphering(K, P)
|
|
2253
|
+
sage: Pdict = S.brute_force(C)
|
|
2254
|
+
sage: S.rank_by_squared_differences(C, Pdict)
|
|
2255
|
+
<BLANKLINE>
|
|
2256
|
+
[(19, ETU),
|
|
2257
|
+
(9, ODE),
|
|
2258
|
+
(20, DST),
|
|
2259
|
+
(5, SHI),
|
|
2260
|
+
(8, PEF),
|
|
2261
|
+
(4, TIJ),
|
|
2262
|
+
(25, YNO),
|
|
2263
|
+
(21, CRS),
|
|
2264
|
+
(6, RGH),
|
|
2265
|
+
(10, NCD),
|
|
2266
|
+
(12, LAB),
|
|
2267
|
+
(23, APQ),
|
|
2268
|
+
(24, ZOP),
|
|
2269
|
+
(0, XMN),
|
|
2270
|
+
(13, KZA),
|
|
2271
|
+
(15, IXY),
|
|
2272
|
+
(1, WLM),
|
|
2273
|
+
(16, HWX),
|
|
2274
|
+
(22, BQR),
|
|
2275
|
+
(11, MBC),
|
|
2276
|
+
(18, FUV),
|
|
2277
|
+
(2, VKL),
|
|
2278
|
+
(17, GVW),
|
|
2279
|
+
(7, QFG),
|
|
2280
|
+
(3, UJK),
|
|
2281
|
+
(14, JYZ)]
|
|
2282
|
+
|
|
2283
|
+
As more ciphertext is available, the reliability of the squared
|
|
2284
|
+
differences ranking function increases::
|
|
2285
|
+
|
|
2286
|
+
sage: P = S.encoding("Shift cipher."); P
|
|
2287
|
+
SHIFTCIPHER
|
|
2288
|
+
sage: C = S.enciphering(K, P)
|
|
2289
|
+
sage: Pdict = S.brute_force(C)
|
|
2290
|
+
sage: S.rank_by_squared_differences(C, Pdict)
|
|
2291
|
+
<BLANKLINE>
|
|
2292
|
+
[(20, DSTQENTASPC),
|
|
2293
|
+
(5, SHIFTCIPHER),
|
|
2294
|
+
(9, ODEBPYELDAN),
|
|
2295
|
+
(19, ETURFOUBTQD),
|
|
2296
|
+
(6, RGHESBHOGDQ),
|
|
2297
|
+
(16, HWXUIRXEWTG),
|
|
2298
|
+
(8, PEFCQZFMEBO),
|
|
2299
|
+
(21, CRSPDMSZROB),
|
|
2300
|
+
(22, BQROCLRYQNA),
|
|
2301
|
+
(25, YNOLZIOVNKX),
|
|
2302
|
+
(3, UJKHVEKRJGT),
|
|
2303
|
+
(18, FUVSGPVCURE),
|
|
2304
|
+
(4, TIJGUDJQIFS),
|
|
2305
|
+
(10, NCDAOXDKCZM),
|
|
2306
|
+
(7, QFGDRAGNFCP),
|
|
2307
|
+
(24, ZOPMAJPWOLY),
|
|
2308
|
+
(2, VKLIWFLSKHU),
|
|
2309
|
+
(12, LABYMVBIAXK),
|
|
2310
|
+
(17, GVWTHQWDVSF),
|
|
2311
|
+
(1, WLMJXGMTLIV),
|
|
2312
|
+
(13, KZAXLUAHZWJ),
|
|
2313
|
+
(0, XMNKYHNUMJW),
|
|
2314
|
+
(15, IXYVJSYFXUH),
|
|
2315
|
+
(14, JYZWKTZGYVI),
|
|
2316
|
+
(11, MBCZNWCJBYL),
|
|
2317
|
+
(23, APQNBKQXPMZ)]
|
|
2318
|
+
|
|
2319
|
+
TESTS:
|
|
2320
|
+
|
|
2321
|
+
The ciphertext cannot be an empty string::
|
|
2322
|
+
|
|
2323
|
+
sage: S.rank_by_squared_differences("", Pdict)
|
|
2324
|
+
Traceback (most recent call last):
|
|
2325
|
+
...
|
|
2326
|
+
AttributeError: 'str' object has no attribute 'parent'...
|
|
2327
|
+
sage: S.rank_by_squared_differences(S.encoding(""), Pdict)
|
|
2328
|
+
Traceback (most recent call last):
|
|
2329
|
+
...
|
|
2330
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
2331
|
+
sage: S.rank_by_squared_differences(S.encoding(" "), Pdict)
|
|
2332
|
+
Traceback (most recent call last):
|
|
2333
|
+
...
|
|
2334
|
+
ValueError: The ciphertext must be a non-empty string.
|
|
2335
|
+
|
|
2336
|
+
The ciphertext must be encoded using the capital letters of the
|
|
2337
|
+
English alphabet as implemented in
|
|
2338
|
+
:func:`AlphabeticStrings()
|
|
2339
|
+
<sage.monoids.string_monoid.AlphabeticStrings>`::
|
|
2340
|
+
|
|
2341
|
+
sage: H = HexadecimalStrings()
|
|
2342
|
+
sage: S.rank_by_squared_differences(H.encoding("shift"), Pdict)
|
|
2343
|
+
Traceback (most recent call last):
|
|
2344
|
+
...
|
|
2345
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
2346
|
+
sage: B = BinaryStrings()
|
|
2347
|
+
sage: S.rank_by_squared_differences(B.encoding("shift"), Pdict)
|
|
2348
|
+
Traceback (most recent call last):
|
|
2349
|
+
...
|
|
2350
|
+
TypeError: The ciphertext must be capital letters of the English alphabet.
|
|
2351
|
+
|
|
2352
|
+
The dictionary ``pdict`` cannot be empty::
|
|
2353
|
+
|
|
2354
|
+
sage: S.rank_by_squared_differences(C, {})
|
|
2355
|
+
Traceback (most recent call last):
|
|
2356
|
+
...
|
|
2357
|
+
KeyError: 0
|
|
2358
|
+
"""
|
|
2359
|
+
# NOTE: the code in this method is very similar to that in the
|
|
2360
|
+
# method rank_by_chi_square(). The only difference here is the
|
|
2361
|
+
# line that computes the list RMk.
|
|
2362
|
+
|
|
2363
|
+
# sanity check
|
|
2364
|
+
from sage.monoids.string_monoid import (
|
|
2365
|
+
AlphabeticStringMonoid,
|
|
2366
|
+
AlphabeticStrings)
|
|
2367
|
+
if not isinstance(C.parent(), AlphabeticStringMonoid):
|
|
2368
|
+
raise TypeError("The ciphertext must be capital letters of the English alphabet.")
|
|
2369
|
+
if str(C) == "":
|
|
2370
|
+
raise ValueError("The ciphertext must be a non-empty string.")
|
|
2371
|
+
|
|
2372
|
+
# compute the rank of each key
|
|
2373
|
+
AS = AlphabeticStrings()
|
|
2374
|
+
# the alphabet in question
|
|
2375
|
+
Alph = self.encoding("".join([str(e) for e in AS.gens()]))
|
|
2376
|
+
StrAlph = str(Alph)
|
|
2377
|
+
# message length
|
|
2378
|
+
L = len(C)
|
|
2379
|
+
# expected frequency tally
|
|
2380
|
+
EA = AS.characteristic_frequency()
|
|
2381
|
+
for e in EA:
|
|
2382
|
+
EA[e] *= L
|
|
2383
|
+
# the rank R(M, k) of M for each key
|
|
2384
|
+
Rank = []
|
|
2385
|
+
for key in range(self.alphabet_size()):
|
|
2386
|
+
# observed frequency tally
|
|
2387
|
+
OM = pdict[key].frequency_distribution().function()
|
|
2388
|
+
for e in Alph:
|
|
2389
|
+
if e in OM:
|
|
2390
|
+
OM[e] *= L
|
|
2391
|
+
else:
|
|
2392
|
+
OM.setdefault(e, 0.0)
|
|
2393
|
+
# the rank R(M, K) of M with shift key k
|
|
2394
|
+
RMk = [(OM[AS(e)] - EA[e])**2 for e in StrAlph]
|
|
2395
|
+
Rank.append((sum(RMk), key))
|
|
2396
|
+
# Sort in non-decreasing order of squared-differences statistic. It's
|
|
2397
|
+
# possible that two different keys share the same squared-differences
|
|
2398
|
+
# statistic.
|
|
2399
|
+
Rank = sorted(Rank)
|
|
2400
|
+
RankedList = []
|
|
2401
|
+
# In the following line, the value of val is not used at all, making
|
|
2402
|
+
# it redundant to access val in the first place. This line
|
|
2403
|
+
# of code is written with readability in mind.
|
|
2404
|
+
[RankedList.append((key, pdict[key])) for val, key in Rank]
|
|
2405
|
+
return RankedList
|
|
2406
|
+
|
|
2407
|
+
def brute_force(self, C, ranking='none'):
|
|
2408
|
+
r"""
|
|
2409
|
+
Attempt a brute force cryptanalysis of the ciphertext ``C``.
|
|
2410
|
+
|
|
2411
|
+
INPUT:
|
|
2412
|
+
|
|
2413
|
+
- ``C`` -- a ciphertext over one of the supported alphabets of this
|
|
2414
|
+
shift cryptosystem. See the class :class:`ShiftCryptosystem` for
|
|
2415
|
+
documentation on the supported alphabets.
|
|
2416
|
+
|
|
2417
|
+
- ``ranking`` -- (default: ``'none'``) the method to use for
|
|
2418
|
+
ranking all possible keys. If ``ranking="none"``, then do not
|
|
2419
|
+
use any ranking function. The following ranking functions are
|
|
2420
|
+
supported:
|
|
2421
|
+
|
|
2422
|
+
- ``'chisquare'`` -- the chi-square ranking function as
|
|
2423
|
+
implemented in the method :func:`rank_by_chi_square`
|
|
2424
|
+
|
|
2425
|
+
- ``'squared_differences'`` -- the squared differences ranking
|
|
2426
|
+
function as implemented in the method
|
|
2427
|
+
:func:`rank_by_squared_differences`.
|
|
2428
|
+
|
|
2429
|
+
OUTPUT:
|
|
2430
|
+
|
|
2431
|
+
- All the possible plaintext sequences corresponding to the
|
|
2432
|
+
ciphertext ``C``. This method effectively uses all the possible
|
|
2433
|
+
keys in this shift cryptosystem to decrypt ``C``. The method is
|
|
2434
|
+
also referred to as exhaustive key search. The output is
|
|
2435
|
+
a dictionary of key, plaintext pairs.
|
|
2436
|
+
|
|
2437
|
+
EXAMPLES:
|
|
2438
|
+
|
|
2439
|
+
Cryptanalyze using all possible keys for various alphabets. Over
|
|
2440
|
+
the upper-case letters of the English alphabet::
|
|
2441
|
+
|
|
2442
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2443
|
+
sage: P = S.encoding("The shift cryptosystem generalizes the Caesar cipher.")
|
|
2444
|
+
sage: K = 7
|
|
2445
|
+
sage: C = S.enciphering(K, P)
|
|
2446
|
+
sage: Dict = S.brute_force(C)
|
|
2447
|
+
sage: for k in range(len(Dict)):
|
|
2448
|
+
....: if Dict[k] == P:
|
|
2449
|
+
....: print("key = " + str(k))
|
|
2450
|
+
key = 7
|
|
2451
|
+
|
|
2452
|
+
Over the hexadecimal number system::
|
|
2453
|
+
|
|
2454
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2455
|
+
sage: P = S.encoding("Encryption & decryption shifts along the alphabet.")
|
|
2456
|
+
sage: K = 5
|
|
2457
|
+
sage: C = S.enciphering(K, P)
|
|
2458
|
+
sage: Dict = S.brute_force(C)
|
|
2459
|
+
sage: for k in range(len(Dict)):
|
|
2460
|
+
....: if Dict[k] == P:
|
|
2461
|
+
....: print("key = " + str(k))
|
|
2462
|
+
key = 5
|
|
2463
|
+
|
|
2464
|
+
And over the binary number system::
|
|
2465
|
+
|
|
2466
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2467
|
+
sage: P = S.encoding("The binary alphabet is very insecure.")
|
|
2468
|
+
sage: K = 1
|
|
2469
|
+
sage: C = S.enciphering(K, P)
|
|
2470
|
+
sage: Dict = S.brute_force(C)
|
|
2471
|
+
sage: for k in range(len(Dict)):
|
|
2472
|
+
....: if Dict[k] == P:
|
|
2473
|
+
....: print("key = " + str(k))
|
|
2474
|
+
key = 1
|
|
2475
|
+
|
|
2476
|
+
Don't use any ranking functions, i.e. ``ranking="none"``::
|
|
2477
|
+
|
|
2478
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2479
|
+
sage: P = S.encoding("Shifting using modular arithmetic.")
|
|
2480
|
+
sage: K = 8
|
|
2481
|
+
sage: C = S.enciphering(K, P)
|
|
2482
|
+
sage: pdict = S.brute_force(C)
|
|
2483
|
+
sage: sorted(pdict.items())
|
|
2484
|
+
<BLANKLINE>
|
|
2485
|
+
[(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
|
|
2486
|
+
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
|
|
2487
|
+
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
|
|
2488
|
+
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
|
|
2489
|
+
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
|
|
2490
|
+
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
|
|
2491
|
+
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
|
|
2492
|
+
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
|
|
2493
|
+
(8, SHIFTINGUSINGMODULARARITHMETIC),
|
|
2494
|
+
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
|
|
2495
|
+
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
|
|
2496
|
+
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
|
|
2497
|
+
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
|
|
2498
|
+
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
|
|
2499
|
+
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
|
|
2500
|
+
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
|
|
2501
|
+
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
|
|
2502
|
+
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
|
|
2503
|
+
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
|
|
2504
|
+
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
|
|
2505
|
+
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
|
|
2506
|
+
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
|
|
2507
|
+
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
|
|
2508
|
+
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
|
|
2509
|
+
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
|
|
2510
|
+
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL)]
|
|
2511
|
+
|
|
2512
|
+
Use the chi-square ranking function, i.e. ``ranking="chisquare"``::
|
|
2513
|
+
|
|
2514
|
+
sage: S.brute_force(C, ranking='chisquare')
|
|
2515
|
+
<BLANKLINE>
|
|
2516
|
+
[(8, SHIFTINGUSINGMODULARARITHMETIC),
|
|
2517
|
+
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
|
|
2518
|
+
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
|
|
2519
|
+
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
|
|
2520
|
+
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
|
|
2521
|
+
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
|
|
2522
|
+
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
|
|
2523
|
+
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
|
|
2524
|
+
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
|
|
2525
|
+
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
|
|
2526
|
+
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
|
|
2527
|
+
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
|
|
2528
|
+
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
|
|
2529
|
+
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
|
|
2530
|
+
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
|
|
2531
|
+
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
|
|
2532
|
+
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
|
|
2533
|
+
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF),
|
|
2534
|
+
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
|
|
2535
|
+
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
|
|
2536
|
+
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
|
|
2537
|
+
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
|
|
2538
|
+
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
|
|
2539
|
+
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
|
|
2540
|
+
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
|
|
2541
|
+
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT)]
|
|
2542
|
+
|
|
2543
|
+
Use the squared differences ranking function, i.e.
|
|
2544
|
+
``ranking="squared_differences"``::
|
|
2545
|
+
|
|
2546
|
+
sage: S.brute_force(C, ranking='squared_differences')
|
|
2547
|
+
<BLANKLINE>
|
|
2548
|
+
[(8, SHIFTINGUSINGMODULARARITHMETIC),
|
|
2549
|
+
(23, DSTQETYRFDTYRXZOFWLCLCTESXPETN),
|
|
2550
|
+
(12, ODEBPEJCQOEJCIKZQHWNWNEPDIAPEY),
|
|
2551
|
+
(2, YNOLZOTMAYOTMSUJARGXGXOZNSKZOI),
|
|
2552
|
+
(9, RGHESHMFTRHMFLNCTKZQZQHSGLDSHB),
|
|
2553
|
+
(7, TIJGUJOHVTJOHNPEVMBSBSJUINFUJD),
|
|
2554
|
+
(21, FUVSGVATHFVATZBQHYNENEVGUZRGVP),
|
|
2555
|
+
(22, ETURFUZSGEUZSYAPGXMDMDUFTYQFUO),
|
|
2556
|
+
(1, ZOPMAPUNBZPUNTVKBSHYHYPAOTLAPJ),
|
|
2557
|
+
(16, KZAXLAFYMKAFYEGVMDSJSJALZEWLAU),
|
|
2558
|
+
(20, GVWTHWBUIGWBUACRIZOFOFWHVASHWQ),
|
|
2559
|
+
(24, CRSPDSXQECSXQWYNEVKBKBSDRWODSM),
|
|
2560
|
+
(14, MBCZNCHAOMCHAGIXOFULULCNBGYNCW),
|
|
2561
|
+
(13, NCDAODIBPNDIBHJYPGVMVMDOCHZODX),
|
|
2562
|
+
(3, XMNKYNSLZXNSLRTIZQFWFWNYMRJYNH),
|
|
2563
|
+
(10, QFGDRGLESQGLEKMBSJYPYPGRFKCRGA),
|
|
2564
|
+
(15, LABYMBGZNLBGZFHWNETKTKBMAFXMBV),
|
|
2565
|
+
(6, UJKHVKPIWUKPIOQFWNCTCTKVJOGVKE),
|
|
2566
|
+
(11, PEFCQFKDRPFKDJLARIXOXOFQEJBQFZ),
|
|
2567
|
+
(25, BQROCRWPDBRWPVXMDUJAJARCQVNCRL),
|
|
2568
|
+
(17, JYZWKZEXLJZEXDFULCRIRIZKYDVKZT),
|
|
2569
|
+
(19, HWXUIXCVJHXCVBDSJAPGPGXIWBTIXR),
|
|
2570
|
+
(4, WLMJXMRKYWMRKQSHYPEVEVMXLQIXMG),
|
|
2571
|
+
(0, APQNBQVOCAQVOUWLCTIZIZQBPUMBQK),
|
|
2572
|
+
(18, IXYVJYDWKIYDWCETKBQHQHYJXCUJYS),
|
|
2573
|
+
(5, VKLIWLQJXVLQJPRGXODUDULWKPHWLF)]
|
|
2574
|
+
|
|
2575
|
+
TESTS:
|
|
2576
|
+
|
|
2577
|
+
Currently, the octal number system is not supported as an alphabet for
|
|
2578
|
+
this shift cryptosystem::
|
|
2579
|
+
|
|
2580
|
+
sage: SA = ShiftCryptosystem(AlphabeticStrings())
|
|
2581
|
+
sage: OctStr = OctalStrings()
|
|
2582
|
+
sage: C = OctStr([1, 2, 3])
|
|
2583
|
+
sage: SA.brute_force(C)
|
|
2584
|
+
Traceback (most recent call last):
|
|
2585
|
+
...
|
|
2586
|
+
TypeError: ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.
|
|
2587
|
+
|
|
2588
|
+
Nor is the radix-64 alphabet supported::
|
|
2589
|
+
|
|
2590
|
+
sage: Rad64 = Radix64Strings()
|
|
2591
|
+
sage: C = Rad64([1, 2, 3])
|
|
2592
|
+
sage: SA.brute_force(C)
|
|
2593
|
+
Traceback (most recent call last):
|
|
2594
|
+
...
|
|
2595
|
+
TypeError: ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.
|
|
2596
|
+
"""
|
|
2597
|
+
# Sanity check: ensure that C is encoded using one of the
|
|
2598
|
+
# supported alphabets of this shift cryptosystem.
|
|
2599
|
+
from sage.monoids.string_monoid import (
|
|
2600
|
+
AlphabeticStringMonoid,
|
|
2601
|
+
BinaryStringMonoid,
|
|
2602
|
+
HexadecimalStringMonoid)
|
|
2603
|
+
if not isinstance(C.parent(), (
|
|
2604
|
+
AlphabeticStringMonoid,
|
|
2605
|
+
BinaryStringMonoid,
|
|
2606
|
+
HexadecimalStringMonoid)):
|
|
2607
|
+
raise TypeError("ciphertext must be encoded using one of the supported cipher domains of this shift cryptosystem.")
|
|
2608
|
+
ranking_functions = ["none", "chisquare", "squared_differences"]
|
|
2609
|
+
if ranking not in ranking_functions:
|
|
2610
|
+
raise ValueError("Keyword 'ranking' must be either 'none', 'chisquare', or 'squared_differences'.")
|
|
2611
|
+
|
|
2612
|
+
# Now do the actual task of cryptanalysis by means of exhaustive key
|
|
2613
|
+
# search, also known as the brute force method.
|
|
2614
|
+
# let D be a dictionary of key/plaintext pairs
|
|
2615
|
+
D = {}
|
|
2616
|
+
|
|
2617
|
+
# NOTE: This loop is a good candidate for loop unrolling. Unless we
|
|
2618
|
+
# can justify that this block of code is a bottleneck on the runtime
|
|
2619
|
+
# of the method, we should leave it as is. For the alphabets that
|
|
2620
|
+
# are supported by this shift cryptosystem, it can be a waste of
|
|
2621
|
+
# time optimizing the code when the largest alphabet size is less
|
|
2622
|
+
# than 100.
|
|
2623
|
+
for k in range(self.alphabet_size()):
|
|
2624
|
+
D.setdefault(k, self.deciphering(k, C))
|
|
2625
|
+
|
|
2626
|
+
if ranking == "none":
|
|
2627
|
+
return D
|
|
2628
|
+
if ranking == "chisquare":
|
|
2629
|
+
return self.rank_by_chi_square(C, D)
|
|
2630
|
+
if ranking == "squared_differences":
|
|
2631
|
+
return self.rank_by_squared_differences(C, D)
|
|
2632
|
+
|
|
2633
|
+
def deciphering(self, K, C):
|
|
2634
|
+
r"""
|
|
2635
|
+
Decrypt the ciphertext ``C`` with the key ``K`` using shift cipher
|
|
2636
|
+
decryption.
|
|
2637
|
+
|
|
2638
|
+
INPUT:
|
|
2639
|
+
|
|
2640
|
+
- ``K`` -- a secret key; a key belonging to the key space of this
|
|
2641
|
+
shift cipher. This key is an integer `k` satisfying the inequality
|
|
2642
|
+
`0 \leq k < n`, where `n` is the size of the cipher domain.
|
|
2643
|
+
|
|
2644
|
+
- ``C`` -- string of ciphertext; possibly an empty string
|
|
2645
|
+
Characters in this string must be encoded using one of the
|
|
2646
|
+
supported alphabets. See the method :func:`encoding()`
|
|
2647
|
+
for more information.
|
|
2648
|
+
|
|
2649
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
2650
|
+
|
|
2651
|
+
EXAMPLES:
|
|
2652
|
+
|
|
2653
|
+
Let's perform decryption over the supported alphabets. Here is
|
|
2654
|
+
decryption over the capital letters of the English alphabet::
|
|
2655
|
+
|
|
2656
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2657
|
+
sage: P = S.encoding("Stop shifting me."); P
|
|
2658
|
+
STOPSHIFTINGME
|
|
2659
|
+
sage: K = 13
|
|
2660
|
+
sage: C = S.enciphering(K, P); C
|
|
2661
|
+
FGBCFUVSGVATZR
|
|
2662
|
+
sage: S.deciphering(K, C) == P
|
|
2663
|
+
True
|
|
2664
|
+
|
|
2665
|
+
Decryption over the hexadecimal number system::
|
|
2666
|
+
|
|
2667
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2668
|
+
sage: P = S.encoding("Shift me now."); P
|
|
2669
|
+
5368696674206d65206e6f772e
|
|
2670
|
+
sage: K = 7
|
|
2671
|
+
sage: C = S.enciphering(K, P); C
|
|
2672
|
+
cadfd0ddeb97d4dc97d5d6ee95
|
|
2673
|
+
sage: S.deciphering(K, C) == P
|
|
2674
|
+
True
|
|
2675
|
+
|
|
2676
|
+
Decryption over the binary number system::
|
|
2677
|
+
|
|
2678
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2679
|
+
sage: P = S.encoding("OK, enough shifting."); P
|
|
2680
|
+
0100111101001011001011000010000001100101011011100110111101110101011001110110100000100000011100110110100001101001011001100111010001101001011011100110011100101110
|
|
2681
|
+
sage: K = 1
|
|
2682
|
+
sage: C = S.enciphering(K, P); C
|
|
2683
|
+
1011000010110100110100111101111110011010100100011001000010001010100110001001011111011111100011001001011110010110100110011000101110010110100100011001100011010001
|
|
2684
|
+
sage: S.deciphering(K, C) == P
|
|
2685
|
+
True
|
|
2686
|
+
"""
|
|
2687
|
+
E = self(self.inverse_key(K))
|
|
2688
|
+
return E(C)
|
|
2689
|
+
|
|
2690
|
+
def enciphering(self, K, P):
|
|
2691
|
+
r"""
|
|
2692
|
+
Encrypt the plaintext ``P`` with the key ``K`` using shift cipher
|
|
2693
|
+
encryption.
|
|
2694
|
+
|
|
2695
|
+
INPUT:
|
|
2696
|
+
|
|
2697
|
+
- ``K`` -- a key belonging to the key space of this shift cipher.
|
|
2698
|
+
This key is an integer `k` satisfying the inequality
|
|
2699
|
+
`0 \leq k < n`, where `n` is the size of the cipher domain.
|
|
2700
|
+
|
|
2701
|
+
- ``P`` -- string of plaintext; possibly an empty string.
|
|
2702
|
+
Characters in this string must be encoded using one of the
|
|
2703
|
+
supported alphabets. See the method :func:`encoding()` for more
|
|
2704
|
+
information.
|
|
2705
|
+
|
|
2706
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``P``
|
|
2707
|
+
|
|
2708
|
+
EXAMPLES:
|
|
2709
|
+
|
|
2710
|
+
Let's perform encryption over the supported alphabets. Here is
|
|
2711
|
+
encryption over the capital letters of the English alphabet::
|
|
2712
|
+
|
|
2713
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2714
|
+
sage: P = S.encoding("Shift your gear."); P
|
|
2715
|
+
SHIFTYOURGEAR
|
|
2716
|
+
sage: K = 3
|
|
2717
|
+
sage: S.enciphering(K, P)
|
|
2718
|
+
VKLIWBRXUJHDU
|
|
2719
|
+
|
|
2720
|
+
Encryption over the hexadecimal number system::
|
|
2721
|
+
|
|
2722
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2723
|
+
sage: P = S.encoding("Capitalize with the shift key."); P
|
|
2724
|
+
4361706974616c697a65207769746820746865207368696674206b65792e
|
|
2725
|
+
sage: K = 5
|
|
2726
|
+
sage: S.enciphering(K, P)
|
|
2727
|
+
98b6c5bec9b6b1becfba75ccbec9bd75c9bdba75c8bdbebbc975b0bace73
|
|
2728
|
+
|
|
2729
|
+
Encryption over the binary number system::
|
|
2730
|
+
|
|
2731
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2732
|
+
sage: P = S.encoding("Don't shift."); P
|
|
2733
|
+
010001000110111101101110001001110111010000100000011100110110100001101001011001100111010000101110
|
|
2734
|
+
sage: K = 1
|
|
2735
|
+
sage: S.enciphering(K, P)
|
|
2736
|
+
101110111001000010010001110110001000101111011111100011001001011110010110100110011000101111010001
|
|
2737
|
+
"""
|
|
2738
|
+
E = self(K)
|
|
2739
|
+
return E(P)
|
|
2740
|
+
|
|
2741
|
+
def encoding(self, S):
|
|
2742
|
+
r"""
|
|
2743
|
+
The encoding of the string ``S`` over the string monoid of this
|
|
2744
|
+
shift cipher. For example, if the string monoid of this cryptosystem
|
|
2745
|
+
is
|
|
2746
|
+
:class:`AlphabeticStringMonoid <sage.monoids.string_monoid.AlphabeticStringMonoid>`,
|
|
2747
|
+
then the encoding of ``S`` would be its upper-case equivalent
|
|
2748
|
+
stripped of all non-alphabetic characters. The following alphabets
|
|
2749
|
+
are supported for the shift cipher:
|
|
2750
|
+
|
|
2751
|
+
- capital letters of the English alphabet as implemented in
|
|
2752
|
+
:func:`AlphabeticStrings() <sage.monoids.string_monoid.AlphabeticStrings>`
|
|
2753
|
+
|
|
2754
|
+
- the alphabet consisting of the hexadecimal number system as
|
|
2755
|
+
implemented in
|
|
2756
|
+
:func:`HexadecimalStrings() <sage.monoids.string_monoid.HexadecimalStrings>`
|
|
2757
|
+
|
|
2758
|
+
- the alphabet consisting of the binary number system as implemented in
|
|
2759
|
+
:func:`BinaryStrings() <sage.monoids.string_monoid.BinaryStrings>`
|
|
2760
|
+
|
|
2761
|
+
INPUT:
|
|
2762
|
+
|
|
2763
|
+
- ``S`` -- string, possibly empty
|
|
2764
|
+
|
|
2765
|
+
OUTPUT: the encoding of ``S`` over the string monoid of this
|
|
2766
|
+
cryptosystem; if ``S`` is an empty string, return an empty string
|
|
2767
|
+
|
|
2768
|
+
EXAMPLES:
|
|
2769
|
+
|
|
2770
|
+
Encoding over the upper-case letters of the English alphabet::
|
|
2771
|
+
|
|
2772
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2773
|
+
sage: S.encoding("Shift cipher on capital letters of the English alphabet.")
|
|
2774
|
+
SHIFTCIPHERONCAPITALLETTERSOFTHEENGLISHALPHABET
|
|
2775
|
+
|
|
2776
|
+
Encoding over the binary system::
|
|
2777
|
+
|
|
2778
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2779
|
+
sage: S.encoding("Binary")
|
|
2780
|
+
010000100110100101101110011000010111001001111001
|
|
2781
|
+
|
|
2782
|
+
Encoding over the hexadecimal system::
|
|
2783
|
+
|
|
2784
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2785
|
+
sage: S.encoding("Over hexadecimal system.")
|
|
2786
|
+
4f7665722068657861646563696d616c2073797374656d2e
|
|
2787
|
+
|
|
2788
|
+
The argument ``S`` can be an empty string, in which case an empty
|
|
2789
|
+
string is returned::
|
|
2790
|
+
|
|
2791
|
+
sage: ShiftCryptosystem(AlphabeticStrings()).encoding("")
|
|
2792
|
+
<BLANKLINE>
|
|
2793
|
+
sage: ShiftCryptosystem(HexadecimalStrings()).encoding("")
|
|
2794
|
+
<BLANKLINE>
|
|
2795
|
+
sage: ShiftCryptosystem(BinaryStrings()).encoding("")
|
|
2796
|
+
<BLANKLINE>
|
|
2797
|
+
"""
|
|
2798
|
+
D = self.cipher_domain()
|
|
2799
|
+
if isinstance(D, AlphabeticStringMonoid):
|
|
2800
|
+
return D(strip_encoding(S))
|
|
2801
|
+
try:
|
|
2802
|
+
return D.encoding(S)
|
|
2803
|
+
except Exception:
|
|
2804
|
+
raise TypeError("Argument S = %s does not encode in the cipher domain" % S)
|
|
2805
|
+
|
|
2806
|
+
def inverse_key(self, K):
|
|
2807
|
+
r"""
|
|
2808
|
+
The inverse key corresponding to the key ``K``. For the shift cipher,
|
|
2809
|
+
the inverse key corresponding to ``K`` is `-K \bmod n`, where
|
|
2810
|
+
`n > 0` is the size of the cipher domain, i.e. the
|
|
2811
|
+
plaintext/ciphertext space. A key `k` of the shift cipher is an
|
|
2812
|
+
integer `0 \leq k < n`. The key `k = 0` has no effect on either the
|
|
2813
|
+
plaintext or the ciphertext.
|
|
2814
|
+
|
|
2815
|
+
INPUT:
|
|
2816
|
+
|
|
2817
|
+
- ``K`` -- a key for this shift cipher. This must be an integer `k`
|
|
2818
|
+
such that `0 \leq k < n`, where `n` is the size of the cipher domain
|
|
2819
|
+
|
|
2820
|
+
OUTPUT: the inverse key corresponding to ``K``
|
|
2821
|
+
|
|
2822
|
+
EXAMPLES:
|
|
2823
|
+
|
|
2824
|
+
Some random keys and their respective inverse keys::
|
|
2825
|
+
|
|
2826
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2827
|
+
sage: key = S.random_key(); key # random
|
|
2828
|
+
2
|
|
2829
|
+
sage: S.inverse_key(key) # random
|
|
2830
|
+
24
|
|
2831
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2832
|
+
sage: key = S.random_key(); key # random
|
|
2833
|
+
12
|
|
2834
|
+
sage: S.inverse_key(key) # random
|
|
2835
|
+
4
|
|
2836
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2837
|
+
sage: key = S.random_key(); key # random
|
|
2838
|
+
1
|
|
2839
|
+
sage: S.inverse_key(key) # random
|
|
2840
|
+
1
|
|
2841
|
+
sage: key = S.random_key(); key # random
|
|
2842
|
+
0
|
|
2843
|
+
sage: S.inverse_key(key) # random
|
|
2844
|
+
0
|
|
2845
|
+
|
|
2846
|
+
Regardless of the value of a key, the addition of the key and its
|
|
2847
|
+
inverse must be equal to the alphabet size. This relationship holds
|
|
2848
|
+
exactly when the value of the key is nonzero::
|
|
2849
|
+
|
|
2850
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2851
|
+
sage: K = S.random_key()
|
|
2852
|
+
sage: while K == 0:
|
|
2853
|
+
....: K = S.random_key()
|
|
2854
|
+
sage: invK = S.inverse_key(K)
|
|
2855
|
+
sage: K + invK == S.alphabet_size()
|
|
2856
|
+
True
|
|
2857
|
+
sage: invK + K == S.alphabet_size()
|
|
2858
|
+
True
|
|
2859
|
+
sage: K = S.random_key()
|
|
2860
|
+
sage: while K != 0:
|
|
2861
|
+
....: K = S.random_key()
|
|
2862
|
+
sage: invK = S.inverse_key(K)
|
|
2863
|
+
sage: K + invK != S.alphabet_size()
|
|
2864
|
+
True
|
|
2865
|
+
sage: K; invK
|
|
2866
|
+
0
|
|
2867
|
+
0
|
|
2868
|
+
|
|
2869
|
+
TESTS:
|
|
2870
|
+
|
|
2871
|
+
The key ``K`` must satisfy the inequality `0 \leq K < n` with `n`
|
|
2872
|
+
being the size of the plaintext, ciphertext, and key spaces. For the
|
|
2873
|
+
shift cryptosystem, all these spaces are the same alphabet. This
|
|
2874
|
+
inequality must be satisfied for each of the supported alphabets.
|
|
2875
|
+
The capital letters of the English alphabet::
|
|
2876
|
+
|
|
2877
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2878
|
+
sage: S.inverse_key(S.alphabet_size())
|
|
2879
|
+
Traceback (most recent call last):
|
|
2880
|
+
...
|
|
2881
|
+
ValueError: K (=26) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2882
|
+
sage: S.inverse_key(-1)
|
|
2883
|
+
Traceback (most recent call last):
|
|
2884
|
+
...
|
|
2885
|
+
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2886
|
+
|
|
2887
|
+
The hexadecimal number system::
|
|
2888
|
+
|
|
2889
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2890
|
+
sage: S.inverse_key(S.alphabet_size())
|
|
2891
|
+
Traceback (most recent call last):
|
|
2892
|
+
...
|
|
2893
|
+
ValueError: K (=16) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2894
|
+
sage: S.inverse_key(-1)
|
|
2895
|
+
Traceback (most recent call last):
|
|
2896
|
+
...
|
|
2897
|
+
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2898
|
+
|
|
2899
|
+
The binary number system::
|
|
2900
|
+
|
|
2901
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2902
|
+
sage: S.inverse_key(S.alphabet_size())
|
|
2903
|
+
Traceback (most recent call last):
|
|
2904
|
+
...
|
|
2905
|
+
ValueError: K (=2) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2906
|
+
sage: S.inverse_key(-1)
|
|
2907
|
+
Traceback (most recent call last):
|
|
2908
|
+
...
|
|
2909
|
+
ValueError: K (=-1) is outside the range of acceptable values for a key of this shift cryptosystem.
|
|
2910
|
+
"""
|
|
2911
|
+
# Sanity check: the key K must satisfy the inequality
|
|
2912
|
+
# 0 <= K < n with n being the size of the plaintext, ciphertext, and
|
|
2913
|
+
# key spaces. For the shift cryptosystem, all these spaces are the
|
|
2914
|
+
# same alphabet.
|
|
2915
|
+
if 0 <= K < self.alphabet_size():
|
|
2916
|
+
# Let A be the alphabet of this cryptosystem and let n be the
|
|
2917
|
+
# number of elements in A. If k is a key, then the corresponding
|
|
2918
|
+
# inverse key is -k mod n.
|
|
2919
|
+
return self.key_space()(-Integer(K)).lift()
|
|
2920
|
+
else:
|
|
2921
|
+
raise ValueError("K (=%s) is outside the range of acceptable values for a key of this shift cryptosystem." % K)
|
|
2922
|
+
|
|
2923
|
+
def random_key(self):
|
|
2924
|
+
r"""
|
|
2925
|
+
Generate a random key within the key space of this shift cipher.
|
|
2926
|
+
The generated key is an integer `0 \leq k < n` with `n` being the
|
|
2927
|
+
size of the cipher domain. Thus there are `n` possible keys in the
|
|
2928
|
+
key space, which is the set `\ZZ / n\ZZ`. The key `k = 0` has no
|
|
2929
|
+
effect on either the plaintext or the ciphertext.
|
|
2930
|
+
|
|
2931
|
+
OUTPUT: a random key within the key space of this shift cryptosystem
|
|
2932
|
+
|
|
2933
|
+
EXAMPLES::
|
|
2934
|
+
|
|
2935
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2936
|
+
sage: S.random_key() # random
|
|
2937
|
+
18
|
|
2938
|
+
sage: S = ShiftCryptosystem(BinaryStrings())
|
|
2939
|
+
sage: S.random_key() # random
|
|
2940
|
+
0
|
|
2941
|
+
sage: S = ShiftCryptosystem(HexadecimalStrings())
|
|
2942
|
+
sage: S.random_key() # random
|
|
2943
|
+
5
|
|
2944
|
+
|
|
2945
|
+
Regardless of the value of a key, the addition of the key and its
|
|
2946
|
+
inverse must be equal to the alphabet size. This relationship holds
|
|
2947
|
+
exactly when the value of the key is nonzero::
|
|
2948
|
+
|
|
2949
|
+
sage: S = ShiftCryptosystem(AlphabeticStrings())
|
|
2950
|
+
sage: K = S.random_key()
|
|
2951
|
+
sage: while K == 0:
|
|
2952
|
+
....: K = S.random_key()
|
|
2953
|
+
sage: invK = S.inverse_key(K)
|
|
2954
|
+
sage: K + invK == S.alphabet_size()
|
|
2955
|
+
True
|
|
2956
|
+
sage: invK + K == S.alphabet_size()
|
|
2957
|
+
True
|
|
2958
|
+
sage: K = S.random_key()
|
|
2959
|
+
sage: while K != 0:
|
|
2960
|
+
....: K = S.random_key()
|
|
2961
|
+
sage: invK = S.inverse_key(K)
|
|
2962
|
+
sage: K + invK != S.alphabet_size()
|
|
2963
|
+
True
|
|
2964
|
+
sage: K; invK
|
|
2965
|
+
0
|
|
2966
|
+
0
|
|
2967
|
+
"""
|
|
2968
|
+
# Return a random element in ZZ/nZZ where n is the number of elements
|
|
2969
|
+
# in the plaintext/ciphertext alphabet and key space.
|
|
2970
|
+
from sage.misc.prandom import randint
|
|
2971
|
+
return Integer(randint(0, self.alphabet_size() - 1))
|
|
2972
|
+
|
|
2973
|
+
|
|
2974
|
+
class SubstitutionCryptosystem(SymmetricKeyCryptosystem):
|
|
2975
|
+
"""
|
|
2976
|
+
Create a substitution cryptosystem.
|
|
2977
|
+
|
|
2978
|
+
INPUT:
|
|
2979
|
+
|
|
2980
|
+
- ``S`` -- string monoid over some alphabet
|
|
2981
|
+
|
|
2982
|
+
OUTPUT: a substitution cryptosystem over the alphabet ``S``
|
|
2983
|
+
|
|
2984
|
+
EXAMPLES::
|
|
2985
|
+
|
|
2986
|
+
sage: M = AlphabeticStrings()
|
|
2987
|
+
sage: E = SubstitutionCryptosystem(M)
|
|
2988
|
+
sage: E
|
|
2989
|
+
Substitution cryptosystem on Free alphabetic string monoid on A-Z
|
|
2990
|
+
sage: K = M([ 25-i for i in range(26) ])
|
|
2991
|
+
sage: K
|
|
2992
|
+
ZYXWVUTSRQPONMLKJIHGFEDCBA
|
|
2993
|
+
sage: e = E(K)
|
|
2994
|
+
sage: m = M("THECATINTHEHAT")
|
|
2995
|
+
sage: e(m)
|
|
2996
|
+
GSVXZGRMGSVSZG
|
|
2997
|
+
|
|
2998
|
+
TESTS::
|
|
2999
|
+
|
|
3000
|
+
sage: M = AlphabeticStrings()
|
|
3001
|
+
sage: E = SubstitutionCryptosystem(M)
|
|
3002
|
+
sage: E == loads(dumps(E))
|
|
3003
|
+
True
|
|
3004
|
+
"""
|
|
3005
|
+
|
|
3006
|
+
def __init__(self, S):
|
|
3007
|
+
"""
|
|
3008
|
+
See ``SubstitutionCryptosystem`` for full documentation.
|
|
3009
|
+
|
|
3010
|
+
EXAMPLES::
|
|
3011
|
+
|
|
3012
|
+
sage: M = AlphabeticStrings()
|
|
3013
|
+
sage: E = SubstitutionCryptosystem(M)
|
|
3014
|
+
sage: E
|
|
3015
|
+
Substitution cryptosystem on Free alphabetic string monoid on A-Z
|
|
3016
|
+
"""
|
|
3017
|
+
if not isinstance(S, StringMonoid_class):
|
|
3018
|
+
raise TypeError("S (= %s) must be a string monoid." % S)
|
|
3019
|
+
SymmetricKeyCryptosystem.__init__(self, S, S, S)
|
|
3020
|
+
|
|
3021
|
+
def __call__(self, K):
|
|
3022
|
+
"""
|
|
3023
|
+
Create a substitution cipher.
|
|
3024
|
+
|
|
3025
|
+
INPUT:
|
|
3026
|
+
|
|
3027
|
+
- ``K`` -- a key which is a permutation of the cryptosystem alphabet
|
|
3028
|
+
|
|
3029
|
+
EXAMPLES::
|
|
3030
|
+
|
|
3031
|
+
sage: M = AlphabeticStrings()
|
|
3032
|
+
sage: E = SubstitutionCryptosystem(M)
|
|
3033
|
+
sage: E
|
|
3034
|
+
Substitution cryptosystem on Free alphabetic string monoid on A-Z
|
|
3035
|
+
sage: K = M([ 25-i for i in range(26) ])
|
|
3036
|
+
sage: K
|
|
3037
|
+
ZYXWVUTSRQPONMLKJIHGFEDCBA
|
|
3038
|
+
sage: e = E(K)
|
|
3039
|
+
sage: m = M("THECATINTHEHAT")
|
|
3040
|
+
sage: e(m)
|
|
3041
|
+
GSVXZGRMGSVSZG
|
|
3042
|
+
"""
|
|
3043
|
+
if not isinstance(K, StringMonoidElement):
|
|
3044
|
+
raise TypeError("K (= %s) must be a string." % K)
|
|
3045
|
+
if K.parent() != self.key_space():
|
|
3046
|
+
raise TypeError("K (= %s) must be a string in the key space." % K)
|
|
3047
|
+
return SubstitutionCipher(self, K)
|
|
3048
|
+
|
|
3049
|
+
def _repr_(self):
|
|
3050
|
+
"""
|
|
3051
|
+
Return a string representation of ``self``.
|
|
3052
|
+
|
|
3053
|
+
EXAMPLES::
|
|
3054
|
+
|
|
3055
|
+
sage: A = AlphabeticStrings()
|
|
3056
|
+
sage: S = SubstitutionCryptosystem(A)
|
|
3057
|
+
sage: S
|
|
3058
|
+
Substitution cryptosystem on Free alphabetic string monoid on A-Z
|
|
3059
|
+
sage: S._repr_()
|
|
3060
|
+
'Substitution cryptosystem on Free alphabetic string monoid on A-Z'
|
|
3061
|
+
"""
|
|
3062
|
+
return "Substitution cryptosystem on %s" % self.cipher_domain()
|
|
3063
|
+
|
|
3064
|
+
def random_key(self):
|
|
3065
|
+
"""
|
|
3066
|
+
Generate a random key within the key space of this substitution
|
|
3067
|
+
cipher. The generated key is a permutation of the cryptosystem
|
|
3068
|
+
alphabet. Let `n` be the length of the alphabet. Then there are
|
|
3069
|
+
`n!` possible keys in the key space.
|
|
3070
|
+
|
|
3071
|
+
OUTPUT: a random key within the key space of this cryptosystem
|
|
3072
|
+
|
|
3073
|
+
EXAMPLES::
|
|
3074
|
+
|
|
3075
|
+
sage: A = AlphabeticStrings()
|
|
3076
|
+
sage: S = SubstitutionCryptosystem(A)
|
|
3077
|
+
sage: K = S.random_key()
|
|
3078
|
+
sage: Ki = S.inverse_key(K)
|
|
3079
|
+
sage: M = "THECATINTHEHAT"
|
|
3080
|
+
sage: e = S(K)
|
|
3081
|
+
sage: d = S(Ki)
|
|
3082
|
+
sage: d(e(A(M))) == A(M)
|
|
3083
|
+
True
|
|
3084
|
+
"""
|
|
3085
|
+
from sage.combinat.permutation import Permutations
|
|
3086
|
+
S = self.cipher_domain()
|
|
3087
|
+
n = S.ngens()
|
|
3088
|
+
I = Permutations(n).random_element()
|
|
3089
|
+
return S([ i-1 for i in I ])
|
|
3090
|
+
|
|
3091
|
+
def inverse_key(self, K):
|
|
3092
|
+
"""
|
|
3093
|
+
The inverse key corresponding to the key ``K``. The specified key is a
|
|
3094
|
+
permutation of the cryptosystem alphabet.
|
|
3095
|
+
|
|
3096
|
+
INPUT:
|
|
3097
|
+
|
|
3098
|
+
- ``K`` -- a key belonging to the key space of this cryptosystem
|
|
3099
|
+
|
|
3100
|
+
OUTPUT: the inverse key of ``K``
|
|
3101
|
+
|
|
3102
|
+
EXAMPLES::
|
|
3103
|
+
|
|
3104
|
+
sage: S = AlphabeticStrings()
|
|
3105
|
+
sage: E = SubstitutionCryptosystem(S)
|
|
3106
|
+
sage: K = E.random_key()
|
|
3107
|
+
sage: L = E.inverse_key(K)
|
|
3108
|
+
sage: M = S("THECATINTHEHAT")
|
|
3109
|
+
sage: e = E(K)
|
|
3110
|
+
sage: c = E(L)
|
|
3111
|
+
sage: c(e(M))
|
|
3112
|
+
THECATINTHEHAT
|
|
3113
|
+
"""
|
|
3114
|
+
I = K._element_list
|
|
3115
|
+
S = self.cipher_domain()
|
|
3116
|
+
n = S.ngens()
|
|
3117
|
+
return S([ I.index(i) for i in range(n) ])
|
|
3118
|
+
|
|
3119
|
+
def encoding(self, M):
|
|
3120
|
+
"""
|
|
3121
|
+
The encoding of the string ``M`` over the string monoid of this
|
|
3122
|
+
substitution cipher. For example, if the string monoid of this
|
|
3123
|
+
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
|
|
3124
|
+
of ``M`` would be its upper-case equivalent stripped of all
|
|
3125
|
+
non-alphabetic characters.
|
|
3126
|
+
|
|
3127
|
+
INPUT:
|
|
3128
|
+
|
|
3129
|
+
- ``M`` -- string, possibly empty
|
|
3130
|
+
|
|
3131
|
+
OUTPUT: the encoding of ``M`` over the string monoid of this
|
|
3132
|
+
cryptosystem
|
|
3133
|
+
|
|
3134
|
+
EXAMPLES::
|
|
3135
|
+
|
|
3136
|
+
sage: M = "Peter Pan(ning) for gold."
|
|
3137
|
+
sage: A = AlphabeticStrings()
|
|
3138
|
+
sage: S = SubstitutionCryptosystem(A)
|
|
3139
|
+
sage: S.encoding(M) == A.encoding(M)
|
|
3140
|
+
True
|
|
3141
|
+
"""
|
|
3142
|
+
S = self.cipher_domain()
|
|
3143
|
+
if isinstance(S, AlphabeticStringMonoid):
|
|
3144
|
+
return S(strip_encoding(M))
|
|
3145
|
+
try:
|
|
3146
|
+
return S.encoding(M)
|
|
3147
|
+
except Exception:
|
|
3148
|
+
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
|
|
3149
|
+
|
|
3150
|
+
def deciphering(self, K, C):
|
|
3151
|
+
"""
|
|
3152
|
+
Decrypt the ciphertext ``C`` using the key ``K``.
|
|
3153
|
+
|
|
3154
|
+
INPUT:
|
|
3155
|
+
|
|
3156
|
+
- ``K`` -- a key belonging to the key space of this substitution cipher
|
|
3157
|
+
|
|
3158
|
+
- ``C`` -- string (possibly empty) over the string monoid of this
|
|
3159
|
+
cryptosystem
|
|
3160
|
+
|
|
3161
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
3162
|
+
|
|
3163
|
+
EXAMPLES::
|
|
3164
|
+
|
|
3165
|
+
sage: S = SubstitutionCryptosystem(AlphabeticStrings())
|
|
3166
|
+
sage: K = S.random_key()
|
|
3167
|
+
sage: M = S.encoding("Don't substitute me!")
|
|
3168
|
+
sage: S.deciphering(K, S.enciphering(K, M)) == M
|
|
3169
|
+
True
|
|
3170
|
+
"""
|
|
3171
|
+
i = self(self.inverse_key(K))
|
|
3172
|
+
return i(C)
|
|
3173
|
+
|
|
3174
|
+
def enciphering(self, K, M):
|
|
3175
|
+
"""
|
|
3176
|
+
Encrypt the plaintext ``M`` using the key ``K``.
|
|
3177
|
+
|
|
3178
|
+
INPUT:
|
|
3179
|
+
|
|
3180
|
+
- ``K`` -- a key belonging to the key space of this substitution cipher
|
|
3181
|
+
|
|
3182
|
+
- ``M`` -- string (possibly empty) over the string monoid of this
|
|
3183
|
+
cryptosystem
|
|
3184
|
+
|
|
3185
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``M``
|
|
3186
|
+
|
|
3187
|
+
EXAMPLES::
|
|
3188
|
+
|
|
3189
|
+
sage: S = SubstitutionCryptosystem(AlphabeticStrings())
|
|
3190
|
+
sage: K = S.random_key()
|
|
3191
|
+
sage: M = S.encoding("Don't substitute me.")
|
|
3192
|
+
sage: S.deciphering(K, S.enciphering(K, M)) == M
|
|
3193
|
+
True
|
|
3194
|
+
"""
|
|
3195
|
+
e = self(K)
|
|
3196
|
+
return e(M)
|
|
3197
|
+
|
|
3198
|
+
|
|
3199
|
+
class TranspositionCryptosystem(SymmetricKeyCryptosystem):
|
|
3200
|
+
"""
|
|
3201
|
+
Create a transposition cryptosystem of block length ``n``.
|
|
3202
|
+
|
|
3203
|
+
INPUT:
|
|
3204
|
+
|
|
3205
|
+
- ``S`` -- string monoid over some alphabet
|
|
3206
|
+
|
|
3207
|
+
- ``n`` -- integer `> 0`; a block length of a block permutation
|
|
3208
|
+
|
|
3209
|
+
OUTPUT:
|
|
3210
|
+
|
|
3211
|
+
- A transposition cryptosystem of block length ``n`` over the
|
|
3212
|
+
alphabet ``S``.
|
|
3213
|
+
|
|
3214
|
+
EXAMPLES::
|
|
3215
|
+
|
|
3216
|
+
sage: S = AlphabeticStrings()
|
|
3217
|
+
sage: E = TranspositionCryptosystem(S,14); E # needs sage.groups
|
|
3218
|
+
Transposition cryptosystem on
|
|
3219
|
+
Free alphabetic string monoid on A-Z of block length 14
|
|
3220
|
+
sage: K = [14 - i for i in range(14)]; K
|
|
3221
|
+
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
3222
|
+
sage: e = E(K) # needs sage.groups
|
|
3223
|
+
sage: e(S("THECATINTHEHAT")) # needs sage.groups
|
|
3224
|
+
TAHEHTNITACEHT
|
|
3225
|
+
|
|
3226
|
+
TESTS::
|
|
3227
|
+
|
|
3228
|
+
sage: S = AlphabeticStrings()
|
|
3229
|
+
sage: E = TranspositionCryptosystem(S,14) # needs sage.groups
|
|
3230
|
+
sage: E == loads(dumps(E)) # needs sage.groups
|
|
3231
|
+
True
|
|
3232
|
+
"""
|
|
3233
|
+
|
|
3234
|
+
def __init__(self, S, n):
|
|
3235
|
+
"""
|
|
3236
|
+
See ``TranspositionCryptosystem`` for full documentation.
|
|
3237
|
+
|
|
3238
|
+
EXAMPLES::
|
|
3239
|
+
|
|
3240
|
+
sage: S = AlphabeticStrings()
|
|
3241
|
+
sage: E = TranspositionCryptosystem(S,14); E # needs sage.groups
|
|
3242
|
+
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
|
|
3243
|
+
"""
|
|
3244
|
+
if not isinstance(S, StringMonoid_class):
|
|
3245
|
+
raise TypeError("S (= %s) must be a string monoid." % S)
|
|
3246
|
+
key_space = SymmetricGroup(n)
|
|
3247
|
+
SymmetricKeyCryptosystem.__init__(self, S, S, key_space, block_length=n)
|
|
3248
|
+
|
|
3249
|
+
def __call__(self, K):
|
|
3250
|
+
"""
|
|
3251
|
+
Create a transposition cipher.
|
|
3252
|
+
|
|
3253
|
+
INPUT:
|
|
3254
|
+
|
|
3255
|
+
- ``K`` -- a key which specifies a block permutation
|
|
3256
|
+
|
|
3257
|
+
EXAMPLES::
|
|
3258
|
+
|
|
3259
|
+
sage: M = AlphabeticStrings()
|
|
3260
|
+
sage: E = TranspositionCryptosystem(M,14); E # needs sage.groups
|
|
3261
|
+
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
|
|
3262
|
+
sage: K = [14 - i for i in range(14)]; K
|
|
3263
|
+
[14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
3264
|
+
sage: e = E(K) # needs sage.groups
|
|
3265
|
+
sage: m = M("THECATINTHEHAT")
|
|
3266
|
+
sage: e(m) # needs sage.groups
|
|
3267
|
+
TAHEHTNITACEHT
|
|
3268
|
+
"""
|
|
3269
|
+
G = self.key_space()
|
|
3270
|
+
if isinstance(K, list):
|
|
3271
|
+
try:
|
|
3272
|
+
K = G(K)
|
|
3273
|
+
except Exception:
|
|
3274
|
+
raise TypeError("K (= %s) must specify a permutation." % K)
|
|
3275
|
+
if not isinstance(K, PermutationGroupElement) and K.parent() == G:
|
|
3276
|
+
raise TypeError("K (= %s) must be a permutation or list specifying a permutation." % K)
|
|
3277
|
+
return TranspositionCipher(self, K)
|
|
3278
|
+
|
|
3279
|
+
def _repr_(self):
|
|
3280
|
+
"""
|
|
3281
|
+
Return a string representation of ``self``.
|
|
3282
|
+
|
|
3283
|
+
EXAMPLES::
|
|
3284
|
+
|
|
3285
|
+
sage: A = AlphabeticStrings()
|
|
3286
|
+
sage: T = TranspositionCryptosystem(A, 14); T # needs sage.groups
|
|
3287
|
+
Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14
|
|
3288
|
+
sage: T._repr_() # needs sage.groups
|
|
3289
|
+
'Transposition cryptosystem on Free alphabetic string monoid on A-Z of block length 14'
|
|
3290
|
+
"""
|
|
3291
|
+
return "Transposition cryptosystem on %s of block length %s" % (
|
|
3292
|
+
self.cipher_domain(), self.block_length())
|
|
3293
|
+
|
|
3294
|
+
def random_key(self):
|
|
3295
|
+
"""
|
|
3296
|
+
Generate a random key within the key space of this transposition
|
|
3297
|
+
cryptosystem. Let `n > 0` be the block length of this cryptosystem.
|
|
3298
|
+
Then there are `n!` possible keys.
|
|
3299
|
+
|
|
3300
|
+
OUTPUT: a random key within the key space of this cryptosystem
|
|
3301
|
+
|
|
3302
|
+
EXAMPLES::
|
|
3303
|
+
|
|
3304
|
+
sage: # needs sage.groups
|
|
3305
|
+
sage: S = AlphabeticStrings()
|
|
3306
|
+
sage: E = TranspositionCryptosystem(S, 14)
|
|
3307
|
+
sage: K = E.random_key()
|
|
3308
|
+
sage: Ki = E.inverse_key(K)
|
|
3309
|
+
sage: e = E(K)
|
|
3310
|
+
sage: d = E(Ki)
|
|
3311
|
+
sage: M = "THECATINTHEHAT"
|
|
3312
|
+
sage: C = e(S(M))
|
|
3313
|
+
sage: d(S(C)) == S(M)
|
|
3314
|
+
True
|
|
3315
|
+
"""
|
|
3316
|
+
n = self.block_length()
|
|
3317
|
+
return SymmetricGroup(n).random_element()
|
|
3318
|
+
|
|
3319
|
+
def inverse_key(self, K, check=True):
|
|
3320
|
+
"""
|
|
3321
|
+
The inverse key corresponding to the key ``K``.
|
|
3322
|
+
|
|
3323
|
+
INPUT:
|
|
3324
|
+
|
|
3325
|
+
- ``K`` -- a key belonging to the key space of this transposition
|
|
3326
|
+
cipher
|
|
3327
|
+
|
|
3328
|
+
- ``check`` -- boolean (default: ``True``); check that ``K`` belongs to
|
|
3329
|
+
the key space of this cryptosystem
|
|
3330
|
+
|
|
3331
|
+
OUTPUT: the inverse key corresponding to ``K``
|
|
3332
|
+
|
|
3333
|
+
EXAMPLES::
|
|
3334
|
+
|
|
3335
|
+
sage: # needs sage.groups
|
|
3336
|
+
sage: S = AlphabeticStrings()
|
|
3337
|
+
sage: E = TranspositionCryptosystem(S, 14)
|
|
3338
|
+
sage: K = E.random_key()
|
|
3339
|
+
sage: Ki = E.inverse_key(K)
|
|
3340
|
+
sage: e = E(K)
|
|
3341
|
+
sage: d = E(Ki)
|
|
3342
|
+
sage: M = "THECATINTHEHAT"
|
|
3343
|
+
sage: C = e(S(M))
|
|
3344
|
+
sage: d(S(C)) == S(M)
|
|
3345
|
+
True
|
|
3346
|
+
"""
|
|
3347
|
+
if check:
|
|
3348
|
+
if K not in self.key_space():
|
|
3349
|
+
raise TypeError("Argument K (= %s) is not in the key space." % K)
|
|
3350
|
+
return K**-1
|
|
3351
|
+
|
|
3352
|
+
def encoding(self, M):
|
|
3353
|
+
"""
|
|
3354
|
+
The encoding of the string ``M`` over the string monoid of this
|
|
3355
|
+
transposition cipher. For example, if the string monoid of this
|
|
3356
|
+
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
|
|
3357
|
+
of ``M`` would be its upper-case equivalent stripped of all
|
|
3358
|
+
non-alphabetic characters.
|
|
3359
|
+
|
|
3360
|
+
INPUT:
|
|
3361
|
+
|
|
3362
|
+
- ``M`` -- string, possibly empty
|
|
3363
|
+
|
|
3364
|
+
OUTPUT: the encoding of ``M`` over the string monoid of this
|
|
3365
|
+
cryptosystem
|
|
3366
|
+
|
|
3367
|
+
EXAMPLES::
|
|
3368
|
+
|
|
3369
|
+
sage: M = "Transposition cipher is not about matrix transpose."
|
|
3370
|
+
sage: A = AlphabeticStrings()
|
|
3371
|
+
sage: T = TranspositionCryptosystem(A, 11) # needs sage.groups
|
|
3372
|
+
sage: T.encoding(M) == A.encoding(M) # needs sage.groups
|
|
3373
|
+
True
|
|
3374
|
+
"""
|
|
3375
|
+
S = self.cipher_domain()
|
|
3376
|
+
if isinstance(S, AlphabeticStringMonoid):
|
|
3377
|
+
return S(strip_encoding(M))
|
|
3378
|
+
try:
|
|
3379
|
+
return S.encoding(M)
|
|
3380
|
+
except Exception:
|
|
3381
|
+
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
|
|
3382
|
+
|
|
3383
|
+
def deciphering(self, K, C):
|
|
3384
|
+
"""
|
|
3385
|
+
Decrypt the ciphertext ``C`` using the key ``K``.
|
|
3386
|
+
|
|
3387
|
+
INPUT:
|
|
3388
|
+
|
|
3389
|
+
- ``K`` -- a key belonging to the key space of this transposition
|
|
3390
|
+
cipher
|
|
3391
|
+
|
|
3392
|
+
- ``C`` -- string (possibly empty) over the string monoid of this
|
|
3393
|
+
cryptosystem
|
|
3394
|
+
|
|
3395
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
3396
|
+
|
|
3397
|
+
EXAMPLES::
|
|
3398
|
+
|
|
3399
|
+
sage: # needs sage.groups
|
|
3400
|
+
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
|
|
3401
|
+
sage: K = T.random_key()
|
|
3402
|
+
sage: M = T.encoding("The cat in the hat.")
|
|
3403
|
+
sage: T.deciphering(K, T.enciphering(K, M)) == M
|
|
3404
|
+
True
|
|
3405
|
+
"""
|
|
3406
|
+
i = self(self.inverse_key(K))
|
|
3407
|
+
return i(C)
|
|
3408
|
+
|
|
3409
|
+
def enciphering(self, K, M):
|
|
3410
|
+
"""
|
|
3411
|
+
Encrypt the plaintext ``M`` using the key ``K``.
|
|
3412
|
+
|
|
3413
|
+
INPUT:
|
|
3414
|
+
|
|
3415
|
+
- ``K`` -- a key belonging to the key space of this transposition
|
|
3416
|
+
cipher
|
|
3417
|
+
|
|
3418
|
+
- ``M`` -- string (possibly empty) over the string monoid of this
|
|
3419
|
+
cryptosystem
|
|
3420
|
+
|
|
3421
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``M``
|
|
3422
|
+
|
|
3423
|
+
EXAMPLES::
|
|
3424
|
+
|
|
3425
|
+
sage: # needs sage.groups
|
|
3426
|
+
sage: T = TranspositionCryptosystem(AlphabeticStrings(), 14)
|
|
3427
|
+
sage: K = T.random_key()
|
|
3428
|
+
sage: M = T.encoding("The cat in the hat.")
|
|
3429
|
+
sage: T.deciphering(K, T.enciphering(K, M)) == M
|
|
3430
|
+
True
|
|
3431
|
+
"""
|
|
3432
|
+
e = self(K)
|
|
3433
|
+
return e(M)
|
|
3434
|
+
|
|
3435
|
+
|
|
3436
|
+
class VigenereCryptosystem(SymmetricKeyCryptosystem):
|
|
3437
|
+
"""
|
|
3438
|
+
Create a Vigenere cryptosystem of block length ``n``.
|
|
3439
|
+
|
|
3440
|
+
INPUT:
|
|
3441
|
+
|
|
3442
|
+
- ``S`` -- string monoid over some alphabet
|
|
3443
|
+
|
|
3444
|
+
- ``n`` -- integer `> 0`; block length of an encryption/decryption key
|
|
3445
|
+
|
|
3446
|
+
OUTPUT:
|
|
3447
|
+
|
|
3448
|
+
- A Vigenere cryptosystem of block length ``n`` over the alphabet
|
|
3449
|
+
``S``.
|
|
3450
|
+
|
|
3451
|
+
EXAMPLES::
|
|
3452
|
+
|
|
3453
|
+
sage: S = AlphabeticStrings()
|
|
3454
|
+
sage: E = VigenereCryptosystem(S,14)
|
|
3455
|
+
sage: E
|
|
3456
|
+
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
|
|
3457
|
+
sage: K = S('ABCDEFGHIJKLMN')
|
|
3458
|
+
sage: K
|
|
3459
|
+
ABCDEFGHIJKLMN
|
|
3460
|
+
sage: e = E(K)
|
|
3461
|
+
sage: e
|
|
3462
|
+
Cipher on Free alphabetic string monoid on A-Z
|
|
3463
|
+
sage: e(S("THECATINTHEHAT"))
|
|
3464
|
+
TIGFEYOUBQOSMG
|
|
3465
|
+
|
|
3466
|
+
TESTS::
|
|
3467
|
+
|
|
3468
|
+
sage: S = AlphabeticStrings()
|
|
3469
|
+
sage: E = VigenereCryptosystem(S,14)
|
|
3470
|
+
sage: E == loads(dumps(E))
|
|
3471
|
+
True
|
|
3472
|
+
"""
|
|
3473
|
+
|
|
3474
|
+
def __init__(self, S, n):
|
|
3475
|
+
"""
|
|
3476
|
+
See ``VigenereCryptosystem`` for full documentation.
|
|
3477
|
+
|
|
3478
|
+
EXAMPLES::
|
|
3479
|
+
|
|
3480
|
+
sage: S = AlphabeticStrings()
|
|
3481
|
+
sage: E = VigenereCryptosystem(S,14)
|
|
3482
|
+
sage: E
|
|
3483
|
+
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
|
|
3484
|
+
"""
|
|
3485
|
+
if not isinstance(S, StringMonoid_class):
|
|
3486
|
+
raise TypeError("S (= %s) must be a string monoid." % S)
|
|
3487
|
+
SymmetricKeyCryptosystem.__init__(self, S, S, S, block_length=1, period=n)
|
|
3488
|
+
|
|
3489
|
+
def __call__(self, K):
|
|
3490
|
+
"""
|
|
3491
|
+
Create a Vigenere cipher.
|
|
3492
|
+
|
|
3493
|
+
INPUT:
|
|
3494
|
+
|
|
3495
|
+
- ``K`` -- a key which specifies a block permutation
|
|
3496
|
+
|
|
3497
|
+
EXAMPLES::
|
|
3498
|
+
|
|
3499
|
+
sage: S = AlphabeticStrings()
|
|
3500
|
+
sage: E = VigenereCryptosystem(S,14)
|
|
3501
|
+
sage: E
|
|
3502
|
+
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
|
|
3503
|
+
sage: K = S('ABCDEFGHIJKLMN')
|
|
3504
|
+
sage: K
|
|
3505
|
+
ABCDEFGHIJKLMN
|
|
3506
|
+
sage: e = E(K)
|
|
3507
|
+
sage: e
|
|
3508
|
+
Cipher on Free alphabetic string monoid on A-Z
|
|
3509
|
+
sage: e(S("THECATINTHEHAT"))
|
|
3510
|
+
TIGFEYOUBQOSMG
|
|
3511
|
+
"""
|
|
3512
|
+
S = self.key_space()
|
|
3513
|
+
m = self.period()
|
|
3514
|
+
if isinstance(K, list):
|
|
3515
|
+
try:
|
|
3516
|
+
K = S(K)
|
|
3517
|
+
except Exception:
|
|
3518
|
+
raise TypeError("K (= %s) must specify a string of length %s." % (K, m))
|
|
3519
|
+
if not len(K) == m:
|
|
3520
|
+
raise TypeError("K (= %s) must specify a string of length %s." % (K, m))
|
|
3521
|
+
return VigenereCipher(self, K)
|
|
3522
|
+
|
|
3523
|
+
def _repr_(self):
|
|
3524
|
+
"""
|
|
3525
|
+
Return a string representation of ``self``.
|
|
3526
|
+
|
|
3527
|
+
EXAMPLES::
|
|
3528
|
+
|
|
3529
|
+
sage: A = AlphabeticStrings()
|
|
3530
|
+
sage: V = VigenereCryptosystem(A, 14)
|
|
3531
|
+
sage: V
|
|
3532
|
+
Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14
|
|
3533
|
+
sage: V._repr_()
|
|
3534
|
+
'Vigenere cryptosystem on Free alphabetic string monoid on A-Z of period 14'
|
|
3535
|
+
"""
|
|
3536
|
+
return "Vigenere cryptosystem on %s of period %s" % (
|
|
3537
|
+
self.cipher_domain(), self.period())
|
|
3538
|
+
|
|
3539
|
+
def random_key(self):
|
|
3540
|
+
"""
|
|
3541
|
+
Generate a random key within the key space of this Vigenere
|
|
3542
|
+
cryptosystem. Let `n > 0` be the length of the cryptosystem alphabet
|
|
3543
|
+
and let `m > 0` be the block length of this cryptosystem. Then there
|
|
3544
|
+
are `n^m` possible keys.
|
|
3545
|
+
|
|
3546
|
+
OUTPUT: a random key within the key space of this cryptosystem
|
|
3547
|
+
|
|
3548
|
+
EXAMPLES::
|
|
3549
|
+
|
|
3550
|
+
sage: A = AlphabeticStrings()
|
|
3551
|
+
sage: V = VigenereCryptosystem(A, 14)
|
|
3552
|
+
sage: M = "THECATINTHEHAT"
|
|
3553
|
+
sage: K = V.random_key()
|
|
3554
|
+
sage: Ki = V.inverse_key(K)
|
|
3555
|
+
sage: e = V(K)
|
|
3556
|
+
sage: d = V(Ki)
|
|
3557
|
+
sage: d(e(A(M))) == A(M)
|
|
3558
|
+
True
|
|
3559
|
+
"""
|
|
3560
|
+
S = self.key_space()
|
|
3561
|
+
n = S.ngens()
|
|
3562
|
+
m = self.period()
|
|
3563
|
+
return S([ randint(0, n-1) for i in range(m) ])
|
|
3564
|
+
|
|
3565
|
+
def inverse_key(self, K):
|
|
3566
|
+
"""
|
|
3567
|
+
The inverse key corresponding to the key ``K``.
|
|
3568
|
+
|
|
3569
|
+
INPUT:
|
|
3570
|
+
|
|
3571
|
+
- ``K`` -- a key within the key space of this Vigenere cryptosystem
|
|
3572
|
+
|
|
3573
|
+
OUTPUT: the inverse key corresponding to ``K``
|
|
3574
|
+
|
|
3575
|
+
EXAMPLES::
|
|
3576
|
+
|
|
3577
|
+
sage: S = AlphabeticStrings()
|
|
3578
|
+
sage: E = VigenereCryptosystem(S,14)
|
|
3579
|
+
sage: K = E.random_key()
|
|
3580
|
+
sage: L = E.inverse_key(K)
|
|
3581
|
+
sage: M = S("THECATINTHEHAT")
|
|
3582
|
+
sage: e = E(K)
|
|
3583
|
+
sage: c = E(L)
|
|
3584
|
+
sage: c(e(M))
|
|
3585
|
+
THECATINTHEHAT
|
|
3586
|
+
"""
|
|
3587
|
+
S = self.key_space()
|
|
3588
|
+
n = S.ngens()
|
|
3589
|
+
return S([ (-i) % (n) for i in K._element_list ])
|
|
3590
|
+
|
|
3591
|
+
def encoding(self, M):
|
|
3592
|
+
"""
|
|
3593
|
+
The encoding of the string ``M`` over the string monoid of this
|
|
3594
|
+
Vigenere cipher. For example, if the string monoid of this
|
|
3595
|
+
cryptosystem is :class:`AlphabeticStringMonoid`, then the encoding
|
|
3596
|
+
of ``M`` would be its upper-case equivalent stripped of all
|
|
3597
|
+
non-alphabetic characters.
|
|
3598
|
+
|
|
3599
|
+
INPUT:
|
|
3600
|
+
|
|
3601
|
+
- ``M`` -- string, possibly empty
|
|
3602
|
+
|
|
3603
|
+
OUTPUT: the encoding of ``M`` over the string monoid of this
|
|
3604
|
+
cryptosystem
|
|
3605
|
+
|
|
3606
|
+
EXAMPLES::
|
|
3607
|
+
|
|
3608
|
+
sage: A = AlphabeticStrings()
|
|
3609
|
+
sage: V = VigenereCryptosystem(A, 24)
|
|
3610
|
+
sage: M = "Jack and Jill went up the hill."
|
|
3611
|
+
sage: V.encoding(M) == A.encoding(M)
|
|
3612
|
+
True
|
|
3613
|
+
"""
|
|
3614
|
+
S = self.cipher_domain()
|
|
3615
|
+
if isinstance(S, AlphabeticStringMonoid):
|
|
3616
|
+
return S(strip_encoding(M))
|
|
3617
|
+
try:
|
|
3618
|
+
return S.encoding(M)
|
|
3619
|
+
except Exception:
|
|
3620
|
+
raise TypeError("Argument M = %s does not encode in the cipher domain" % M)
|
|
3621
|
+
|
|
3622
|
+
def deciphering(self, K, C):
|
|
3623
|
+
"""
|
|
3624
|
+
Decrypt the ciphertext ``C`` using the key ``K``.
|
|
3625
|
+
|
|
3626
|
+
INPUT:
|
|
3627
|
+
|
|
3628
|
+
- ``K`` -- a key belonging to the key space of this Vigenere cipher
|
|
3629
|
+
|
|
3630
|
+
- ``C`` -- string (possibly empty) over the string monoid of this
|
|
3631
|
+
cryptosystem
|
|
3632
|
+
|
|
3633
|
+
OUTPUT: the plaintext corresponding to the ciphertext ``C``
|
|
3634
|
+
|
|
3635
|
+
EXAMPLES::
|
|
3636
|
+
|
|
3637
|
+
sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
|
|
3638
|
+
sage: K = V.random_key()
|
|
3639
|
+
sage: M = V.encoding("Jack and Jill went up the hill.")
|
|
3640
|
+
sage: V.deciphering(K, V.enciphering(K, M)) == M
|
|
3641
|
+
True
|
|
3642
|
+
"""
|
|
3643
|
+
i = self(self.inverse_key(K))
|
|
3644
|
+
return i(C)
|
|
3645
|
+
|
|
3646
|
+
def enciphering(self, K, M):
|
|
3647
|
+
"""
|
|
3648
|
+
Encrypt the plaintext ``M`` using the key ``K``.
|
|
3649
|
+
|
|
3650
|
+
INPUT:
|
|
3651
|
+
|
|
3652
|
+
- ``K`` -- a key belonging to the key space of this Vigenere cipher
|
|
3653
|
+
|
|
3654
|
+
- ``M`` -- string (possibly empty) over the string monoid of this
|
|
3655
|
+
cryptosystem
|
|
3656
|
+
|
|
3657
|
+
OUTPUT: the ciphertext corresponding to the plaintext ``M``
|
|
3658
|
+
|
|
3659
|
+
EXAMPLES::
|
|
3660
|
+
|
|
3661
|
+
sage: V = VigenereCryptosystem(AlphabeticStrings(), 24)
|
|
3662
|
+
sage: K = V.random_key()
|
|
3663
|
+
sage: M = V.encoding("Jack and Jill went up the hill.")
|
|
3664
|
+
sage: V.deciphering(K, V.enciphering(K, M)) == M
|
|
3665
|
+
True
|
|
3666
|
+
"""
|
|
3667
|
+
e = self(K)
|
|
3668
|
+
return e(M)
|