Compare commits

...
16 Commits
Author SHA1 Message Date
KKlochko e3954804c8 Add the cracking option.
continuous-integration/drone/push Build is passing
2023-09-17 21:45:09 +03:00
KKlochko b1f3bb8585 Rename the symbol_frequences module to symbol-frequences. 2023-09-17 21:43:09 +03:00
KKlochko a6f3ca160f Update the nonsense for the is-nonsense? test.
continuous-integration/drone/push Build is passing
2023-09-16 22:16:46 +03:00
KKlochko 001b2a8b65 Rename the language_analyzer module to language-analyzer. 2023-09-16 22:16:08 +03:00
KKlochko e8a023fe61 Add the symbol option and its default. 2023-09-16 22:08:12 +03:00
KKlochko 6d282e5ad4 Add the action-type to distinguish the actions. 2023-09-16 22:05:53 +03:00
KKlochko 5ca5765e01 Add symbol factories for punctuation, space, defaults symbols. 2023-09-16 22:02:18 +03:00
KKlochko a7ccad8e1e Add the caesar-analyzers to get the plaintext from a ciphertext.
continuous-integration/drone/push Build is failing
2023-09-15 21:30:37 +03:00
KKlochko 79e2957402 Add Chi-squared Statistic function to analyze texts.
continuous-integration/drone/push Build is failing
2023-09-14 21:09:28 +03:00
KKlochko 4750242a1d Add the frequences of symbols as defaults. 2023-09-14 20:50:28 +03:00
KKlochko 174abd8072 Add a simple language analyzers.
continuous-integration/drone/push Build is failing
2023-09-13 21:37:53 +03:00
KKlochko 617dd34a23 Add an option to read a message from a file.
continuous-integration/drone/push Build is passing
2023-09-12 21:55:48 +03:00
KKlochko 63f55bbc50 Add the CI/CD configuration. 2023-09-11 22:23:17 +03:00
KKlochko 4b50b9289b Add tests for factories. 2023-09-11 22:20:27 +03:00
KKlochko 0b368c144e Add factories for symbols. 2023-09-11 22:09:23 +03:00
KKlochko 453b90d537 Add a simple cli interface. 2023-09-10 20:53:30 +03:00
12 changed files with 500 additions and 3 deletions
+10
View File
@@ -0,0 +1,10 @@
kind: pipeline
type: docker
name: default
steps:
- name: test
image: clojure:temurin-11-lein-2.10.0-alpine
commands:
- lein test
+4 -1
View File
@@ -3,7 +3,10 @@
:url "https://gitlab.com/KKlochko/cipher-analytical-machine"
:license {:name "LGPL"
:url "http://www.gnu.org/licenses/lgpl-3.0.txt"}
:dependencies [[org.clojure/clojure "1.11.1"]]
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/tools.cli "1.0.219"]
[org.apache.tika/tika-core "1.28.5"]
[org.apache.tika/tika-langdetect "1.28.5"]]
:main ^:skip-aot cipher-analytical-machine.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all
@@ -0,0 +1,34 @@
(ns cipher-analytical-machine.caesar-analyzers
(:require [cipher-analytical-machine.caesar :as caesar]
[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-key
"To find the key with frequencies of letters."
[ciphertext symbols letter-frequences]
(->> (get-all-texts ciphertext symbols)
(map (fn [[key text]]
[key (ca/chi-squared-statistic text letter-frequences)]))
(reduce
(fn [[key value] [new-key new-value]]
(if (> value new-value) [new-key new-value]
[key value]))
[0 Double/MAX_VALUE])
(first)))
(defn get-plaintext
"Return the plaintext from a ciphertext."
[ciphertext symbols letter-frequences]
(caesar/decrypt-message ciphertext
(get-key ciphertext symbols letter-frequences)
symbols))
@@ -0,0 +1,38 @@
(ns cipher-analytical-machine.cipher_analyzers
(:require [clojure.string :as cs])
(:gen-class))
(defn count-characters
"Count the letters and return map."
[text]
(reduce
(fn [acc val]
(assoc acc val
(inc (get acc val 0))))
{} text))
(defn chi-squared-letter-statistic
"To calculate the frequency distribution of a letter."
[letter-count letter-frequence length-text]
(let [letter-frequence (* length-text letter-frequence)]
(/ (Math/pow
(- letter-count letter-frequence) 2)
letter-frequence)))
(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]
(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)
(chi-squared-letter-statistic count
(get letter-frequences char)
length-text)
0)))
0
letter-counts)))
+116
View File
@@ -0,0 +1,116 @@
(ns cipher-analytical-machine.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]
[clojure.string :as cs]
[clojure.tools.cli :refer [parse-opts]])
(:gen-class))
(def cipher-options
#{"Caesar"})
(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")]
["-C" "--cracking" "Cracking the encrypted message."]
["-h" "--help"]])
(defn usage [options-summary]
(->> ["This is cipher-analytical-machine. It can help you to learn how ciphers works."
""
"Usage: cipher-analytical-machine [options]"
""
"Options:"
options-summary
""]
(cs/join \newline)))
(defn error-msg [errors]
(str "The following errors occurred while parsing your command:\n\n"
(cs/join \newline errors)))
(defn map-has-keys?
"Return true if all keys in the map."
[map keys]
(every? #(contains? map %) keys))
(defn validate-args
"Validate command line arguments. Either return a map indicating the program
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)]
(cond
(:help options)
{:exit-message (usage summary) :ok? true}
errors
{:exit-message (error-msg errors)}
(map-has-keys? options [:encrypt, :decrypt])
{:exit-message (error-msg ["You can't use enctypt and decrypt mode at the same time!!!"])}
(map-has-keys? options [:message, :message-file])
{:exit-message (error-msg ["You can't use message and message-file options at the same time!!!"])}
(and (map-has-keys? options [:cipher, :key])
(or (contains? options :message)
(contains? options :message-file))
(or (contains? options :encrypt)
(contains? options :decrypt)))
(cond
(contains? options :encrypt)
{:options options :arguments arguments :action-type :encrypt}
(contains? options :decrypt)
{:options options :arguments arguments :action-type :decrypt})
(and (map-has-keys? options [:cipher, :cracking])
(or (contains? options :message)
(contains? options :message-file)))
{:options options :arguments arguments :action-type :cracking}
:else
{:exit-message (usage summary)})))
(defn exit
[exit-message status]
(println exit-message)
(System/exit (if status 0 1)))
(defn get-message
"Return the message"
[options arguments]
(cond
(contains? options :message)
(:message options)
(contains? options :message-file)
(file/read-file (:message-file options))))
(defn actions
[options arguments action-type]
(let [message (get-message options arguments)
key (Integer/parseInt (:key options))
symbols (:symbols options)]
(println
(cond
(= action-type :cracking)
(caesar-analyzers/get-plaintext message symbols symbol-frequences/english-letter-frequences)
(= action-type :encrypt)
(caesar/encrypt-message message key symbols)
(= action-type :decrypt)
(caesar/decrypt-message message key symbols)))))
+7 -2
View File
@@ -1,7 +1,12 @@
(ns cipher-analytical-machine.core
(:require
[cipher-analytical-machine.cli :as cli])
(:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
(let [{:keys [options arguments action-type exit-message ok?]} (cli/validate-args args)]
(if exit-message
(cli/exit exit-message ok?)
(cli/actions options arguments action-type))))
+18
View File
@@ -0,0 +1,18 @@
(ns cipher-analytical-machine.file
(:require [clojure.java.io :as io])
(:gen-class))
(defn is-file-exists?
"Checks that a file is exists"
[file-path]
(-> (io/file file-path)
(.exists)))
(defn is-file-not-exists?
"Checks that a file is not exists"
[file-path]
(not (is-file-exists? file-path)))
(defn read-file [file-path]
(slurp file-path))
@@ -0,0 +1,21 @@
(ns cipher-analytical-machine.language-analyzer
(:import [org.apache.tika.language LanguageIdentifier])
(:gen-class))
(defn detect-language
"Return a identified language."
[text]
(-> (new LanguageIdentifier text)
.getLanguage))
(defn is-language?
"Check if the text written a language."
[text language-id]
(= (detect-language text)
language-id))
(defn is-nonsense?
"Check if the text written a language is not a language."
[text language-id]
(not (is-language? text language-id)))
@@ -0,0 +1,63 @@
(ns cipher-analytical-machine.symbol-factories
(:require [clojure.string :as cs])
(:gen-class))
(defn lower-english-alphabet-factory
"Return the English alphabet with only lowercase letters"
[]
(reduce
(fn [acc val] (str acc (char val)))
""
(range (int \a) (inc (int \z)))))
(defn english-alphabet-factory
"Return the English alphabet with lower and upper case letters"
[]
(let [letters (lower-english-alphabet-factory)]
(str letters (cs/upper-case letters))))
(defn lower-ukrainian-alphabet-factory
"Return the Ukrainian alphabet with only lowercase letters"
[]
"абвгґдеєжзиіїйклмнопрстуфхцчшщьюя")
(defn ukrainian-alphabet-factory
"Return the Ukrainian alphabet with lower and upper case letters"
[]
(let [letters (lower-ukrainian-alphabet-factory)]
(str letters (cs/upper-case letters))))
(defn digit-set-factory
"Return digits"
[]
"0123456789")
(defn punctuation-symbol-factory
"Return punctuation symbols"
[]
".,:;-!?")
(defn space-symbol-factory
"Return punctuation symbols"
[]
" \t\n")
(defn default-symbol-factory
"Return a default string of symbols for a language"
[language-code]
(cond
(= language-code "en")
(str (english-alphabet-factory)
(punctuation-symbol-factory)
(digit-set-factory)
\ )
(= language-code "uk")
(str (ukrainian-alphabet-factory)
(punctuation-symbol-factory)
(digit-set-factory)
\ )
:else
(default-symbol-factory "en")))
@@ -0,0 +1,69 @@
(ns cipher-analytical-machine.symbol-frequences
(:gen-class))
(def english-letter-frequences {
\a 0.0804
\b 0.0154
\c 0.0306
\d 0.0399
\e 0.1251
\f 0.0230
\g 0.0196
\h 0.0549
\i 0.0726
\j 0.0016
\k 0.0067
\l 0.0414
\m 0.0253
\n 0.0709
\o 0.0760
\p 0.0200
\q 0.0011
\r 0.0612
\s 0.0654
\t 0.0925
\u 0.0271
\v 0.0099
\w 0.0192
\x 0.0019
\y 0.0173
\z 0.0009
})
(def ukrainian-letter-frequences {
\а 0.064
\б 0.013
0.046
\г 0.013
0.0001
0.027
\е 0.042
0.005
0.007
0.020
0.055
\і 0.044
0.010
0.009
0.033
0.027
0.029
0.068
\о 0.086
\п 0.025
\р 0.043
\с 0.037
0.045
\у 0.027
0.003
\х 0.011
0.010
0.011
0.005
0.004
0.016
0.008
0.019
\ 0.138
})
@@ -0,0 +1,45 @@
(ns cipher-analytical-machine.language-analyzer-test
(:require
[clojure.test :refer :all]
[cipher-analytical-machine.language-analyzer :refer :all]
))
(deftest detect-language-test
(testing "Checking the English text"
(is (= "en" (detect-language "This is a sentence that uses English."))))
(testing "Checking the Ukrainian text"
(is (= "uk" (detect-language "Текст, що написаний українською."))))
(testing "Checking a gibberish that uses English letters"
(is (= "lt" (detect-language "dfgjldfjgdfl gjdfg idfjig hdgesr khs e."))))
(testing "Checking a gibberish that uses Ukrainian letters"
(is (= "uk" (detect-language "іаврпшшцді врадів аргів аріл варї йцґ.")))))
(deftest is-language?-test
(testing "Checking the English text"
(is (is-language? "This is a sentence that uses English." "en")))
(testing "Checking the Ukrainian text"
(is (is-language? "Текст, що написаний українською." "uk")))
(testing "Checking a gibberish that uses English letters"
(is (is-language? "dfgjldfjgdfl gjdfg idfjig hdgesr khs e." "lt")))
(testing "Checking a gibberish that uses Ukrainian letters"
(is (is-language? "іаврпшшцді врадів аргів аріл варї йцґ." "uk"))))
(deftest is-nonsense?-test
(testing "Checking the English text"
(is (not (is-nonsense? "This is a sentence that uses English." "en"))))
(testing "Checking the Ukrainian text"
(is (not (is-nonsense? "Текст, що написаний українською." "uk"))))
(testing "Checking a gibberish that uses English letters"
(is (is-nonsense? "dfgjldfjgdfl gjdfg idfjig hdgesr khs e." "en")))
(testing "Checking a gibberish that uses Ukrainian letters"
(is (is-nonsense? "іаврпшшцді івафі лоінїмч йцувіа івапґ." "uk"))))
@@ -0,0 +1,75 @@
(ns cipher-analytical-machine.symbol-factories-test
(:require
[clojure.test :refer :all]
[cipher-analytical-machine.symbol-factories :refer :all]
))
(deftest lower-english-alphabet-factory-test
(let [symbols (lower-english-alphabet-factory)]
(testing "It must have the same order."
(is (= "abcdefghijklmnopqrstuvwxyz" symbols)))
(testing "It must have 26 letters."
(is (= 26 (count symbols))))))
(deftest english-alphabet-factory-test
(let [symbols (english-alphabet-factory)]
(testing "It must have the same order."
(is (= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" symbols)))
(testing "It must have 2*26 letters."
(is (= (* 2 26) (count symbols))))))
(deftest lower-ukrainian-alphabet-factory-test
(let [symbols (lower-ukrainian-alphabet-factory)]
(testing "It must have the same order."
(is (= "абвгґдеєжзиіїйклмнопрстуфхцчшщьюя" symbols)))
(testing "It must have 33 letters."
(is (= 33 (count symbols))))))
(deftest ukrainian-alphabet-factory-test
(let [symbols (ukrainian-alphabet-factory)]
(testing "It must have the same order."
(is (= "абвгґдеєжзиіїйклмнопрстуфхцчшщьюяАБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ" symbols)))
(testing "It must have 2*33 letters."
(is (= (* 2 33) (count symbols))))))
(deftest digit-set-factory-test
(let [symbols (digit-set-factory)]
(testing "It must have the same order."
(is (= "0123456789" symbols)))
(testing "It must have 10 digits."
(is (= 10 (count symbols))))))
(deftest punctuation-symbol-factory-test
(let [symbols (punctuation-symbol-factory)]
(testing "It must have the same order."
(is (= ".,:;-!?" symbols)))
(testing "It must have 7 symbols."
(is (= 7 (count symbols))))))
(deftest space-symbol-factory-test
(let [symbols (space-symbol-factory)]
(testing "It must have the same order."
(is (= " \t\n" symbols)))
(testing "It must have 3 symbols."
(is (= 3 (count symbols))))))
(deftest default-symbol-factory-test
(testing "If language code is 'en', then it must have symbols."
(let [symbols (default-symbol-factory "en")]
(is (= 70 (count symbols)))))
(testing "If language code is 'uk', then it must have symbols."
(let [symbols (default-symbol-factory "uk")]
(is (= 84 (count symbols)))))
(testing "If language code is unidentified, then it must have the 'en'."
(let [symbols (default-symbol-factory "Maybe a code")]
(is (= 70 (count symbols))))))