CryptoStego
JS library for steganography with encryption - Hide text in an image with encryption and obfuscation. Support least significant bit mode and DCT mode.
Version
v1.8
DEMO
http://stego.js.org Note: Library needs HTML5 support!
Download
Download cryptostego.min.js Note: This JS library needs HTML5 support!
Features
- Obfuscation - Random initialization of invalid bits
- Non-linear bit-by-bit message storage without any header. Built for absolute security (No signal for password error. Wrong password results in wrong message).
- New in 1.8: Valid bits and their order decided by Mersenne Twister PRNG, and seed generated using
SHA512(password)
. This is faster compared to old method and leads to less JS errors. - LSB (Least Significant Bit) mode
- Use least significant bits of RGB channels of each pixel to store message
- Resulting image is visually identical to original one
- Can only be stored in non-compressed format such as PNG
- DCT (Discrete cosine transform) mode
- Store information by slightly changing lowest frequency component of each block in frequency domain
- Robust to image compression but stores less data compared to LSB mode
- Resulting image looks different from original one
- New in 1.8: Add support CbCr downsampling. Hence more robust on compression at level 5
- New in 1.8: Add support to JPEG style quantization and the bucket range is thus more reasonable on secrecy and robustness trade-off
- New in 1.8: Use DCT difference instead of absolute value. Thus basic robustness against filtering (e.g. exposure, brilliance adjustment)
- New in 1.8: Add support to write data into any frequency band on DCT space. Before I only wrote to lowest frequency band.
- New in 1.8: More detailed comments in base class code. You can adjust all parameters for your use case
Usage
This library provides 3 wrapper functions. Use <script src="cryptostego.min.js"></script>
in your HTML to include this library.
loadIMGtoCanvas(inputid, canvasid, callback, maxsize)
This function loads an image from file input to a dynamically generated canvas. After that, it will call callback()
function to do some stuff, and then delete the generated canvas.
inputid
is the id of the html5 file input.- You need to put a file input element like
<input type="file" id="file" accept="image/*" />
in your HTML and ask user to select an image here.
- You need to put a file input element like
canvasid
is the id of the canvas that this function will generate.canvasid
will be generated by this function dynamically when called. You will use thiscanvasid
incallback()
to locate the canvas.- You must use an id that not currently used in your HTML, When this function is called, a canvas with id to be
canvasid
will be created. Then, the image selected by user will be loaded to this canvas. - Canvas created by this function (
canvasid
) will be deleted after finishing calling callback function. So you might want to copy image in this canvas to another canvas in callback function or trig a download in callback function.
- You must use an id that not currently used in your HTML, When this function is called, a canvas with id to be
callback
is the callback function that will be called after image successfully loaded to canvas.- Use
canvasid
to locate the canvas incallback()
function. canvasid
will be deleted after callingcallback()
. Make sure you store all data needed incallback
function.- An example for callback -> download the result image after steganography:
//callback function is writefunc() function writefunc(){ if(writeMsgToCanvas('canvas',$("#msg").val(),$("#pass").val(),3)!=null){ var myCanvas = document.getElementById("canvas"); //canvasid='canvas' var image = myCanvas.toDataURL("image/jpeg",1.0); var element = document.createElement('a'); element.setAttribute('href', image); element.setAttribute('download', 'result.jpg'); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } } //For convenience, jQuery is used here. But that's not necessary. loadIMGtoCanvas('file','canvas',writefunc,500);
- Use
maxsize
is the max width or height for created canvas (default value is 0)- If either image width or image height larger than
maxsize
, This function scale the image so that it fits the generated canvas with width and height not larger thanmaxsize
- If
maxsize
<=0, image will not be scaled (use original size). This is not recommended if your callback function is steganography function (write info to image). As super large image will make browser dead. A recommended value is 500. - Make sure when callback function is reading info from image, your
maxsize
is at least themaxsize
you use for steganography. As steganography algorithm is not robust to scale.
- If either image width or image height larger than
writeMsgToCanvas(canvasid,msg,pass,level)
This function writes your message into image in canvas canvasid
. Before calling this function, make sure some image is loaded in canvasid
. Usually, this function will be called in callback function of loadIMGtoCanvas
-
canvasid
specifies the id of the canvas to whose image the message will be written to. -
msg
specifies the message. -
pass
specifies the password for retrieving the message. (default value is '') -
level
an integer specifies the steganography level. [0-5]0
-> LSB mode (default), result image looks identical to original image.1
-5
-> DCT mode, higher value means better robustness to compression but the image looks more different from the original one.- Generally, if you don't need image compression robustness, use level
0
, otherwise, level2
and3
are recommended.
-
Return value is either
true
or a string,===true
stands forsuccess
and a string stands forfailure
with string being error message.
readMsgFromCanvas(canvasid,pass,level)
This function reads your message from image in canvas canvasid
. Before calling this function, make sure some image is loaded in canvasid
. Usually, this function will be called in callback function of loadIMGtoCanvas
- To successfully read message,
pass
andlevel
should be same as what you use inwriteMsgToCanvas
. And the image incanvasid
should be the result image ofwriteMsgToCanvas
. - All parameters have same meaning as in
writeMsgToCanvas
. - Return value is a string
- the string is either retrieved message or error message.
Advance Usage
writeMsgToCanvas
and readMsgFromCanvas
functions are wrappers with 5 sets of pre-defined parameters. If none of them work for you, you can finetune those parameters yourself using writeMsgToCanvas_base(canvasid, msg, pass, use_dct, num_copy, multiply, loc, use_y, use_downsampling)
and readMsgFromCanvas_base(canvasid, pass, use_dct, num_copy, multiply, loc, use_y, use_downsampling)
. Configurable parameters are use_dct, num_copy, multiply, loc, use_y, use_downsampling
. Make sure they are same on write and read otherwise read will fail.
writeMsgToCanvas_base
returns true
on success (check with result === true
). Otherwise, a string with error message.
readMsgFromCanvas_base
returns a size 2 array [status, message]
: status
is a boolean: true
means success and decrypted message is in message
. false
means failure and error message is in message
. Note, status===true
does not imply real success, the message
might be random characters if password is wrong. By design, there's no way to check if this function call is really successful.
Configurable parameters
- use_dct (bool): true for DCT, false for LSB
- num_copy (positive integer): how many copies of each bit to write into image. Larger value is more robust but reduces data capacity (how many data you can write). For LSB, you should just use
num_copy=1
as LSB is not robust to compression anyway
below only valid for use_dct=true
- multiply (positive real number):
Q' = multiply * Q
will be used to quantize DCT matrix.Q
is JPEG 50% quantization matrix. Larger value is more robust but image is more distorted - loc (1D array of int 0-63): which frequency band locations on each block to write data. For example
[1,8]
means to use frequency matrix location[(0, 1), (1, 0)]
and[0]
means only using lowest frequency band - use_y (bool): whether to manipulate Y channel. If
false
, data will only be written to CbCr channels - use_downsampling(bool): whether to downsample on CrCb, if
true
, CbCr DCT will be performed on16*16
blocks
Build Your Project with CryptoStego
Generally speaking, you don't need to touch any algorithm details as they are well encapsulated. I think for most (99%) use cases (for example, you don't want to use canvas
), you can just adapt writeMsgToCanvas_base
and readMsgFromCanvas_base
to fit your needs. Any function called by those two _base
functions is pure algorithm.
Compression Robustness for DCT
Raw image and data
You can download those images and try to decrypt them on http://stego.js.org. Leave the password cell empty.
Image before steganography / post-stego (level 0) are same to human eyes (650.1KB in PNG format):
Data:
你好,世界!
HELLO WORLD!
¡HOLA MUNDO!
مرحبا بالعالم!
BONJOUR LE MONDE!
こんにちは世界!
ПРИВЕТ МИР!
The decryption results should be correct for all levels without compression. Above image is generated using level 0.
Limit of Level 1 (Compression Ratio 15.8% - 102.6KB)
Level 1-5 should all work at or above this compression ratio. Above image is generated using level 1 (very similar to original)
Limit of Level 2 (Compression Ratio 11% - 71.6KB)
Level 2-5 should all work at or above this compression ratio. Above image is generated using level 2 (very similar to original with noticeable distortion)
Limit of Level 3 (Compression Ratio 8.8% - 57.3KB)
Level 3-5 should all work at or above this compression ratio. Above image is generated using level 3
Level 3 should be safe for most compressions by social apps (reduced size image), including Messengers, WeChat etc.
Limit of Level 4 (Compression Ratio 5.4% - 35.0KB)
Level 4-5 should all work at or above this compression ratio. Above image is generated using level 4
Limit of Level 5 (Compression Ratio 2.7% - 17.7KB)
Level 5 should work at or above this compression ratio. Above image is generated using level 5.
Partial Decryption of level 5 (Compression Ratio 1.4% - 9.4KB)
At compression ratio 1.4% (< 10KB), the level 5 steganography (above image) still recovers most of the data
你好,世界A
HELLO WOZLD!
¡HOLA MUNDO!
مرحبا بالعدلم!
BONJOUR LE OONDE!
こんにちね世畍!
ПРP鐒ѕЦ ИИР!
Other Robustness for DCT
Robust to Photo Editing
Simple photo editing (brilliance, exposure etc.) applies most changes to lowest frequency band. Level 1-3 DCT does not apply to lowest frequency band so they are pretty robust. Level 4-5 is not robust to photo editing!
I used the same maple image, wrote same data with level 3 and let iPhone photo app auto enhance the result image (basically adjusts on brilliance, exposure and highlights). Then sent to my computer via WeChat reduced size image. I can successfully read data in my computer. Below is the image I received at my computer (73.6KB) (you can download and decrypt it on http://stego.js.org. Choose level 3 and leave password cell blank):
Robust to Photo Stylizing
Stylizing changes images much more compared to simple editing. But luckily, level 3 is robust to most iPhone stylization (e.g. vivid, dramatic etc.). However, to fully recover message, I can't compress much after stylization.
Below is the same maple image first stego data using level 3, no password and then stylized by iPhone photo app with vivid warm theme.
Robust to Resizing
To achieve robustness on resizing, you must know at which size the message is written in because the data order is related to image size. That said, you should resize the image so that it matches the size when the data is written in before decryption.
After data is written in, scaling up will never cause issue because all information is preserved. Level 1-3 should be robust if new image is more than 0.25X of original image. Level 4-5 should be robust if new image is more than 0.0625X of original image. If compression is involved during resizing, the robustness is weakened. (Level 5 is more robust than level 4 to resizing as downsampling is used)
Below is the maple image first stego data using level 5, no password, then scale down to 0.0625X and then scale up 16X for decryption. The decrypt results are correct and complete.
Coding Example
Refer to example/
folder.
Copyright
Jeffery Zhao
License: GNU AGPL v3.0 or later (GNU GPL v3.0 license allowed for non-commercial purposes but derived / re-distributed work must apply same license as this project or pure AGPL v3.0)
The copyright for Crypto-JS is reserved by its authors.