Merge branch 'dev' into 'main'
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Ready to use the implementation of the Caesar cipher See merge request KKlochko/cipher-analytical-machine!1main
commit
4d5726f363
@ -0,0 +1,10 @@
|
||||
image: clojure:temurin-11-lein-2.10.0-alpine
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
||||
unit tests:
|
||||
stage: test
|
||||
script:
|
||||
- lein test
|
||||
|
@ -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)))
|
||||
|
@ -1,4 +1,4 @@
|
||||
(ns cipher-analytical-machine.language-analyzer
|
||||
(ns cipher-analytical-machine.analyzers.language
|
||||
(:import [org.apache.tika.language LanguageIdentifier])
|
||||
(:gen-class))
|
||||
|
@ -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)))
|
||||
|
@ -1,4 +1,4 @@
|
||||
(ns cipher-analytical-machine.caesar
|
||||
(ns cipher-analytical-machine.ciphers.caesar
|
||||
(:require [clojure.string :as cs])
|
||||
(:gen-class))
|
||||
|
@ -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))))
|
||||
|
@ -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))))
|
||||
|
@ -1,4 +1,4 @@
|
||||
(ns cipher-analytical-machine.file
|
||||
(ns cipher-analytical-machine.cli.file
|
||||
(:require [clojure.java.io :as io])
|
||||
(:gen-class))
|
||||
|
@ -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"]])
|
||||
|
@ -1,4 +1,4 @@
|
||||
(ns cipher-analytical-machine.symbol-factories
|
||||
(ns cipher-analytical-machine.symbols.factories
|
||||
(:require [clojure.string :as cs])
|
||||
(:gen-class))
|
||||
|
@ -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<Character, Integer> encryptedLetterCounts = new HashMap<>();
|
||||
|
||||
for (char c : ciphertext.toLowerCase().toCharArray()) {
|
||||
encryptedLetterCounts.put(c, encryptedLetterCounts.getOrDefault(c, 0) + 1);
|
||||
}
|
||||
|
||||
// Знаходимо букву, яка зустрічається найчастіше
|
||||
char mostFrequentEncryptedLetter = ' ';
|
||||
int mostFrequentEncryptedLetterCount = 0;
|
||||
|
||||
for (Entry<Character, Integer> 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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package cipher_analytical_machine.analyzers.caesar;
|
||||
|
||||
public interface IsNonsense {
|
||||
boolean isNonsense(String message);
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package cipher_analytical_machine.ciphers.caesar;
|
||||
|
||||
public interface Decrypted {
|
||||
String decrypt(String message, int key, String symbols);
|
||||
}
|
||||
|
@ -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
|
@ -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,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
|
@ -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
|
@ -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))))))
|
||||
|
Loading…
Reference in new issue