173 lines
5.6 KiB
R
173 lines
5.6 KiB
R
# The ADFGVX cipher.
|
|
# See also eg. https://www.nku.edu/~christensen/092hnr304%20ADFGVX.pdf
|
|
|
|
# ADFGVX class using R6
|
|
library(R6)
|
|
|
|
ADFGVX <- R6Class("ADFGVX",
|
|
public = list(
|
|
polybius = NULL,
|
|
pdim = NULL,
|
|
key = NULL,
|
|
keylen = NULL,
|
|
alphabet = NULL,
|
|
encode = NULL,
|
|
decode = NULL,
|
|
|
|
# Constructor
|
|
initialize = function(s, k, alph = "ADFGVX") {
|
|
self$polybius <- toupper(strsplit(s, "")[[1]])
|
|
self$pdim <- floor(sqrt(length(self$polybius)))
|
|
self$alphabet <- toupper(strsplit(alph, "")[[1]])
|
|
|
|
# Create encoding dictionary
|
|
self$encode <- list()
|
|
for (i in 1:self$pdim) {
|
|
for (j in 1:self$pdim) {
|
|
char <- self$polybius[(i - 1) * self$pdim + j]
|
|
self$encode[[char]] <- c(self$alphabet[i], self$alphabet[j])
|
|
}
|
|
}
|
|
|
|
# Create decoding dictionary
|
|
self$decode <- list()
|
|
for (char in names(self$encode)) {
|
|
key_str <- paste(self$encode[[char]], collapse = "")
|
|
self$decode[[key_str]] <- char
|
|
}
|
|
|
|
stopifnot(self$pdim^2 == length(self$polybius) &&
|
|
self$pdim == length(self$alphabet))
|
|
|
|
self$key <- toupper(strsplit(k, "")[[1]])
|
|
self$keylen <- length(self$key)
|
|
},
|
|
|
|
# Encrypt with the ADFGVX cipher
|
|
encrypt = function(s) {
|
|
chars_upper <- toupper(strsplit(s, "")[[1]])
|
|
chars_filtered <- chars_upper[chars_upper %in% self$polybius]
|
|
|
|
# Encode characters
|
|
chars <- unlist(lapply(chars_filtered, function(c) self$encode[[c]]))
|
|
|
|
# Create column vectors
|
|
colvecs <- list()
|
|
for (i in 1:self$keylen) {
|
|
indices <- seq(i, length(chars), by = self$keylen)
|
|
colvecs[[self$key[i]]] <- chars[indices]
|
|
}
|
|
|
|
# Sort by key
|
|
sorted_names <- sort(names(colvecs))
|
|
result <- unlist(lapply(sorted_names, function(name) colvecs[[name]]))
|
|
|
|
return(paste(result, collapse = ""))
|
|
},
|
|
|
|
# Decrypt with the ADFGVX cipher
|
|
decrypt = function(s) {
|
|
chars <- toupper(strsplit(s, "")[[1]])
|
|
chars <- chars[chars %in% self$alphabet]
|
|
|
|
sortedkey <- sort(self$key)
|
|
order <- sapply(sortedkey, function(ch) which(self$key == ch)[1])
|
|
originalorder <- sapply(self$key, function(ch) which(sortedkey == ch)[1])
|
|
|
|
# Calculate strides (column lengths)
|
|
div_result <- length(chars) %/% self$keylen
|
|
rem_result <- length(chars) %% self$keylen
|
|
strides <- sapply(order, function(i) {
|
|
div_result + ifelse(rem_result >= i, 1, 0)
|
|
})
|
|
|
|
# Calculate starts and ends of columns
|
|
starts <- c(1, cumsum(strides[-length(strides)]) + 1)
|
|
ends <- starts + strides - 1
|
|
|
|
# Get reordered columns
|
|
cols <- lapply(originalorder, function(i) {
|
|
chars[starts[i]:ends[i]]
|
|
})
|
|
|
|
# Recover the rows
|
|
nrows <- (length(chars) - 1) %/% self$keylen + 1
|
|
pairs <- c()
|
|
for (i in 1:nrows) {
|
|
for (j in 1:self$keylen) {
|
|
if ((i - 1) * self$keylen + j > length(chars)) break
|
|
pairs <- c(pairs, cols[[j]][i])
|
|
}
|
|
}
|
|
|
|
# Decode pairs
|
|
result <- c()
|
|
for (i in seq(1, length(pairs) - 1, by = 2)) {
|
|
pair_str <- paste(pairs[i:(i+1)], collapse = "")
|
|
result <- c(result, self$decode[[pair_str]])
|
|
}
|
|
|
|
return(paste(result, collapse = ""))
|
|
}
|
|
)
|
|
)
|
|
|
|
# Main execution
|
|
set.seed(123) # For reproducibility
|
|
POLYBIUS <- paste(sample(strsplit("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "")[[1]]),
|
|
collapse = "")
|
|
|
|
# Get dictionary words - try multiple sources
|
|
get_valid_key <- function() {
|
|
# Try to read unixdict.txt if it exists
|
|
if (file.exists("unixdict.txt")) {
|
|
dict_words <- readLines("unixdict.txt")
|
|
} else {
|
|
# Use sample words if file doesn't exist
|
|
dict_words <- c("absolutes", "abruption", "abundance", "abysmally",
|
|
"acrobatic", "algorithm", "ambiguity", "amplitude",
|
|
"ancestory", "archetype", "asynchron", "auctioned",
|
|
"backbone", "backfired", "backlights", "bandwidth",
|
|
"benchmark", "biography", "birthplace", "blasphemy",
|
|
"boulevard", "bricklaying", "broadcast", "butchering",
|
|
"cauterize", "chemicals", "chromatic", "clipboard",
|
|
"completing", "conjugate", "copyright", "daughters",
|
|
"debuggers", "delimiters", "demograph", "dialyzing",
|
|
"dictionary", "education", "equations", "factorize",
|
|
"geography", "goshdarn", "graciously", "graphed",
|
|
"hampshire", "imploding", "indexable", "jeculates",
|
|
"keyboards", "labyrinth", "livestock", "logarithm",
|
|
"machinery", "molecules", "nightmare", "obscurity",
|
|
"particles", "quizboard", "racehorst", "reduction",
|
|
"savepoint", "sectional", "shipyard", "splatting",
|
|
"substance", "technology", "ulceration", "vineyard",
|
|
"warehouse", "workplace", "xanthopsy", "yachtsmen")
|
|
}
|
|
|
|
# Filter for 9-letter words with unique characters
|
|
valid_words <- dict_words[sapply(dict_words, function(w) {
|
|
n <- nchar(w)
|
|
n == 9 && length(unique(strsplit(w, "")[[1]])) == n
|
|
})]
|
|
|
|
if (length(valid_words) == 0) {
|
|
stop("No valid 9-letter words with unique characters found")
|
|
}
|
|
|
|
return(toupper(sample(valid_words, 1)))
|
|
}
|
|
|
|
KEY <- get_valid_key()
|
|
|
|
# Create cipher and encrypt/decrypt
|
|
SECRETS <- ADFGVX$new(POLYBIUS, KEY)
|
|
message <- "ATTACKAT1200AM"
|
|
|
|
cat("Polybius:", POLYBIUS, ", Key:", KEY, "\n")
|
|
cat("Message:", message, "\n")
|
|
|
|
encoded <- SECRETS$encrypt(message)
|
|
decoded <- SECRETS$decrypt(encoded)
|
|
|
|
cat("Encoded:", encoded, "\n")
|
|
cat("Decoded:", decoded, "\n")
|