steggy-noencrypt
Version:
a basic steganography library for png files (encryption support removed)
87 lines (67 loc) • 2.62 kB
JavaScript
const { PNG } = require('pngjs')
const { getShasumData } = require('./encryption')
const {
countBytesForNRgbBytes,
isAlphaByte,
isRgbByte,
recombineRgbAndAlpha,
splitRgbAndAlpha,
} = require('./png')
const {
BYTE_SIZE,
LENGTH_BYTES,
SHASUM_BYTES,
} = require('./defaults')
const getLengthData = message => {
const lengthHex = message.length.toString(16)
const lengthBuffer = Buffer.from(lengthHex.length % 2 ? `0${lengthHex}` : lengthHex, 'hex')
const pad = Buffer.alloc(LENGTH_BYTES - lengthBuffer.length)
return Buffer.concat([pad, lengthBuffer], LENGTH_BYTES)
}
const getBit = data => i => {
const byteIndex = Math.floor(i / BYTE_SIZE)
const bitIndex = i % BYTE_SIZE
const byte = data[byteIndex]
const shiftDistance = (BYTE_SIZE - 1) - bitIndex
return (byte >> shiftDistance) % 2
}
const addDataToByte = data => (byte, i) =>
((byte >> 1) << 1) | getBit(data)(i)
const embedData = ([data, bed]) => bed.map(addDataToByte(data))
const store = (imageData, message) => {
const bytesAvailable = imageData.length
const bytesToStore = LENGTH_BYTES + SHASUM_BYTES + message.length
const bytesRequired = countBytesForNRgbBytes(bytesToStore)
if (bytesAvailable < bytesRequired)
throw new Error('Image is not large enough to store message')
const lengthData = getLengthData(message)
const shasumData = getShasumData(message)
const bytesToUse = imageData.slice(0, bytesRequired)
const bytesToLeave = imageData.slice(bytesRequired)
const [ rgb, alpha ] = splitRgbAndAlpha(bytesToUse)
const lengthDataSize = LENGTH_BYTES * BYTE_SIZE
const shasumDataSize = SHASUM_BYTES * BYTE_SIZE
const bytesToUseWithLengthData = rgb.slice(0, lengthDataSize)
const bytesToUseWithShasumData = rgb.slice(lengthDataSize, lengthDataSize + shasumDataSize)
const bytesToUseWithMessageData = rgb.slice(lengthDataSize + shasumDataSize)
const embeddedData = Buffer.concat([
[lengthData, bytesToUseWithLengthData],
[shasumData, bytesToUseWithShasumData],
[message, bytesToUseWithMessageData],
].map(embedData), rgb.length)
const recombined = recombineRgbAndAlpha(embeddedData, alpha)
const adjustedImageData = Buffer.concat([recombined, bytesToLeave], bytesAvailable)
return adjustedImageData
}
const conceal = (image, message, encoding) => {
const messageBuffer = Buffer.isBuffer(message)
? message
: Buffer.from(message, encoding)
const png = PNG.sync.read(image)
const data = store(png.data, messageBuffer)
const adjustedPng = Object.assign({}, png, { data })
return PNG.sync.write(adjustedPng)
}
module.exports = {
conceal,
}