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