Compare commits

..
3 Commits
Author SHA1 Message Date
KKlochko 7f6f7c9f72 Add a simple cli interface. 2023-09-10 20:53:30 +03:00
KKlochko a8368d09e6 Add a implementation of Caesar cipher. 2023-09-09 21:26:38 +03:00
KKlochko ade86e319f Add symbol fixtures. 2023-09-09 21:08:35 +03:00
14 changed files with 31 additions and 445 deletions
-10
View File
@@ -1,10 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
- name: test
image: clojure:temurin-11-lein-2.10.0-alpine
commands:
- lein test
+1 -3
View File
@@ -4,9 +4,7 @@
: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.apache.tika/tika-core "1.28.5"]
[org.apache.tika/tika-langdetect "1.28.5"]]
[org.clojure/tools.cli "1.0.219"]]
:main ^:skip-aot cipher-analytical-machine.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all
@@ -1,34 +0,0 @@
(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))
@@ -1,38 +0,0 @@
(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)))
+8 -42
View File
@@ -1,10 +1,6 @@
(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))
@@ -14,15 +10,11 @@
(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]
@@ -60,25 +52,11 @@
(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))
(contains? options :message)
(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}
{:options options :arguments arguments}
:else
{:exit-message (usage summary)})))
@@ -88,29 +66,17 @@
(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)
[options arguments]
(println (str options arguments))
(let [message (:message options)
key (Integer/parseInt (:key options))
symbols (:symbols options)]
symbols "abc"]
(println
(cond
(= action-type :cracking)
(caesar-analyzers/get-plaintext message symbols symbol-frequences/english-letter-frequences)
(= action-type :encrypt)
(contains? options :encrypt)
(caesar/encrypt-message message key symbols)
(= action-type :decrypt)
(contains? options :decrypt)
(caesar/decrypt-message message key symbols)))))
+2 -2
View File
@@ -5,8 +5,8 @@
(defn -main
[& args]
(let [{:keys [options arguments action-type exit-message ok?]} (cli/validate-args args)]
(let [{:keys [options arguments exit-message ok?]} (cli/validate-args args)]
(if exit-message
(cli/exit exit-message ok?)
(cli/actions options arguments action-type))))
(cli/actions options arguments))))
-18
View File
@@ -1,18 +0,0 @@
(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))
@@ -1,21 +0,0 @@
(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,63 +0,0 @@
(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,20 @@
(ns cipher-analytical-machine.symbol_fixtures
(:require [clojure.string :as cs])
(:gen-class))
(defn get-lower-ukrainian-alphabet
"Return the ukrainian alphabet for the symbols string"
[]
"абвгґдеєжзиіїйклмнопрстуфхцчшщьюя")
(defn get-ukrainian-alphabet
"Return the ukrainian alphabet for the symbols string"
[]
(let [letters (get-lower-ukrainian-alphabet)]
(str letters (cs/upper-case letters))))
(defn get-digits
"Return digits for the symbols string"
[]
"0123456789")
@@ -1,69 +0,0 @@
(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
})
@@ -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}))))))
@@ -1,45 +0,0 @@
(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,75 +0,0 @@
(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))))))