diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e760f82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +xor-vigenere diff --git a/README.md b/README.md new file mode 100644 index 0000000..12c3e2d --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# xor-vigenere + +xor-encoded vigenere cypher decoder for ru-ru alphabet. +Decodes uppercase text without white spaces and Ë letter. + +## Build + +Requires [go1.19](https://go.dev/dl/) + +``` +$ go build . +``` + +## Usage + +Specify input file and wait for result. + +``` +$ ./xor-vigenere -i example/cipher.txt -r 2 +Cypher text file: example/cipher.txt +Parallel workers: 12 (override with -w flag) +Trying to predict secret key length ... 8 (override with -l flag) +Cypher text sample size to analyze: 2000 (override with -s flag) +Number of letters in permutations: 4 [ОЕАИ] (override with -p flag) +Number of results to show: 2 (override with -r flag) +Analyzing 100% [##################################################] (65536/65536, 36315 keys/s) + +Key=АБЫРВАЛГ Sample:ГДЕЖЕВЕСЬМИРВДЕНЬМОЕГОРОЖДЕНИЯГДЕЭЛЕКТРИЧЕСКИЕФО Div:0.145333 +Key=АЙЫРВАЛГ Sample:ГМЕЖЕВЕСЬДИРВДЕНЬДОЕГОРОЖМЕНИЯГДЕХЛЕКТРИЧНСКИЕФО Div:0.166576 +Execution duration: 1.805705397s +``` + +`АБЫРВАЛГ` is a valid secret key on top. + +To receive better (but slower) results use: +- bigger sample size for analyzing cypher text (`-s 0` for analyzing whole text) +- look through more possible keys (`-r` flag) +- use more frequently used letters to create more permutations (slows **really** hard, `-p` flag) diff --git a/coder.go b/coder.go new file mode 100644 index 0000000..c41b7d9 --- /dev/null +++ b/coder.go @@ -0,0 +1,94 @@ +package main + +import "math" + +var ( + alphabet = []rune("АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ") + alphabetLen = len(alphabet) + + alphabetFreq = []float64{ // took from https://dpva.ru/Guide/GuideUnitsAlphabets/Alphabets/FrequencyRuLetters/ (without Ë) + 0.080159, // А + 0.015942, // Б + 0.045400, // ... + 0.016957, + 0.029801, + 0.084523, + 0.009398, + 0.016492, + 0.073559, + 0.012090, + 0.034952, + 0.044013, + 0.032080, + 0.066997, + 0.109714, + 0.028117, + 0.047352, + 0.054698, + 0.062606, + 0.026225, + 0.002645, + 0.009710, + 0.004829, + 0.014453, + 0.007283, + 0.003608, + 0.000367, + 0.018999, + 0.017392, + 0.003188, + 0.006377, + 0.020074, + } + + mostFrequest = []rune("ОЕАИНТСРВЛК") +) + +type Coder struct { + ToCode map[rune]int + ToRune map[int]rune + RuFreq map[rune]float64 +} + +func NewCoder() Coder { + coder := Coder{ + ToCode: make(map[rune]int, alphabetLen), + ToRune: make(map[int]rune, alphabetLen), + RuFreq: make(map[rune]float64, alphabetLen), + } + + for i, r := range alphabet { + coder.ToCode[r] = i + coder.ToRune[i] = r + coder.RuFreq[r] = alphabetFreq[i] + } + + return coder +} + +// Code with xor encoding. +func (c Coder) Code(text, key []rune) []rune { + res := make([]rune, 0, len(text)) + for i, r := range text { + keyIndex := i % len(key) + res = append(res, rune(c.ToRune[c.ToCode[r]^c.ToCode[key[keyIndex]]])) + } + return res +} + +func (c Coder) RuneFrequency(text, key []rune) map[rune]int { + res := make(map[rune]int, alphabetLen) + for i, r := range text { + keyIndex := i % len(key) + res[rune(c.ToRune[c.ToCode[r]^c.ToCode[key[keyIndex]]])] += 1 + } + return res +} + +func (c Coder) AlphabetDivergence(analysis map[rune]int, textLen float64) float64 { + var result float64 + for r, freq := range analysis { + result += math.Abs(float64(freq)/textLen - c.RuFreq[r]) + } + return result +} diff --git a/example/cipher.txt b/example/cipher.txt new file mode 100644 index 0000000..9999c42 --- /dev/null +++ b/example/cipher.txt @@ -0,0 +1 @@ +ГЕЮЦЗВОТЬНУААДООЬНХХБОЫНЖЕЮЭКЯИЗЕЬРХИТЫЛЧДКЪКЕЯННБЛШООЪЙВЪРОЖИЖЖБПЬРМКЕЫКБЧШПЕЩОИЦЮУМТЧПАНАЮРРОДАМАЮРЛХЗЕИФХТВРЖКДЛЮУИЖНВЪЮДМНЛУИПЙЭВСЙЗЕГДВКВОУСУЫЕПАЪСАМНШКЖОИЕЖЦЮЛДЕУОВУЬКГЛЖТУЫЬПАЙЖРМХХЦОЖГРЙСШЕДРЦАДЙЮРМОСЕКУЯТОВЗЕУЩЯМЛЖНЧЭКТМЕЗТКПЛЛЛВЗНСЛЩГКДЛЕЕМЮЮУТЛООГУВУЯЖЖНТЭЭВЕЗРЗБЪЛРАФТТБЦЖКЯДНГСЮСЗНЖГЯГЪГТАЖЖРБЬТЗЧЩНЗБЦХУЕЩМУУУЯЗРЙШЕЬРХИТЫЛЧДКЪКЕЯННБЛШАСЕУОЛЫТЗРЪСАФЩГЗЗПООНШЮТОПЖТБЧБЙАПНСУЦРЭЖГДНЭСШПЕЗГТПШАВФОТТЭЧРБАМЛНЪЩВМВЫЖМЮСРИВЕЖТЙЩРЙИЩТНДШЭВПЕИЯФЦРЯКЫГНДЩЮЕМЕЕНПФЫЩВОСТСХБРНГЙКБМРЬТЪЬПБРМОЫЗЛГБЮВРРЕМИЦЮБИИВНСУЛЮАМРЕЕПЯЭКТЧПАДШШНЕЩТКБДЧВМОСИКПХЙЬПЫЕСЯХОЬФОЛТСШХПЫЛППЯЭЭВУСОСИТЩРЛЕАДЙБЭОЖСОСЭХУТЙЖНМХЭМОЬЖНЭЧХРКЕЛМДЦЭМЕИЛПДЙБИАФМРПГГЗЩОМОСЕЬМЧБЖПСУУЙАЪЛЛЮЫЕПЕЕТУЗЯРЛТОБЕЕЗТТАЬЧЕКЗФЪЕЫЗВДЫЪСШОУКЙЩХЖЬЗШТПЭХЙЮПЛМЪЦХАИПЛМЧЮЫЩМГПЕРДЖВМГОИЛХУМКЫНМДКЮРЕЖВОКЗЭЩХЗШРБЪЮРАОПМЪФЮБРОВЕМАТУНОАУМЮГДЕАЛЖДЦХЙЬМЬНБЧТЩПГСЬОХФАЕЫЭМЛУАВЗЙЖДДЦЭМГЕТПЙЛВСПЕУЕЧЮЯРУГДАЛИБКТЧРЕЖЯЭЩМГЫПСХВВМГБДДЦМТОНЗЕМУПАРЛФАЖЫТВШОДДПЛЮАЬОЗОЛЙЮТПЫНЧТЩБРВООНПКЪВЗЛИДДЧМЭНАРКЙМЦЗЛЛЖМГЫЬНРГБЫЛЦГРЬШОАРКЪВЗЛИАБЦЭВНГЙОКЫХАНЛЛЧПСРЭСЧМООЛРАИАГПБЛРЖНЕЖСГХХНЛЛСЬДКАВЗЙНДБЧШАТЕУАЮЫЪСШОУКБФХЙАИЖЯЙЩРПОЙОАЦХЪПУАГСЭОЫЗБЖРЛБКХЛЧЛТЖДФАКСОИАМЫЪМРЩНЧЛУШИОЬЖРВХЩНОУЖВДРШЙАЙМЕЦСХДАЫЙИИЪЫЗСБПЕУЦГЙСФМОМЫИКМАЛЦБЧТБРШЗИУЮЯЙЕАНОУЩЮЖКГЬРДГШРЕАЯНПЦХНОЪСИВЫОЕАИНВПЛШЙЯЙНЗАИЦЖЕЖООЙШЫЭДФОАУИЗСИЪЙРГЬЬЗТЖРВЩУЕУЯДНДЛХЗЗРИНЙЦЙЮЯТЛВААЫБЖЕАГЛБКСЗЛАГДПЦЭМЙЙЖДЭЖВМЖОЙОЩЧРТУАШБЛУЧВИИУАКУЭВЛГХАФПХЙЬПЫЕСЫШВКШЫЕСХЪЖЕАНБЪРЮАОЩБЧДЧБЗГЕЗНЮЦРСТЫЖНМЮЬНРГЖМДЩЪВБГОЕУСЮОНОМРПЙШУНШИАРЗАСМФОАЮЪРГОЬЙАКЮВРРГЗЦБЙШМНЛМОЛРЮПИАГСЭЫЪСШОУСЛХЬСКЫЖСКИБРОФЪЕНИЧВМЕЖЙРФШПОВДАУЮЬКЗМГПБЬГЧИПНСУЫЫВШГУОЛХУМРАШЙХРРИОЖЛЗБФХЙААЯСУУТМСДГСЙЪЮААЗАРБЭФВНГОДПСВМРМГКБФЫКУНСАЛФЮООИИИУЫЪНОЗНГКУЯМЖЛИУИЙХЗЩОВАМХЗИУФБЗЮРГПЕОЛЗСИЪЦЛЛЙОМШЫЭНШИНБЖВККОСКТУТБЛЛДАФИЬЗНФМОЖЮЫЗНОИОМЫНРИБЖТЛЮСЩЛЕОАОУБВЛЕУАЖЧРЪИЪСЫНФЮХЕЫЙОНЯХОЬФОАКИЪКЧЛСИМНВГЕАИАЕХЭПИЩЗДДСРГРФАОЕЫФТУИЛМЙКЫМВЛПИГМХТАФБЫОУБВЛКГБПМЪЗПЕУЯЕХЗПУХМОСНШЬБОИЛБЯЮПНРГСДШЮЖНФБДДЦМОООАОСХЦЖЕЖЛЯЕЮЪВБЫЬББЪЮХКЛМРЙЮЕВЛЛТСТОШОФАГКПЦЮОИЪМРПКМГОВМОГЙЮТИЩЯТЪЙЛАСОМРЙЦПЙАЙФЕСЫБНРЕТИКДФККГПГПРЮУОЗБСДЪРРЮУЙАНУЫЩЙЙТЕОЮЫВБЛВОЦСРУДЕВНЪЧУМЛЕТОНЯРЛВЛПБПШЧЖОЫНВЭДЧВЭЩЛКБФЫКПЕИББЦЮХКГЙАЛФАКЕЮГЛБЫЯМЛКГНПМЪККЛЙСОЫВЮЛЕЕИУЗБЭКЛЙРТСЮЛСЖЬЛПДЯТИЪИОМУЫУЯБГКТГХТСБНМТСАЗСАРЯУЮСЗПЕТКПРМИУБГПДРМБОЙНРЙРЧВДШЫЕМЦЛОГЕИОРХЬЕАИНВПЛШЙЯФСЕАЮЯМПФСЬЛЫЯЗЛЧФТПЭХРЫИНЛПКЮОЗЛАОГХАКЛФЬТДЪХНОДЬТЭСРНЕАЯЧУХЦЗТРЗЕКЫХЪЬКГБПМЪВТРЕЯЗЮЩГОИРПСУЭЭЛЛАОГХАКЛЛВААЫФСМЛЬЧУХППЕПНВДЛПЬЕВВУЕЙЮМНЛИЕЦУЫВСЧПОДТСЗЛАГДПЦЭМЙФНХГЫВКЛЫРКБЧШТУЗЬНЪЮЙЗКГЛСУЫЫАСЗГТСУТВТЧТЯГЬАВЧБЛНПЬАВЧБЛБЪРШИАБДРБМЪКДЕБОКЗЭМКЫГСЙЩЛЗСЕБЕСГХПНЕООСЧРЙЬЖШЕОИЫЮСШВАААСЩЛЩНЖДФАЗЛОТТМАЩАОЕВЩДЦШИАБЛХОЛШЕНЛЙОГХВТАЙИЕМУПГЕАИАЕХЭПОВРББЪЛПЕМГМДМРЙОЪЯЭУХУМНОПОЗЮВГЫЩЯЗБШЮАОЫЛЛЮУЧВВЕМИКЯХОЬФОЛТСШХДОПЬЮЦЫСКГФВАЮЫММЮГЛБЙХАЫЖШРМИЫКЗЛМТДМЭМГЕЙОСУФМРЛМОКЕССЙЩЖСЭЯХОЬФОЛТСШХЧЩНЭУЫЪТАЪГВЙНРУДОИАКЫППИЬЖГПЦХНОЖЛМБЕСВБЛЛСОИУВНЖНВДЛВЗЛЛАОКХТМЙДННЮЩЗРОЙФЕНЙЮМНЛМРПЩШПИАГСЭЯХОЬФОЛТСШХЗЛБЛБЯХЙФАГКПЦЮОПЕОЮФЫЫЗГЕМОГЮАРЕАБРТСРЧИЪСРПШЮООАБИКЙЛОИАГЯГЛХЪЬЩШЛДСРТСЩБОМЮЯТИЖЛМБРРЗЙКННБМРЙАКГББЪРГОЬЙАУАЭВМЕФКПЩЭЗВЩЛРБТБСРЕБОЙКЪТИЙЛВСХВБОЙНРЙРФЗМЧЬНКИЪКЧЗШВРЮФМСБННБРМПОДННЙЧРЗМЪНЗМЫТВЙЪЬКПШЮЙЕЬЛЛБЖВКМГЙАОРПОИКГББЩЮЕВОИАРЩЮКНЕУМБРМПЫОДРБМЪКНЛФИРЙЮАЫКЖЛДЦЭЩЙДНТПРЮИИДЖРДСАЗСЩЛЛБКМАОЩФТПЪЬПЕКУОРЗСТОЪЯБТЪЭКЛПЖМЭДЭЙУБЛЧЙХСТАЩЛЛРДЪММЖЖОМУФМКЩНРГЮФЮКЛЙДДРРЬТЪЩЕЖЯШРТЛЙАЮЫАРИЪСКБЩСМЛЧОИЧИТЩПГЫУУЮЩЙЕБГРРЙТМАЕОАОЛШЗДОСВЕЮАЗВЖЭИГКХЧБЛВУВХБРИЩФТПЩЛБРЛЕДБЦШПФОУШБРСТОЪЯОУЛХЕААЧЕКЗФЪЕЫЬУГЫБАОЪЯМПТУМДМОАЯСЮПЕЬООСЫБИАДГЛБЩХУЬЯИАЛХЭХИБМОГКХОДЙНРБЧЯТОПНЛЗЫЫМНЗОЕДВХЯТГЦКБФХЙЕБЗАИЙХСМГИЬМХЯМПЫНСЙРРГАКГНТЦХРБЛВОЦСРМТЙЖТЙРПКВРСЕСФЮРСЕИББЖВКМГЙАОРПОИКНЛЭГХРЕКЖЛДМШРЬЪЬНДФАКДОССЮЭШАОЩМОКЮУХААМРЮЧЮРАБЛНТЛГИОВТНЮРЮПУЙНТЙФАЗВЕТХПЯЭМЯЩЖБДЯАСГГЦВЪФШЪУЩНЖДХЗЗНЧЦОСХИКЕГЬВЪФШУААВААХЗИЕЙГЛДЛМЭНБЛИПЦРТАМНЧБЛЮААЖОАЮИХЧААГВПЙЮГЭЩНМРРГХАОПЫЙЙЮЙКЕБАКУГОЕЖЬВЕХЪРОЫТКПТЪААЫСИСЮТЖЕЖЯМПЮУМРЕЕДДЦШЭКЕАДБЬРМКЖГМЙЩШУЕАГТЮЭЪКММГНБЩХУОЗПЕУЮЫЮНЛЬЕВУЯЗТЪЙАЮЙМОАЦСОЦЙЮБОЙНРЙРФЗМЧЬНКИЪКЧПЖЛЙСРРНЕМРПЭХАЫЙГЯСАСИУЙПАРРХЯТЕФТПЧЛРОДУИГАЪЙИШЕЕЖЯХУЬЛБАНЯЮТОИНЙЕХЪРОЫМОРРХСНГБЕСКШРЕЩГППКЫЗСЩНЛЙНЛАЕЪЯМБУТЗСЧПАОЛШЖЕЩТЯОЛШАЫБГТЭШЫСШЧГХЛЫЪВЯИИУЩЗЪВКЦЦОПЙЮЕВЛИАРЗРПНЛОИЛХЫВЕЙОАНЮВЗЛЧДАВИФЗЛЛАДДЙЮАДРПОФХФВХДУОЩЮЫЗСЩЖЛБЬРУТОООИЪРБРЕБЫИХВУВОСЛДШЭВТОПНЪТЦЗЛОДНЪТЫКСЩРПДМЪКБАГГПКЫМВООИДХУПЮЪНГСЮТВЮТЖМТЧХЖПОУСПЦРЙВИИУЩУЯТОЙГШДШЮНРОЗШДКВАЕЖОИЛЫЫЗОДНЛЭЯРЙЕЕМОКЗФМВГФАЙЬТМЛГИИРРЛЪАЩЯЗБШЮАОЫЛЛХЮЫЮДУЖРЙЯХЙИБГТМХГБОЪСИГФРНИЫНСЛХЩВНЖРНЙСЮЙАОБНТЬРИУЫЛЛРЫЬЕАЗЖЧБЙХЙЬЖШЙЕХЪРОЫВЫКЩЮУТЕУЖДЦЭММЕИВЙРРНЕАГГДДШААЖНВМЫСЙЕЪСЯШУЬКГАГЗБЧШАСЗГТСУТВЯЪЯВАРРБОЪСНЪТЮБОЖЯПСЫЧЖНГФНЪТУТЕКЖНЭКДВЛЧЫИГАЬККЛПУЩСРОИЙТПЪОШАААЛППШРУААРНДЮТХЕЫОЫФЩЮЙОЪГХЕЫЫКЧЖНСУЗТЩДЛЭЩБДБЭПЕЗТГЮАЖИАЧЕКЗФЪЕЫЙРДКВЮЯЖЖЕВХЯТЯЗНОАХЦВЛГМОЕОЮЖЗЖГЛЛЦШОНЛНПДЛРФИХИОЗУВЮСФЙЛЙФЮПТГЭППЭРЙУВТТБХЭКЕИНВНЮБРОАЖООХЫЮДАЖООХЫЮДЕБИЦРШНОЖСИИРШНОЖСЬДЩШХЕЗДВБРШАЕЫЛЛЙЮЬСНШЛРБЬУМВЛУИГЫВЮСЖЛМЙИЬЗЛЖРТДКЯТИОДЖБЮВИНОПУЛЫЪРОДУИЮЙХЙЬОАОХЮФМРБНСПТШЕДШИЬЧЮТВНЛМРЙЮЬРАБЛТБСУМВЕУИУРШНОЖСИИРШНОЖСЬЙМЧВЛЕЕИКХЬПЕИУУЕЗЭСНОМРПЯЮЧНШСЬЙСАММОСОВХЪВККРДУХТБЛЕСКДНРТАДГЕУ \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5efa747 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module xor-vigenere + +go 1.19 + +require github.com/schollz/progressbar/v3 v3.11.0 + +require ( + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/rivo/uniseg v0.4.2 // indirect + golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect + golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..49df8a6 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= +github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/schollz/progressbar/v3 v3.11.0 h1:3nIBUF1Zw/pGUaRHP7PZWmARP7ZQbWQ6vL6hwoQiIvU= +github.com/schollz/progressbar/v3 v3.11.0/go.mod h1:R2djRgv58sn00AGysc4fN0ip4piOGd3z88K+zVBjczs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/main.go b/main.go new file mode 100644 index 0000000..8eee94d --- /dev/null +++ b/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "sync" + "time" + + "github.com/schollz/progressbar/v3" +) + +const ( + outputSampleSize = 6 + maxSecretKeySize = 12 + expectedMinTextLen = 2000 +) + +func main() { + input := flag.String("i", "", "cypher text") + debug := flag.Bool("debug", false, "print debug info") + secretKeyLen := flag.Int("l", 0, "length of secret key if known") + permLettersLen := flag.Int("p", 4, "number of most frequest russian letters for cartesian permutation") + workers := flag.Int("w", 0, "number of parallel workers (default number of CPU)") + resultLen := flag.Int("r", 50, "number of top results to process") + sampleSize := flag.Int("s", 2000, "size of cypher text sample to analyze, 0 to analyze whole text") + flag.Parse() + + start := time.Now() + + // Read cypher text. + data, err := os.ReadFile(*input) + if err != nil { + fmt.Printf("reading cypher text: %s\n", err.Error()) + os.Exit(1) + } + fmt.Printf("Cypher text file: %s\n", *input) + cypherText := []rune(string(data)) + + if len(cypherText) < expectedMinTextLen { + fmt.Printf("! Text length %d < %d, frequency analysis may be affected", len(cypherText), expectedMinTextLen) + } + + // Set number of parallel workers. + if *workers == 0 { + cpuN := runtime.NumCPU() + workers = &cpuN + } + fmt.Printf("Parallel workers: %d (override with -w flag)\n", *workers) + + // Finding secret key length. + if *secretKeyLen == 0 { + fmt.Print("Trying to predict secret key length ...") + prediction := predictSecretKeyLen(cypherText, maxSecretKeySize) + fmt.Printf(" %d (override with -l flag)\n", prediction) + secretKeyLen = &prediction + } else { + fmt.Printf("Secret key length: %d (override with -w flag)\n", *secretKeyLen) + } + + // Set sample size to analyze crypto text. + if *sampleSize == 0 { + size := len(cypherText) + sampleSize = &size + } + fmt.Printf("Cypher text sample size to analyze: %d (override with -s flag)\n", *sampleSize) + + // Print other parameters. + fmt.Printf("Number of letters in permutations: %d [%s] (override with -p flag)\n", *permLettersLen, string(mostFrequest[:*permLettersLen])) + + // + fmt.Printf("Number of results to show: %d (override with -r flag)\n", *resultLen) + + // Start decoding. + coder := NewCoder() + groups := groupText(cypherText, *secretKeyLen) + + permutations := make([][]rune, *secretKeyLen) + for i := range permutations { + permutations[i] = make([]rune, *permLettersLen) + mostUsedRune := popularRune(groups[i]) + for j, r := range mostFrequest[:*permLettersLen] { + permutations[i][j] = rune(coder.ToRune[coder.ToCode[mostUsedRune]^coder.ToCode[r]]) + } + } + + possibleKeysLen := 1 + for _, p := range permutations { + possibleKeysLen *= len(p) + } + + result := NewTop(*resultLen) + pb := newProgressBar(possibleKeysLen) + + jobs := make(chan []rune) + wg := new(sync.WaitGroup) + wg.Add(possibleKeysLen) + + for i := 0; i < *workers; i++ { + go func(jobs <-chan []rune) { + for job := range jobs { + decodedFreq := coder.RuneFrequency(cypherText[:*sampleSize], job) + result.Add(string(job), coder.AlphabetDivergence(decodedFreq, float64(*sampleSize))) + pb.Add(1) + wg.Done() + } + }(jobs) + } + + cartesian(permutations, func(r []rune) { + jobs <- r + }) + wg.Wait() + fmt.Println() + + // Print results. + for _, s := range result.List() { + fmt.Printf("Key=%s Sample:%s Div:%f\n", s, string(coder.Code(cypherText[:outputSampleSize**secretKeyLen], []rune(s))), result.Value(s)) + } + + fmt.Printf("Execution duration: %s\n", time.Since(start)) + if *debug { + PrintMemUsage() + } +} + +func newProgressBar(cap int) *progressbar.ProgressBar { + return progressbar.NewOptions(cap, + progressbar.OptionSetDescription("Analyzing"), + progressbar.OptionSetWriter(os.Stderr), + progressbar.OptionSetWidth(10), + progressbar.OptionThrottle(100*time.Millisecond), + progressbar.OptionShowCount(), + progressbar.OptionShowIts(), + progressbar.OptionSetItsString("keys"), + progressbar.OptionOnCompletion(func() { + fmt.Fprint(os.Stderr, "\n") + }), + progressbar.OptionSpinnerType(14), + progressbar.OptionSetWidth(50), + progressbar.OptionSetTheme(progressbar.Theme{ + Saucer: "#", + SaucerHead: "#", + SaucerPadding: " ", + BarStart: "[", + BarEnd: "]", + }), + ) +} + +func predictSecretKeyLen(text []rune, limit int) int { + lim := len(text) / 2 + if lim > limit { + lim = limit + } + + ln := len(text) + + var ( + predictedLen int + predictedOverlap int + ) + for i := 1; i <= lim; i++ { + overlap := 0 + for j := 0; j < ln; j++ { + shiftIndex := (j + i) % ln + if text[j] == text[shiftIndex] { + overlap++ + } + } + if overlap > predictedOverlap { + predictedOverlap = overlap + predictedLen = i + } + } + + return predictedLen +} diff --git a/text.go b/text.go new file mode 100644 index 0000000..c3776c1 --- /dev/null +++ b/text.go @@ -0,0 +1,61 @@ +package main + +func groupText(text []rune, period int) []map[rune]int { + res := make([]map[rune]int, period) + for i := range res { + res[i] = make(map[rune]int, alphabetLen) + } + + for i, r := range text { + index := i % period + res[index][r] += 1 + } + + return res +} + +func popularRune(m map[rune]int) rune { + var ( + res rune + max = 0 + ) + for l, counter := range m { + if counter > max { + res = l + max = counter + } + } + + return res +} + +func cartesian(runes [][]rune, processCombination func([]rune)) { + c := 1 + for _, a := range runes { + c *= len(a) + } + if c == 0 { + return + } + + b := make([]rune, c*len(runes)) + n := make([]int, len(runes)) + s := 0 + + for i := 0; i < c; i++ { + e := s + len(runes) + pi := b[s:e] + s = e + for j, n := range n { + pi[j] = runes[j][n] + } + for j := len(n) - 1; j >= 0; j-- { + n[j]++ + if n[j] < len(runes[j]) { + break + } + n[j] = 0 + } + processCombination(pi) + } +} diff --git a/top.go b/top.go new file mode 100644 index 0000000..aefc2c2 --- /dev/null +++ b/top.go @@ -0,0 +1,82 @@ +package main + +import ( + "sort" + "sync" +) + +type Top struct { + mu sync.Mutex + capacity int + positions map[string]float64 + threshold float64 + thresholdKey string +} + +func NewTop(capacity int) Top { + return Top{ + capacity: capacity, + positions: make(map[string]float64, capacity), + } +} + +func (t *Top) Add(key string, divergence float64) { + t.mu.Lock() + defer t.mu.Unlock() + + // When structure is not filled, add all values. + if len(t.positions) < t.capacity { + t.positions[key] = divergence + if divergence > t.threshold { + t.threshold = divergence + t.thresholdKey = key + } + return + } + + // When structure is filled, do not add values outside of the threshold. + if divergence > t.threshold { + return + } + + // To add new value, remove threshold value. + delete(t.positions, t.thresholdKey) + t.positions[key] = divergence + + // Find new threshold value in updated map. + var ( + newThreshold float64 + newThresholdVal string + ) + for key, divergence := range t.positions { + if divergence > newThreshold { + newThreshold = divergence + newThresholdVal = key + } + } + t.threshold = newThreshold + t.thresholdKey = newThresholdVal +} + +func (t *Top) List() []string { + t.mu.Lock() + defer t.mu.Unlock() + + res := make([]string, 0, len(t.positions)) + for key := range t.positions { + res = append(res, key) + } + + sort.Slice(res, func(i, j int) bool { + return t.positions[res[i]] < t.positions[res[j]] + }) + + return res +} + +func (t *Top) Value(key string) float64 { + t.mu.Lock() + defer t.mu.Unlock() + + return t.positions[key] +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..45e3b95 --- /dev/null +++ b/util.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "runtime" +) + +func PrintMemUsage() { + var m runtime.MemStats + runtime.ReadMemStats(&m) + fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) + fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) + fmt.Printf("\tNumGC = %v\n", m.NumGC) +} + +func bToMb(b uint64) uint64 { + return b / 1024 / 1024 +}