From 2d05e868aa500b9f611a03a126499cd07a45dd3f Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 24 Mar 2025 10:21:44 +0200 Subject: [PATCH] initial upload --- README.md | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++ dracondi.awk | 96 +++++++++++++++++++++++++++++++++++++ dracondi.py | 65 ++++++++++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 README.md create mode 100644 dracondi.awk create mode 100755 dracondi.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..915658f --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +The DRACONDI cipher +=================== +DRACONDI (short for "Dual-key Rotating Alphabet CONDI") is a symmetrical, pen +and paper, dual-key cipher based upon the ACA's CONDI ("CONsecutive DIgraph") +cipher. It combines the ease of manual and software implementations with a +lot of security enhancements compared to the original CONDI design. + +Design rationale +---------------- +The original CONDI cipher as +[described by ACA](https://cryptogram.org/downloads/aca.info/ciphers/Condi.pdf), +while being very simple to implement and use, poses multiple problems when it +comes to security, such as: + +- the initial offset only influences the first ciphertext letter; +- repeated plaintext digraphs are encrypted into the same ciphertext digraphs; +- the keyspace size is too small for modern use (about 88 bits); +- the keying scheme doesn't consider repeated keyphrase letters; +- the original CONDI preserves word division which eases the cryptanalysis. + +By ditching the word division and altering the CONDI's offset calculation step +to account for the letter position in the message and the ciphertext letter +position in the second keyed alphabet, DRACONDI achieves the following +improvements: + +- the initial offset is now affecting the rest of the ciphertext; +- the keyspace size has been increased to about 225 bits; +- repeated plaintext digraphs no longer encrypt into the same ciphertext; +- repeated letters in the keyphrase now affect the keyed alphabet; +- the ciphertext is (optionally) divided into the groups of 5 that no longer + convey any additional information about the plaintext nature. + +These improvements come at a cost of slightly longer offset calculation using +the second keyed alphabet, but otherwise the overall simplicity of the original +CONDI cipher is fully retained in DRACONDI. + +The DRACONDI algorithm +---------------------- +The entire DRACONDI cipher can be described in three sections: key preparation, +encryption and decryption. Additionally, all key and message material which is +alphabetical must first undergo the sanitization part (i.e. lowercasing it and +removing all non-alphabetical characters not belonging to the `[a-z]` range). + +### Key preparation + +Unless both parties have agreed on using two completely randomized alphabets +(which is the most preferred way of setting up the key material for DRACONDI +and can be achieved in different ways, e.g. sharing a shuffled 52-card deck), +the key preparation process takes two keyphrases and outputs two mixed +alphabets, KA1 and KA2. The algorithm is identical for both keyphrases. +Follow these steps to create a mixed alphabet from a keyphrase of length `N` +(without any spaces or non-alphabetical characters): + +1. Start writing down the (sanitized) keyphrase "as is". For every letter, + replace it with the **previous** letter in the alphabet if it already + occurred in the keyphrase. Repeat this process if necessary until a new + letter is found. +2. Write the rest of the alphabet in the natural order. +3. Count `N` letters forward from the beginning and write down the resulting + alphabet, wrapping around at the end. If `N` is over 25, count `N mod 26` + letters instead. + +You should be left with a mixed and shifted alphabet of 26 unique letters. +Write the indices from 0 to 25 above it for more convenient usage. + +Example: with the key phrase `sivispacemparabellum` (20 letters long), the +unshifted alphabet will look like `sivhrpacemozqybdlkujfgntwx`. Then, since +our shift is 20, we read it from the 20th position (starting at 0) wrapping +around at the end: `fgntwxsivhrpacemozqybdlkuj`. This is our final keyed +alphabet. Now, we need to repeat the process with a different keyphrase to +get the second keyed alphabet. + +### Encryption + +The encryption process takes an initial plaintext message in the sanitized +form and two keyed alphabets, KA1 and KA2. Follow these steps to encrypt a +message with DRACONDI. + +1. Randomly choose the initial offset from 0 to 25. Write its corresponding + letter in the KA1 alphabet as the first ciphertext letter. +2. For every plaintext letter, do the following: +2.1. Locate the plaintext letter position in the KA1 alphabet. Locate the + ciphertext position by moving (offset) letters to the right, wrapping + around the alphabet if necessary. +2.2. Write down the ciphertext letter according to the KA1 alphabet. +2.3. Get the position of the ciphertext letter in the KA2 alphabet. Calculate + the next offset as the sum of that position and the amount of letters + already encrypted in the message (modulo 26). + +As an optional step, you can split the ciphertext into 5-letter groups. This +grouping needs to be removed at the sanitization step before decrypting the +message on the receiving end. All reference implementations of DRACONDI in +software are required to implement this step. + +### Decryption + +The decryption process takes a ciphertext message in the sanitized form and +two keyed alphabets, KA1 and KA2. Follow these steps to decrypt a message +with DRACONDI. + +1. Derive the initial offset from the first ciphertext letter in the KA1 + alphabet. Discard the first ciphertext letter afterwards. +2. For every remaining ciphertext letter, do the following: +2.1. Locate the ciphertext letter position in the KA1 alphabet. Locate the + plaintext position by moving (offset) letters to the left, wrapping around + the alphabet if necessary. +2.2. Write down the plaintext letter according to the KA1 alphabet. +2.3. Get the position of the ciphertext letter in the KA2 alphabet. Calculate + the next offset as the sum of that position and the amount of letters + already decrypted in the message (modulo 26). + +Implementation requirements +--------------------------- +When implementing DRACONDI in software, the following rules are in place for an +implementation to be considered correct: + +1. The software shall support three modes: keyed alphabets generation (from the + keyphrase pair), encryption and decryption. +2. The keyed alphabets generation part shall be able to accept already permuted + alphabets and output them completely unaltered, making no changes to them. +3. The encryption part shall sanitize the message and output the ciphertext in + 5-letter groups separated by a whitespace. +4. The decryption part shall sanitize the ciphertext before processing, + removing any whitespace and non-letter characters from the input. + +Credits +------- +Created by Luxferre in 2025, released into the public domain with no warranties. + +The original CONDI cipher was created by Wilfred Higginson in 2011. diff --git a/dracondi.awk b/dracondi.awk new file mode 100644 index 0000000..ddb0529 --- /dev/null +++ b/dracondi.awk @@ -0,0 +1,96 @@ +#!/usr/bin/env awk -f + +function prepmsg(s) { + s = tolower(s) + gsub(/[^a-z]/, "", s) + return s +} + +function group5(s, l, i) { + l = length(s) + if((l % 5) > 0) { + for(i=l%5;i<5;i++) + s = s CHR[ORD_A + int(rand()*26)] + } + gsub(/.{5}/, "& ", s) + return s +} + +function dracondi_key(kp, l, l2, i, r, c, s) { + kp = prepmsg(kp) + l = length(kp) + r = "" + for(i=0;i 0 && length(r) < 26) + c = CHR[ORD_A + ((25 + ORD[c] - ORD_A) % 26)] + r = r c + } + r = r "abcdefghijklmnopqrstuvwxyz" + l2 = length(r) + s = "" + for(i=0;i 0) + print dracondi_enc(msg, ka1, ka2) + else break + } else if(mode == "d") { + while(getline msg) + if(length(msg) > 0) + print dracondi_dec(msg, ka1, ka2) + else break + } else print "Invalid mode" +} diff --git a/dracondi.py b/dracondi.py new file mode 100755 index 0000000..bef6bce --- /dev/null +++ b/dracondi.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +# DRACONDI: Dual-key Rotating Alphabet CONDI + +import sys, re +from collections import OrderedDict +from random import randint, choice +from string import ascii_lowercase + +def prepmsg(s:str): + return re.sub(r'[^a-z]', '', s.lower()) + +def dracondi_key(keyphrase:str): + prekey = '' + oa = ord('a') + for c in prepmsg(keyphrase): + while prekey.find(c) != -1 and len(prekey) < 26: + c = chr(oa + ((25 + ord(c) - oa) % 26)) + prekey += c + offs = len(prekey) % 26 + alpha = prekey + ''.join(ascii_lowercase) + unshifted = ''.join(list(OrderedDict.fromkeys(alpha).keys())) + return (unshifted + unshifted)[offs:offs+26] + +def dracondi_enc(msg:str, ka1:str, ka2:str): + offs = randint(0, 25) + res = ka1[offs] + for i,c in enumerate(prepmsg(msg)): + rc = ka1[(ka1.index(c) + offs) % 26] + res += rc + offs = (ka2.index(rc) + i + 1) % 26 + l = len(res) % 5 + if l > 0: + res += ''.join([choice(ascii_lowercase) for i in range(0, 5 - l)]) + return ' '.join([res[i:i+5] for i in range(0, len(res), 5)]) + +def dracondi_dec(msg:str, ka1:str, ka2:str): + res = '' + msg = prepmsg(msg) + offs = ka1.index(msg[0]) + for i,c in enumerate(msg[1:]): + res += ka1[(26 + ka1.index(c) - offs) % 26] + offs = (ka2.index(c) + i + 1) % 26 + return res + +# entrypoint parameters: [mode] [key1] [key2] +# modes: e - encrypt, d - decrypt, k - print out keyed alphabets +def main(): + mode = sys.argv[1].lower() + kp1 = sys.argv[2] + kp2 = sys.argv[3] + ka1 = dracondi_key(kp1) + ka2 = dracondi_key(kp2) + if mode.startswith('e'): + msg = sys.stdin.read() + print(dracondi_enc(msg, ka1, ka2)) + elif mode.startswith('k'): + print('Keyed alphabet 1:', ka1) + print('Keyed alphabet 2:', ka2) + else: + msg = sys.stdin.read() + print(dracondi_dec(msg, ka1, ka2)) + +if __name__ == '__main__': + main()