Compare commits

..
13 Commits
Author SHA1 Message Date
KKlochko 6f15eedacd Add tests for the cipher-analyzers module.
continuous-integration/drone/push Build is passing
2023-09-18 22:01:02 +03:00
KKlochko 182d9894f7 Rename the cipher_analyzer module to cipher-analyzer. 2023-09-18 21:57:32 +03:00
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
11 changed files with 331 additions and 21 deletions
+3 -1
View File
@@ -4,7 +4,9 @@
:license {:name "LGPL"
:url "http://www.gnu.org/licenses/lgpl-3.0.txt"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/tools.cli "1.0.219"]]
[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)))
+24 -6
View File
@@ -2,6 +2,9 @@
(: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))
@@ -17,6 +20,9 @@
: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]
@@ -62,7 +68,17 @@
(contains? options :message-file))
(or (contains? options :encrypt)
(contains? options :decrypt)))
{:options options :arguments arguments}
(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)})))
@@ -83,16 +99,18 @@
(file/read-file (:message-file options))))
(defn actions
[options arguments]
(println (str options arguments))
[options arguments action-type]
(let [message (get-message options arguments)
key (Integer/parseInt (:key options))
symbols "abc"]
symbols (:symbols options)]
(println
(cond
(contains? options :encrypt)
(= action-type :cracking)
(caesar-analyzers/get-plaintext message symbols symbol-frequences/english-letter-frequences)
(= action-type :encrypt)
(caesar/encrypt-message message key symbols)
(contains? options :decrypt)
(= action-type :decrypt)
(caesar/decrypt-message message key symbols)))))
+2 -2
View File
@@ -5,8 +5,8 @@
(defn -main
[& args]
(let [{:keys [options arguments exit-message ok?]} (cli/validate-args args)]
(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))))
(cli/actions options arguments action-type))))
@@ -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)))
@@ -1,4 +1,4 @@
(ns cipher-analytical-machine.symbol_factories
(ns cipher-analytical-machine.symbol-factories
(:require [clojure.string :as cs])
(:gen-class))
@@ -32,3 +32,32 @@
[]
"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,25 @@
(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}))))))
@@ -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"))))
@@ -1,46 +1,75 @@
(ns cipher-analytical-machine.symbol-factories-test
(:require
[clojure.test :refer :all]
[cipher-analytical-machine.symbol_factories :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 same order"
(testing "It must have the same order."
(is (= "abcdefghijklmnopqrstuvwxyz" symbols)))
(testing "It must have 26 letters"
(testing "It must have 26 letters."
(is (= 26 (count symbols))))))
(deftest english-alphabet-factory-test
(let [symbols (english-alphabet-factory)]
(testing "It must have same order"
(testing "It must have the same order."
(is (= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" symbols)))
(testing "It must have 2*26 letters"
(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 same order"
(testing "It must have the same order."
(is (= "абвгґдеєжзиіїйклмнопрстуфхцчшщьюя" symbols)))
(testing "It must have 33 letters"
(testing "It must have 33 letters."
(is (= 33 (count symbols))))))
(deftest ukrainian-alphabet-factory-test
(let [symbols (ukrainian-alphabet-factory)]
(testing "It must have same order"
(testing "It must have the same order."
(is (= "абвгґдеєжзиіїйклмнопрстуфхцчшщьюяАБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ" symbols)))
(testing "It must have 2*33 letters"
(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 same order"
(testing "It must have the same order."
(is (= "0123456789" symbols)))
(testing "It must have 10 digits"
(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))))))