[back to intro] - [to part 1] - [to part 2] - [practical example] - [Capabilities]

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:

\(x_{n} \equiv ax_{n-1} + b\pmod{m}\)

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.

[back to intro] - [to part 1] - [to part 2] - [practical example] - [Capabilities]