diff --git a/.drone.yaml b/.drone.yaml index 9fbba0e..7e27df2 100644 --- a/.drone.yaml +++ b/.drone.yaml @@ -5,6 +5,42 @@ name: default steps: - name: test image: clojure:temurin-11-lein-2.10.0-alpine + volumes: + - name: package_cache + path: /root/.m2/ commands: - lein test + - name: build + image: clojure:temurin-11-lein-2.10.0-alpine + volumes: + - name: package_cache + path: /root/.m2/ + commands: + - lein uberjar + when: + event: tag + + - name: gitea_release + image: plugins/gitea-release + settings: + api_key: + from_secret: API_KEY + base_url: + from_secret: BASE_URL + files: + - target/uberjar/*.jar + checksum: + - md5 + - sha1 + - sha256 + - sha512 + - adler32 + - crc32 + when: + event: tag + +volumes: + - name: package_cache + temp: {} + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..2e638c4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,10 @@ +image: clojure:temurin-11-lein-2.10.0-alpine + +stages: + - test + +unit tests: + stage: test + script: + - lein test + diff --git a/CHANGELOG.md b/CHANGELOG.md index bf3b74d..246820a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.2] - 2023-10-21 + +### Changed +- Update the CHANGELOG. +- Update the version. +- Update the README. +- Update the CI/CD configuration to publish the artifacts to the release. +- Update the diffs in the CHANGELOG. + +## [0.7.1] - 2023-10-20 + +### Removed +- Remove the test for FrequencyAnalyzer. +- Remove the option for FrequencyAnalyzer. + +## [0.7.0] - 2023-10-20 + +### Changed +- Refactor to move actions to separated files. + +## [0.6.3] - 2023-10-14 + +### Added +- Add the IsNonsense interface for FrequencyAnalyzer. + +### Changed +- Update the frequency analyzer. + +## [0.6.2] - 2023-10-14 + +### Added +- Add the analyzer options and their actions. +- Add the available ciphers and analyzer for the help. +- Add validations to check if a cipher or an analyzer is available. + +### Changed +- Refactor to move options to a separated file. + +## [0.6.1] - 2023-10-13 + +### Added +- Add the function to make a frequency string from a map. + +### Fixed +- Fix the bug that the default language symbols override the symbols. + +### Changed +- Update the FrequencyAnalyzer to use the frequency string instead. + +## [0.6.0] - 2023-10-12 + +### Fixed +- Fix the typo "frequences" to frequencies. + +### Changed +- Refactor the project structure. + +## [0.5.5] - 2023-10-09 + +### Added +- Add the Gitlab CI/CD configuration. + +## [0.5.4] - 2023-10-05 + +### Added +- Add the java source path. +- Add the Caesar decrypted interface for Java. +- Add a frequency analyzer as a stub. +- Add the clojure wrapper for the frequency analizer and its test. + ## [0.5.3] - 2023-10-05 ### Added @@ -107,7 +177,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Project initialization. -[Unreleased]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.3...main?from_project_id=50218541&straight=false +[Unreleased]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.7.2...main?from_project_id=50218541&straight=false +[0.7.2]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.7.1...0.7.2?from_project_id=50218541&straight=false +[0.7.1]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.7.0...0.7.1?from_project_id=50218541&straight=false +[0.7.0]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.6.3...0.7.0?from_project_id=50218541&straight=false +[0.6.3]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.6.2...0.6.3?from_project_id=50218541&straight=false +[0.6.2]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.6.1...0.6.2?from_project_id=50218541&straight=false +[0.6.1]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.6.0...0.6.1?from_project_id=50218541&straight=false +[0.6.0]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.5...0.6.0?from_project_id=50218541&straight=false +[0.5.5]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.4...0.5.5?from_project_id=50218541&straight=false +[0.5.4]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.3...0.5.4?from_project_id=50218541&straight=false [0.5.3]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.2...0.5.3?from_project_id=50218541&straight=false [0.5.2]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.1...0.5.2?from_project_id=50218541&straight=false [0.5.1]: https://gitlab.com/KKlochko/cipher-analytical-machine/-/compare/0.5.0...0.5.1?from_project_id=50218541&straight=false diff --git a/README.md b/README.md index 1ce950d..3f3f08b 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,12 @@ Download from https://gitlab.com/KKlochko/cipher-analytical-machine. You need install java, before run the application. Run the application with a command: - $ java -jar cipher-analytical-machine-0.1.0-standalone.jar [args] + $ java -jar cipher-analytical-machine-VERSION-standalone.jar [args] + $ java -jar cipher-analytical-machine-0.7.2-standalone.jar [args] ## License Copyright © 2023 Kostiantyn Klochko Under the GNU Lesser General Public License v3.0 or later. + diff --git a/project.clj b/project.clj index b612dd7..116b5f3 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject cipher-analytical-machine "0.1.0-SNAPSHOT" +(defproject cipher-analytical-machine "0.7.2" :description "A program helps to learn how ciphers works." :url "https://gitlab.com/KKlochko/cipher-analytical-machine" :license {:name "LGPL" @@ -8,6 +8,7 @@ [org.apache.tika/tika-core "1.28.5"] [org.apache.tika/tika-langdetect "1.28.5"]] :main ^:skip-aot cipher-analytical-machine.core + :java-source-paths ["src/main/java"] :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}) diff --git a/src/cipher_analytical_machine/cipher_analyzers.clj b/src/cipher_analytical_machine/analyzers/analyzers.clj similarity index 84% rename from src/cipher_analytical_machine/cipher_analyzers.clj rename to src/cipher_analytical_machine/analyzers/analyzers.clj index b0fb3e5..d463afe 100644 --- a/src/cipher_analytical_machine/cipher_analyzers.clj +++ b/src/cipher_analytical_machine/analyzers/analyzers.clj @@ -1,4 +1,4 @@ -(ns cipher-analytical-machine.cipher-analyzers +(ns cipher-analytical-machine.analyzers.analyzers (:require [clojure.string :as cs]) (:gen-class)) @@ -21,16 +21,16 @@ (defn chi-squared-statistic "To calculate the frequency distribution of a language. More detail about the algorithm here: http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-statistic/" - [text letter-frequences] + [text letter-frequencies] (let [length-text (count text) letter-counts (count-characters (cs/lower-case text))] ;letter-counts (reduce (fn [acc [char count]] (+ acc - (if (contains? letter-frequences char) + (if (contains? letter-frequencies char) (chi-squared-letter-statistic count - (get letter-frequences char) + (get letter-frequencies char) length-text) 0))) 0 diff --git a/src/cipher_analytical_machine/analyzers/caesar.clj b/src/cipher_analytical_machine/analyzers/caesar.clj new file mode 100644 index 0000000..77f337b --- /dev/null +++ b/src/cipher_analytical_machine/analyzers/caesar.clj @@ -0,0 +1,64 @@ +(ns cipher-analytical-machine.analyzers.caesar + (:require [cipher-analytical-machine.ciphers.caesar :as caesar] + [clojure.string :as cs] + [cipher-analytical-machine.analyzers.language :as language] + [cipher-analytical-machine.analyzers.analyzers :as ca]) + (:import [cipher_analytical_machine.ciphers.caesar Decrypted] + [cipher_analytical_machine.analyzers.caesar IsNonsense] + [cipher_analytical_machine.analyzers.caesar FrequencyAnalyzer]) + (:gen-class)) + +(defn get-all-texts + "Return a list of pairs which have a key and a second posible plaintext." + [ciphertext symbols] + (let [keys (range (count symbols))] + (reduce + (fn [acc key] + (conj acc [key (caesar/decrypt-message ciphertext key symbols)])) + '() keys))) + +(defn get-all-scores + "For pairs (key, plaintext) finds scores and return list of pairs [key, score]." + [letter-frequencies pairs] + (map (fn [[key text]] + [key (ca/chi-squared-statistic text letter-frequencies)]) + pairs)) + +(defn get-min-score-pair + "For pairs (key, plaintext) finds scores and return list of pairs [key, score]." + [pairs] + (reduce + (fn [[key value] [new-key new-value]] + (if (> value new-value) [new-key new-value] + [key value])) + [0 Double/MAX_VALUE] + pairs)) + +(defn get-key + "To find the key with frequencies of letters." + [ciphertext symbols letter-frequencies] + (->> (get-all-texts ciphertext symbols) + (get-all-scores letter-frequencies) + (get-min-score-pair) + (first))) + +(defn get-plaintext + "Return the plaintext from a ciphertext. The function is case-insensitive." + [ciphertext symbols letter-frequencies] + (let [ciphertext (cs/lower-case ciphertext)] + (caesar/decrypt-message ciphertext + (get-key ciphertext symbols letter-frequencies) + symbols))) + +(defn frequency-analizer-get-plaintext + "Return the plaintext from a ciphertext using simple analizer. The function is case-insensitive." + [ciphertext symbols letter-frequencies-string language-code] + (let [decrypt (reify Decrypted + (decrypt [this message key symbols] + (caesar/decrypt-message message key symbols))) + is-nonsense (reify IsNonsense + (isNonsense [this message] + (language/is-nonsense? message language-code)))] + (-> (new FrequencyAnalyzer ciphertext symbols letter-frequencies-string decrypt is-nonsense) + .crack))) + diff --git a/src/cipher_analytical_machine/language_analyzer.clj b/src/cipher_analytical_machine/analyzers/language.clj similarity index 90% rename from src/cipher_analytical_machine/language_analyzer.clj rename to src/cipher_analytical_machine/analyzers/language.clj index 867c17f..ddfc7ab 100644 --- a/src/cipher_analytical_machine/language_analyzer.clj +++ b/src/cipher_analytical_machine/analyzers/language.clj @@ -1,4 +1,4 @@ -(ns cipher-analytical-machine.language-analyzer +(ns cipher-analytical-machine.analyzers.language (:import [org.apache.tika.language LanguageIdentifier]) (:gen-class)) diff --git a/src/cipher_analytical_machine/caesar_analyzers.clj b/src/cipher_analytical_machine/caesar_analyzers.clj deleted file mode 100644 index 4d71f06..0000000 --- a/src/cipher_analytical_machine/caesar_analyzers.clj +++ /dev/null @@ -1,48 +0,0 @@ -(ns cipher-analytical-machine.caesar-analyzers - (:require [cipher-analytical-machine.caesar :as caesar] - [clojure.string :as cs] - [cipher-analytical-machine.cipher-analyzers :as ca]) - (:gen-class)) - -(defn get-all-texts - "Return a list of pairs which have a key and a second posible plaintext." - [ciphertext symbols] - (let [keys (range (count symbols))] - (reduce - (fn [acc key] - (conj acc [key (caesar/decrypt-message ciphertext key symbols)])) - '() keys))) - -(defn get-all-scores - "For pairs (key, plaintext) finds scores and return list of pairs [key, score]." - [letter-frequences pairs] - (map (fn [[key text]] - [key (ca/chi-squared-statistic text letter-frequences)]) - pairs)) - -(defn get-min-score-pair - "For pairs (key, plaintext) finds scores and return list of pairs [key, score]." - [pairs] - (reduce - (fn [[key value] [new-key new-value]] - (if (> value new-value) [new-key new-value] - [key value])) - [0 Double/MAX_VALUE] - pairs)) - -(defn get-key - "To find the key with frequencies of letters." - [ciphertext symbols letter-frequences] - (->> (get-all-texts ciphertext symbols) - (get-all-scores letter-frequences) - (get-min-score-pair) - (first))) - -(defn get-plaintext - "Return the plaintext from a ciphertext. The function is case-insensitive." - [ciphertext symbols letter-frequences] - (let [ciphertext (cs/lower-case ciphertext)] - (caesar/decrypt-message ciphertext - (get-key ciphertext symbols letter-frequences) - symbols))) - diff --git a/src/cipher_analytical_machine/caesar.clj b/src/cipher_analytical_machine/ciphers/caesar.clj similarity index 96% rename from src/cipher_analytical_machine/caesar.clj rename to src/cipher_analytical_machine/ciphers/caesar.clj index cdaba11..6b8b897 100644 --- a/src/cipher_analytical_machine/caesar.clj +++ b/src/cipher_analytical_machine/ciphers/caesar.clj @@ -1,4 +1,4 @@ -(ns cipher-analytical-machine.caesar +(ns cipher-analytical-machine.ciphers.caesar (:require [clojure.string :as cs]) (:gen-class)) diff --git a/src/cipher_analytical_machine/cli/actions/cracking.clj b/src/cipher_analytical_machine/cli/actions/cracking.clj new file mode 100644 index 0000000..5e68aa1 --- /dev/null +++ b/src/cipher_analytical_machine/cli/actions/cracking.clj @@ -0,0 +1,26 @@ +(ns cipher-analytical-machine.cli.actions.cracking + (:require + [cipher-analytical-machine.analyzers.caesar :as caesar-analyzers] + [cipher-analytical-machine.symbols.frequencies :as symbol-frequencies]) + (:gen-class)) + +(defn cracking-caesar + [options arguments] + (let [analyzer (:analyzer options) + message (:message options) + symbols (:symbols options) + frequencies symbol-frequencies/english-letter-frequencies] + (cond + (= analyzer "Chi^2") + (caesar-analyzers/get-plaintext message symbols frequencies) + + :else + (caesar-analyzers/get-plaintext message symbols frequencies)))) + +(defn cracking-actions + [options arguments action-type] + (let [cipher (:cipher options)] + (cond + (= cipher "Caesar") + (cracking-caesar options arguments)))) + diff --git a/src/cipher_analytical_machine/cli/actions/cryptography.clj b/src/cipher_analytical_machine/cli/actions/cryptography.clj new file mode 100644 index 0000000..cdddd12 --- /dev/null +++ b/src/cipher_analytical_machine/cli/actions/cryptography.clj @@ -0,0 +1,24 @@ +(ns cipher-analytical-machine.cli.actions.cryptography + (:require + [cipher-analytical-machine.ciphers.caesar :as caesar]) + (:gen-class)) + +(defn caesar-actions + [options arguments action-type] + (let [message (:message options) + key (Integer/parseInt (:key options)) + symbols (:symbols options)] + (cond + (= action-type :encrypt) + (caesar/encrypt-message message key symbols) + + (= action-type :decrypt) + (caesar/decrypt-message message key symbols)))) + +(defn cryptography-actions + [options arguments action-type] + (let [cipher (:cipher options)] + (cond + (= cipher "Caesar") + (caesar-actions options arguments action-type)))) + diff --git a/src/cipher_analytical_machine/cli.clj b/src/cipher_analytical_machine/cli/cli.clj similarity index 64% rename from src/cipher_analytical_machine/cli.clj rename to src/cipher_analytical_machine/cli/cli.clj index 2b198c7..c4f67bc 100644 --- a/src/cipher_analytical_machine/cli.clj +++ b/src/cipher_analytical_machine/cli/cli.clj @@ -1,36 +1,14 @@ -(ns cipher-analytical-machine.cli +(ns cipher-analytical-machine.cli.cli (:require - [cipher-analytical-machine.file :as file] - [cipher-analytical-machine.caesar :as caesar] - [cipher-analytical-machine.caesar-analyzers :as caesar-analyzers] - [cipher-analytical-machine.symbol-factories :as sf] - [cipher-analytical-machine.symbol-frequences :as symbol-frequences] + [cipher-analytical-machine.cli.file :as file] + [cipher-analytical-machine.symbols.factories :as sf] + [cipher-analytical-machine.cli.options :as options] [clojure.string :as cs] + [cipher-analytical-machine.cli.actions.cryptography :as cryptography] + [cipher-analytical-machine.cli.actions.cracking :as cracking] [clojure.tools.cli :refer [parse-opts]]) (:gen-class)) -(def cipher-options - #{"Caesar"}) - -(def language-options - #{"en", "uk"}) - -(def cli-options - [["-m" "--message MESSAGE" "The message will be encrypted or decrypted."] - ["-M" "--message-file MESSAGE-FILE" "The file contains the message that will be encrypted or decrypted."] - ["-k" "--key KEY" "The key will be used to encrypt or decrypt."] - ["-c" "--cipher CIPHER" "The cipher will be used to encrypt or decrypt a message." - :default "Caesar"] - ["-e" "--encrypt" "Encrypt the message."] - ["-d" "--decrypt" "Decrypt the message."] - ["-s" "--symbols" "The string will be used as a set of symbols for a cipher." - :default (sf/default-symbol-factory "en")] - ["-l" "--language CODE" "The string will be used to set a default symbols for a cipher." - :validate [#(contains? language-options %) "Must be a code from the list: en, uk!!!"]] - ["-C" "--cracking" "Cracking the encrypted message."] - ["-O" "--output-file OUTPUT-FILE" "Save the program output to a file."] - ["-h" "--help"]]) - (defn usage [options-summary] (->> ["This is cipher-analytical-machine. It can help you to learn how ciphers works." "" @@ -38,6 +16,12 @@ "" "Options:" options-summary + "" + "Available values" + "" + (options/get-available-cipher-options-as-string) + "" + (options/get-available-analyzers-options-as-string) ""] (cs/join \newline))) @@ -55,7 +39,7 @@ should exit (with an error message, and optional ok status), or a map indicating the action the program should take and the options provided." [args] - (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)] + (let [{:keys [options arguments errors summary]} (parse-opts args options/cli-options)] (cond (:help options) {:exit-message (usage summary) :ok? true} @@ -77,6 +61,16 @@ (file/is-file-exists? (:output-file options))) {:exit-message (error-msg ["Please, choose another file!!! Overwriting was prevented!!!"])} + (false? (contains? options/cipher-options (:cipher options))) + {:exit-message (error-msg ["Please, choose an available cipher!!!\n" + (options/get-available-cipher-options-as-string)])} + + (and (map-has-keys? options [:cipher, :cracking, :analyzer]) + (false? (contains? + (get options/analyzers-options (:cipher options)) (:analyzer options)))) + {:exit-message (error-msg ["Please, choose an available analyzer!!!\n" + (options/get-available-analyzers-options-as-string)])} + (and (map-has-keys? options [:cipher, :key]) (or (contains? options :message) (contains? options :message-file)) @@ -122,8 +116,10 @@ (defn set-symbols "Set defaults symbols for a language." [options] - (set-option options :symbols - (sf/default-symbol-factory (:language options)))) + (if (contains? options :language) + (set-option options :symbols + (sf/default-symbol-factory (:language options))) + options)) (defn load-and-set-option "Load the option and set it." @@ -151,38 +147,16 @@ (save-output output options)) (println output)) -(defn cracking-actions - [options arguments action-type] - (let [message (:message options) - symbols (:symbols options) - frequencies symbol-frequences/english-letter-frequences] - (cond - (= action-type :cracking) - (caesar-analyzers/get-plaintext message symbols frequencies) - ))) - -(defn crypt-actions - [options arguments action-type] - (let [message (:message options) - key (Integer/parseInt (:key options)) - symbols (:symbols options)] - (cond - (= action-type :encrypt) - (caesar/encrypt-message message key symbols) - - (= action-type :decrypt) - (caesar/decrypt-message message key symbols)))) - (defn actions [options arguments action-type] (let [options (load-all-options options)] (-> (cond (contains? #{:encrypt :decrypt} action-type) - (crypt-actions options arguments action-type) + (cryptography/cryptography-actions options arguments action-type) (= action-type :cracking) - (cracking-actions options arguments action-type)) + (cracking/cracking-actions options arguments action-type)) (show-and-save-output options)))) diff --git a/src/cipher_analytical_machine/file.clj b/src/cipher_analytical_machine/cli/file.clj similarity index 91% rename from src/cipher_analytical_machine/file.clj rename to src/cipher_analytical_machine/cli/file.clj index bc58876..7de67e9 100644 --- a/src/cipher_analytical_machine/file.clj +++ b/src/cipher_analytical_machine/cli/file.clj @@ -1,4 +1,4 @@ -(ns cipher-analytical-machine.file +(ns cipher-analytical-machine.cli.file (:require [clojure.java.io :as io]) (:gen-class)) diff --git a/src/cipher_analytical_machine/cli/options.clj b/src/cipher_analytical_machine/cli/options.clj new file mode 100644 index 0000000..0c9d968 --- /dev/null +++ b/src/cipher_analytical_machine/cli/options.clj @@ -0,0 +1,64 @@ +(ns cipher-analytical-machine.cli.options + (:require + [cipher-analytical-machine.symbols.factories :as sf] + [clojure.string :as cs]) + (:gen-class)) + +(defn option-values-as-row + "Return the formatted string of values for an option." + [option values] + (str option ": " (cs/join ", " values))) + +(defn option-values-as-list + "Return the formatted string of values for an option as a multiline list." + [option values] + (str option ": \n - " (cs/join "\n - " values))) + +(defn map-of-option-value-as-list + "Return the formatted string of values for an option as a multiline list. + The values are a map of pairs (suboption and its options)." + [option values] + (let [values (map #(apply option-values-as-row %) values)] + (option-values-as-list option values))) + +(def cipher-options + #{"Caesar"}) + +(def default-cipher "Caesar") + +(defn get-available-cipher-options-as-string + "Return the formatted string of analyzers options." + [] + (option-values-as-row "Ciphers" cipher-options)) + +(def analyzers-options + {"Caesar" #{"Chi^2"}}) + +(defn get-available-analyzers-options-as-string + "Return the formatted string of analyzers options." + [] + (map-of-option-value-as-list "Analyzers" analyzers-options)) + +(def language-options + #{"en", "uk"}) + +(def default-symbols + (sf/default-symbol-factory "en")) + +(def cli-options + [["-m" "--message MESSAGE" "The message will be encrypted or decrypted."] + ["-M" "--message-file MESSAGE-FILE" "The file contains the message that will be encrypted or decrypted."] + ["-k" "--key KEY" "The key will be used to encrypt or decrypt."] + ["-c" "--cipher CIPHER" "The cipher will be used to encrypt or decrypt a message." + :default default-cipher] + ["-e" "--encrypt" "Encrypt the message."] + ["-d" "--decrypt" "Decrypt the message."] + ["-s" "--symbols SYMBOLS" "The string will be used as a set of symbols for a cipher." + :default default-symbols] + ["-l" "--language CODE" "The string will be used to set a default symbols for a cipher." + :validate [#(contains? language-options %) "Must be a code from the list: en, uk!!!"]] + ["-C" "--cracking" "Cracking the encrypted message."] + ["-a" "--analyzer ANALYZER" "The way of cracking."] + ["-O" "--output-file OUTPUT-FILE" "Save the program output to a file."] + ["-h" "--help"]]) + diff --git a/src/cipher_analytical_machine/core.clj b/src/cipher_analytical_machine/core.clj index 5e7e992..e5106b4 100644 --- a/src/cipher_analytical_machine/core.clj +++ b/src/cipher_analytical_machine/core.clj @@ -1,6 +1,6 @@ (ns cipher-analytical-machine.core (:require - [cipher-analytical-machine.cli :as cli]) + [cipher-analytical-machine.cli.cli :as cli]) (:gen-class)) (defn -main diff --git a/src/cipher_analytical_machine/symbol_factories.clj b/src/cipher_analytical_machine/symbols/factories.clj similarity index 95% rename from src/cipher_analytical_machine/symbol_factories.clj rename to src/cipher_analytical_machine/symbols/factories.clj index 65ac2f5..7cbd8a3 100644 --- a/src/cipher_analytical_machine/symbol_factories.clj +++ b/src/cipher_analytical_machine/symbols/factories.clj @@ -1,4 +1,4 @@ -(ns cipher-analytical-machine.symbol-factories +(ns cipher-analytical-machine.symbols.factories (:require [clojure.string :as cs]) (:gen-class)) diff --git a/src/cipher_analytical_machine/symbol_frequences.clj b/src/cipher_analytical_machine/symbols/frequencies.clj similarity index 65% rename from src/cipher_analytical_machine/symbol_frequences.clj rename to src/cipher_analytical_machine/symbols/frequencies.clj index 3878e31..dad6368 100644 --- a/src/cipher_analytical_machine/symbol_frequences.clj +++ b/src/cipher_analytical_machine/symbols/frequencies.clj @@ -1,7 +1,17 @@ -(ns cipher-analytical-machine.symbol-frequences +(ns cipher-analytical-machine.symbols.frequencies (:gen-class)) -(def english-letter-frequences { +(defn map-to-string + "Convert the map of frequencies to the frequency string. Letters are sorted by frequency in descending order." + [frequency-map] + (->> frequency-map + (reduce (fn [acc el] (conj acc el)) []) + (sort-by last) + (map first) + (reverse) + (apply str))) + +(def english-letter-frequencies { \a 0.0804 \b 0.0154 \c 0.0306 @@ -30,7 +40,7 @@ \z 0.0009 }) -(def ukrainian-letter-frequences { +(def ukrainian-letter-frequencies { \а 0.064 \б 0.013 \в 0.046 diff --git a/src/main/java/cipher_analytical_machine/analyzers/caesar/FrequencyAnalyzer.java b/src/main/java/cipher_analytical_machine/analyzers/caesar/FrequencyAnalyzer.java new file mode 100644 index 0000000..ac75e0f --- /dev/null +++ b/src/main/java/cipher_analytical_machine/analyzers/caesar/FrequencyAnalyzer.java @@ -0,0 +1,53 @@ +package cipher_analytical_machine.analyzers.caesar; + +import cipher_analytical_machine.analyzers.caesar.IsNonsense; +import cipher_analytical_machine.ciphers.caesar.Decrypted; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class FrequencyAnalyzer { + private String ciphertext; + private String symbols; + private String symbol_frequences; + private Decrypted decryptor; + private IsNonsense isNonsense; + + public FrequencyAnalyzer(String ciphertext, String symbols, String symbol_frequences, Decrypted decryptor, IsNonsense isNonsense) { + this.ciphertext = ciphertext; + this.symbol_frequences = symbol_frequences; + this.symbols = symbols; + this.decryptor = decryptor; + this.isNonsense = isNonsense; + } + + public String crack() { + // Знаходимо кількість літер + Map encryptedLetterCounts = new HashMap<>(); + + for (char c : ciphertext.toLowerCase().toCharArray()) { + encryptedLetterCounts.put(c, encryptedLetterCounts.getOrDefault(c, 0) + 1); + } + + // Знаходимо букву, яка зустрічається найчастіше + char mostFrequentEncryptedLetter = ' '; + int mostFrequentEncryptedLetterCount = 0; + + for (Entry entry : encryptedLetterCounts.entrySet()) { + if (entry.getValue() > mostFrequentEncryptedLetterCount) { + mostFrequentEncryptedLetter = entry.getKey(); + mostFrequentEncryptedLetterCount = entry.getValue(); + } + } + + // Обчислимо зміщення + int size = symbols.length(); + int indexOfMostFrequentEncryptedLetter = symbols.indexOf(mostFrequentEncryptedLetter); + int indexOfMostFrequentLetter = symbols.indexOf(symbol_frequences.charAt(0)); + int shift = (indexOfMostFrequentEncryptedLetter - indexOfMostFrequentLetter) % size; + + return decryptor.decrypt(ciphertext, shift, symbols); + } +} + diff --git a/src/main/java/cipher_analytical_machine/analyzers/caesar/IsNonsense.java b/src/main/java/cipher_analytical_machine/analyzers/caesar/IsNonsense.java new file mode 100644 index 0000000..c956921 --- /dev/null +++ b/src/main/java/cipher_analytical_machine/analyzers/caesar/IsNonsense.java @@ -0,0 +1,6 @@ +package cipher_analytical_machine.analyzers.caesar; + +public interface IsNonsense { + boolean isNonsense(String message); +} + diff --git a/src/main/java/cipher_analytical_machine/ciphers/caesar/Decrypted.java b/src/main/java/cipher_analytical_machine/ciphers/caesar/Decrypted.java new file mode 100644 index 0000000..1b86df2 --- /dev/null +++ b/src/main/java/cipher_analytical_machine/ciphers/caesar/Decrypted.java @@ -0,0 +1,6 @@ +package cipher_analytical_machine.ciphers.caesar; + +public interface Decrypted { + String decrypt(String message, int key, String symbols); +} + diff --git a/test/cipher_analytical_machine/caesar_analyzers_test.clj b/test/cipher_analytical_machine/analyzers/caesar_test.clj similarity index 79% rename from test/cipher_analytical_machine/caesar_analyzers_test.clj rename to test/cipher_analytical_machine/analyzers/caesar_test.clj index 2a06d97..2c2977d 100644 --- a/test/cipher_analytical_machine/caesar_analyzers_test.clj +++ b/test/cipher_analytical_machine/analyzers/caesar_test.clj @@ -1,9 +1,9 @@ -(ns cipher-analytical-machine.caesar-analyzers-test +(ns cipher-analytical-machine.analyzers.caesar-test (:require [clojure.test :refer :all] - [cipher-analytical-machine.caesar-analyzers :refer :all] - [cipher-analytical-machine.symbol-frequences :as sf] - [cipher-analytical-machine.cipher-analyzers :as ca] + [cipher-analytical-machine.analyzers.caesar :refer :all] + [cipher-analytical-machine.symbols.frequencies :as sf] + [cipher-analytical-machine.analyzers.analyzers :as ca] [clojure.string :as cs])) (deftest get-all-texts-test @@ -25,9 +25,9 @@ (testing "There're 27 combinations for a 'Hello World'." (let [ciphertext "khoorczruog" symbols "abcdefghijklmnopqrstuvwxyz " - frequences sf/english-letter-frequences + frequencies sf/english-letter-frequencies text-pairs (get-all-texts ciphertext symbols)] - (is (= 27 (count (get-all-scores frequences text-pairs))))))) + (is (= 27 (count (get-all-scores frequencies text-pairs))))))) (deftest get-min-score-pair-test (testing "If a key is one, then the min score pair is 15." @@ -40,37 +40,37 @@ ciphertext "khoorczruog" key 3 symbols "abcdefghijklmnopqrstuvwxyz " - frequences sf/english-letter-frequences] + frequencies sf/english-letter-frequencies] (testing "The plaintext is encrypted with 3 as the key." - (is (= key (get-key ciphertext symbols frequences))))) + (is (= key (get-key ciphertext symbols frequencies))))) (let [; З поеми "Кавказ" Тараса Григоровича Шевченка: plaintext "Борітеся – поборете, Вам Бог помагає! За вас правда, за вас слава. І воля святая!" ciphertext "жхчощйшде–ецхжхчйщй,езєуежхиецхуєиєк!емєезєшецчєзїє,емєезєшештєзє.еоезхтдешздщєд!" key 7 symbols "абвгґдеєжзиіїйклмнопрстуфхцчшщьюя " - frequences sf/ukrainian-letter-frequences] + frequencies sf/ukrainian-letter-frequencies] (testing "The plaintext is encrypted with 3 as the key." - (is (= key (get-key ciphertext symbols frequences)))))) + (is (= key (get-key ciphertext symbols frequencies)))))) (deftest get-plaintext-test (let [plaintext "hello world" ciphertext "khoorczruog" symbols "abcdefghijklmnopqrstuvwxyz " - frequences sf/english-letter-frequences] + frequencies sf/english-letter-frequencies] (testing "The plaintext is encrypted with 3 as the key." - (is (= plaintext (get-plaintext ciphertext symbols frequences)))) + (is (= plaintext (get-plaintext ciphertext symbols frequencies)))) (testing "The ciphertext is case-insensitive." - (is (= plaintext (get-plaintext "KhoorcZruog" symbols frequences))))) + (is (= plaintext (get-plaintext "KhoorcZruog" symbols frequencies))))) (let [; З поеми "Кавказ" Тараса Григоровича Шевченка: plaintext "Борітеся – поборете, Вам Бог помагає! За вас правда, за вас слава. І воля святая!" ciphertext "жхчощйшде–ецхжхчйщй,езєуежхиецхуєиєк!емєезєшецчєзїє,емєезєшештєзє.еоезхтдешздщєд!" key 7 symbols "абвгґдеєжзиіїйклмнопрстуфхцчшщьюя " - frequences sf/ukrainian-letter-frequences] + frequencies sf/ukrainian-letter-frequencies] (testing "The ciphertext is case-insensitive." (is (= (cs/lower-case plaintext) - (get-plaintext ciphertext symbols frequences)))))) + (get-plaintext ciphertext symbols frequencies)))))) diff --git a/test/cipher_analytical_machine/language_analyzer_test.clj b/test/cipher_analytical_machine/analyzers/language_test.clj similarity index 93% rename from test/cipher_analytical_machine/language_analyzer_test.clj rename to test/cipher_analytical_machine/analyzers/language_test.clj index c380455..63317f6 100644 --- a/test/cipher_analytical_machine/language_analyzer_test.clj +++ b/test/cipher_analytical_machine/analyzers/language_test.clj @@ -1,7 +1,7 @@ -(ns cipher-analytical-machine.language-analyzer-test +(ns cipher-analytical-machine.analyzers.language-test (:require [clojure.test :refer :all] - [cipher-analytical-machine.language-analyzer :refer :all] + [cipher-analytical-machine.analyzers.language :refer :all] )) (deftest detect-language-test diff --git a/test/cipher_analytical_machine/cipher_analyzers_test.clj b/test/cipher_analytical_machine/cipher_analyzers_test.clj deleted file mode 100644 index 16991f2..0000000 --- a/test/cipher_analytical_machine/cipher_analyzers_test.clj +++ /dev/null @@ -1,25 +0,0 @@ -(ns cipher-analytical-machine.cipher-analyzers-test - (:require - [clojure.test :refer :all] - [cipher-analytical-machine.cipher-analyzers :refer :all])) - -(deftest count-characters-test - (testing "Count two dublicates and one uniq character from a string." - (is (= {\a 2 \b 1} - (count-characters "aba"))))) - -(deftest chi-squared-letter-statistic-test - (testing "If the frequence of A is 0.1, the text length is 100 and contains 20 times, then the chi-squared is 10." - (is (= 10.0 - (chi-squared-letter-statistic 20 0.1 100)))) - - (testing "If the frequence of A is 0.1, the text length is 10 and contains 5 times, then the chi-squared is 16.0." - (is (= 16.0 - (chi-squared-letter-statistic 5 0.1 10))))) - -(deftest chi-squared-statistic-test - (let [text "aaaaabbbbb"] - (testing "If the frequences of a and b is 0.1, the text length is 10 and contains them 5 times, then score is 32." - (is (= 32.0 - (chi-squared-statistic text {\a 0.1 \b 0.1})))))) - diff --git a/test/cipher_analytical_machine/caesar_test.clj b/test/cipher_analytical_machine/ciphers/caesar_test.clj similarity index 95% rename from test/cipher_analytical_machine/caesar_test.clj rename to test/cipher_analytical_machine/ciphers/caesar_test.clj index e1b773e..3611c86 100644 --- a/test/cipher_analytical_machine/caesar_test.clj +++ b/test/cipher_analytical_machine/ciphers/caesar_test.clj @@ -1,7 +1,7 @@ -(ns cipher-analytical-machine.caesar-test +(ns cipher-analytical-machine.ciphers.caesar-test (:require [clojure.test :refer :all] - [cipher-analytical-machine.caesar :refer :all] + [cipher-analytical-machine.ciphers.caesar :refer :all] )) (deftest calculate-char-index-test diff --git a/test/cipher_analytical_machine/symbol_factories_test.clj b/test/cipher_analytical_machine/symbols/factories_test.clj similarity index 94% rename from test/cipher_analytical_machine/symbol_factories_test.clj rename to test/cipher_analytical_machine/symbols/factories_test.clj index b00d054..0a85f02 100644 --- a/test/cipher_analytical_machine/symbol_factories_test.clj +++ b/test/cipher_analytical_machine/symbols/factories_test.clj @@ -1,7 +1,7 @@ -(ns cipher-analytical-machine.symbol-factories-test +(ns cipher-analytical-machine.symbols.factories-test (:require [clojure.test :refer :all] - [cipher-analytical-machine.symbol-factories :refer :all] + [cipher-analytical-machine.symbols.factories :refer :all] )) (deftest english-alphabet-factory-test diff --git a/test/cipher_analytical_machine/symbols/frequencies_test.clj b/test/cipher_analytical_machine/symbols/frequencies_test.clj new file mode 100644 index 0000000..1db5bd0 --- /dev/null +++ b/test/cipher_analytical_machine/symbols/frequencies_test.clj @@ -0,0 +1,11 @@ +(ns cipher-analytical-machine.symbols.frequencies-test + (:require + [clojure.test :refer :all] + [cipher-analytical-machine.symbols.frequencies :refer :all])) + +(deftest calculate-char-index-test + (let [symbol-map english-letter-frequencies] + (testing "The map must be converted to 'etaoinsrhldcumfpgwybvkxjqz'" + (is (= "etaoinsrhldcumfpgwybvkxjqz" + (map-to-string symbol-map)))))) +