initial upload
This commit is contained in:
@@ -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.
|
||||||
@@ -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<l;i++) {
|
||||||
|
c = substr(kp, i+1, 1)
|
||||||
|
while(index(r, c) > 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<l2;i++) {
|
||||||
|
c = substr(r, i+1, 1)
|
||||||
|
if(index(s, c) == 0)
|
||||||
|
s = s c
|
||||||
|
}
|
||||||
|
return (substr(s, l+1) substr(s, 0, l))
|
||||||
|
}
|
||||||
|
|
||||||
|
function dracondi_enc(msg, ka1, ka2, offs, l, i, r, c, rc) {
|
||||||
|
offs = int(rand() * 26)
|
||||||
|
r = substr(ka1, offs+1, 1)
|
||||||
|
msg = prepmsg(msg)
|
||||||
|
l = length(msg)
|
||||||
|
for(i=0;i<l;i++) {
|
||||||
|
c = substr(msg, i+1, 1)
|
||||||
|
rc = substr(ka1, ((index(ka1, c) - 1 + offs) % 26) + 1, 1)
|
||||||
|
r = r rc
|
||||||
|
offs = (index(ka2, rc) + i) % 26 # +1 already implied by index
|
||||||
|
}
|
||||||
|
return group5(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dracondi_dec(msg, ka1, ka2, offs, l, i, r, c, rc) {
|
||||||
|
msg = prepmsg(msg)
|
||||||
|
offs = index(ka1, substr(msg,1,1)) - 1
|
||||||
|
msg = substr(msg, 2)
|
||||||
|
r = ""
|
||||||
|
l = length(msg)
|
||||||
|
for(i=0;i<l;i++) {
|
||||||
|
c = substr(msg, i+1, 1)
|
||||||
|
rc = substr(ka1, ((index(ka1, c) + 25 - offs) % 26) + 1, 1)
|
||||||
|
r = r rc
|
||||||
|
offs = (index(ka2, c) + i) % 26 # +1 already implied by index
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
srand()
|
||||||
|
split("", CHR)
|
||||||
|
split("", ORD)
|
||||||
|
for(i=0;i<256;i++) {
|
||||||
|
c = sprintf("%c", i)
|
||||||
|
CHR[i] = c
|
||||||
|
ORD[c] = i
|
||||||
|
}
|
||||||
|
ORD_A = ORD["a"]
|
||||||
|
mode = ARGV[ARGC - 3]
|
||||||
|
ka1 = dracondi_key(ARGV[ARGC - 2])
|
||||||
|
ka2 = dracondi_key(ARGV[ARGC - 1])
|
||||||
|
if(mode == "k") {
|
||||||
|
print "Keyed alphabet 1:", ka1
|
||||||
|
print "Keyed alphabet 2:", ka2
|
||||||
|
} else if(mode == "e") {
|
||||||
|
while(getline msg)
|
||||||
|
if(length(msg) > 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"
|
||||||
|
}
|
||||||
Executable
+65
@@ -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()
|
||||||
Reference in New Issue
Block a user