At this stage, StegosauR
converts any message into a
sequence of code numbers and hides it in a very orderly manner in the
pixels of an image. This is not very desirable of course, as it is
sufficient to loop across all pixels in a very predictable way to
extract the whole message. A better solution would be to scramble the
position of each code number across the whole image in a seemingly
random way. StegosauR
achieves this step via deterministic
coordinate generation.
Together with the secret message and the target image, the user is
also asked to provide an encryption password (or let
StegosauR
use the default one).
To add a bit of complexity, the password is converted into a hexadecimal hash value, which is then converted to a number-only sequence.
library(openssl)
secret <- "StegosauR"
scrt_sha <- sha512(secret)
scrt_sha
## [1] "b4abb8e986da34dd805f91bf2e528c853a5647134389ba36b031a33c0633fc709963958ae2af7b16d86d7c1617547993129981e3ca5fccf21712d7c56b286d4b"
#change to integer
#based on: https://stackoverflow.com/questions/27442991/how-to-get-a-hash-code-as-integer-in-r
hex_to_int = function(h) {
xx = strsplit(tolower(h), "")[[1L]]
pos = match(xx, c(0L:9L, letters[1L:6L]))
}
int_sha <- paste(hex_to_int(scrt_sha), collapse="")
int_sha
## [1] "125111212915109714114514149161610212163156391396411675824549101211471214211441317441613811010741069111531116812271497148132728658101042310109215413116161313163282314813671239714512"
This number is used as a base to generate a sequence of pseudo-random
values. The algorithm used by StegosauR
is a simple Linear
Congruential Generator (LCG), with the basic formula:
The values of \(a\), \(b\) and \(m\) come from int_sha, respectively from its first, middle and last 3 digits. The seed \(x_{0}\) comes from int_sha too. More precisely, it is just \(a\), \(b\) and \(m\) pasted together.
Each code number requires three coordinates to be written into an
image: x, y and channel number. StegosauR
generates an
appropriate number of pseudo-random values, rescales them within the
minimum and maximum limits of the image, and assigns them to each code
number. The rescaling procedure greatly reduces the diversity produced
by the pseudo-random generator. For this reason, StegosauR
always generates more numbers than needed (based on message length),
then weeds out duplicates after rescaling.
Very short example:
#Le's say we want to hide a single letter. StegosauR will transform it into a twelve-digit code and
#we will need three coordinates for every code number within that twelve-digits code.
coords <- 12*3*2
#The final multiplication by 2 is added to generate more pseudo-random numbers than necessary, so
#we can remove duplicates if needed.
pseud_rand <- numeric()
#split int_sha into three parts
int_sha.head <- as.numeric(substr(int_sha,1,3))
int_sha.mid <- as.numeric(substr(int_sha,floor((nchar(int_sha)/2))-0,floor((nchar(int_sha)/2))+2))
int_sha.tail <- as.numeric(substr(int_sha,nchar(int_sha)-2,nchar(int_sha)))
#seed value
pseud_rand[1] <- as.numeric(paste(int_sha.head, int_sha.mid, int_sha.tail, sep = ""))
for (i in 2:coords) {
pseud_rand[i] <- magrittr::mod((int_sha.head*pseud_rand[i-1])+int_sha.mid, int_sha.tail*2^32)
}
#fill a three-column matrix
mx <- as.data.frame(matrix(pseud_rand, nrow = 12*2, ncol = 3, byrow=TRUE))
colnames(mx) <- c("x","y","channel")
#rescale to fit within minimum and maximum x, y and channel values (these values are automatically extracted from the image during the encoding/decoding procedure).
#rescale x
min_x_new <- 1
max_x_new <- 404
min_x_old <- min(mx[,1])
max_x_old <- max(mx[,1])
mx[,1] <- floor(((max_x_new-min_x_new)/(max_x_old-min_x_old))*(mx[,1]-max_x_old)+max_x_new)
#rescale y
min_y_new <- 1
max_y_new <- 250
min_y_old <- min(mx[,2])
max_y_old <- max(mx[,2])
mx[,2] <- floor(((max_y_new-min_y_new)/(max_y_old-min_y_old))*(mx[,2]-max_y_old)+max_y_new)
#rescale channel
min_c_new <- 1
max_c_new <-3
min_c_old <- min(mx[,3])
max_c_old <- max(mx[,3])
mx[,3] <- floor(((max_c_new-min_c_new)/(max_c_old-min_c_old))*(mx[,3]-max_c_old)+max_c_new)
#remove possible duplicates
mx <- unique(mx)
#Keep only the necessary amount of rows (12).
mx <- mx[13:24,]
This is the final set of pseudo-random coordinates where each code number will be placed:
mx
## x y channel
## 13 347 218 1
## 14 370 29 1
## 15 217 171 1
## 16 337 63 1
## 17 142 82 1
## 18 118 230 2
## 19 85 248 1
## 20 20 74 1
## 21 157 76 2
## 22 238 82 1
## 23 24 96 3
## 24 208 70 2
The same sequence can be reproduced in order to decode the message, provided that the correct password is used.
The R package openssl
, used here to generate the hash
sequence, offers also the possibility to add some salt to the
hashing procedure by adding a second user-defined passcode. This
additional security measure does not necessarily improve the protection
offered by StegosauR
, since the hashing procedure is used
just as a way to generate a long number for the pseudo-random generator.
I implemented the salting option anyway. It might have some interesting
applications here, like a lock that can only be opened by turning two
keys at the same time.