public void Extract(Stream input) { int[] coeff; int i, n, k, hash, code; using (HuffmanDecode hd = new HuffmanDecode(input)) { coeff = hd.Decode(); } logger.Info("Permutation starts"); Permutation permutation = new Permutation(coeff.Length, this.random); logger.Info(coeff.Length + " indices shuffled"); // extract length information CalcEmbeddedLength(permutation, coeff); k = (this.extractedFileLength >> 24) % 32; n = (1 << k) - 1; this.extractedFileLength &= 0x007fffff; logger.Info("Length of embedded file: " + extractedFileLength + " bytes"); if (n > 0) { while (true) { hash = 0; code = 1; while (code <= n) { this.pos++; if (this.pos >= coeff.Length) goto leaveContext; this.shuffledIndex = permutation.GetShuffled(this.pos); this.extractedBit = ExtractBit(coeff); if (this.extractedBit == -1) continue; else if (this.extractedBit == 1) hash ^= code; code++; } for (i = 0; i < k; i++) { this.extractedByte |= (hash >> i & 1) << this.availableExtractedBits++; if (this.availableExtractedBits == 8) { WriteExtractedByte(); // check for pending end of embedded data if (this.nBytesExtracted == this.extractedFileLength) goto leaveContext; } } } } else { while (++this.pos < coeff.Length && this.pos < permutation.Length) { this.shuffledIndex = permutation.GetShuffled(this.pos); this.extractedBit = ExtractBit(coeff); if (this.extractedBit == -1) continue; this.extractedByte |= this.extractedBit << this.availableExtractedBits++; if (this.availableExtractedBits == 8) { WriteExtractedByte(); if (this.nBytesExtracted == extractedFileLength) break; } } } leaveContext: ; if (this.nBytesExtracted < this.extractedFileLength) { logger.Warn("Incomplete file: only " + this.nBytesExtracted + " of " + this.extractedFileLength + " bytes extracted"); } }
/// <summary> /// This method controls the compression of the image. Starting at the /// upper left of the image, it compresses 8x8 blocks of data until the /// entire image has been compressed. /// </summary> /// <param name="out"></param> private void WriteCompressedData() { // This initial setting of MinBlockWidth and MinBlockHeight is done to // ensure they start with values larger than will actually be the case. int MinBlockWidth = this.imageWidth % 8 != 0 ? (int)(Math.Floor(this.imageWidth / 8.0) + 1) * 8 : this.imageWidth; int MinBlockHeight = this.imageHeight % 8 != 0 ? (int)(Math.Floor(this.imageHeight / 8.0) + 1) * 8 : this.imageHeight; int comp, shuffledIndex; for (comp = 0; comp < JpegInfo.NumberOfComponents; comp++) { MinBlockWidth = Math.Min(MinBlockWidth, this.JpegObj.BlockWidth[comp]); MinBlockHeight = Math.Min(MinBlockHeight, this.JpegObj.BlockHeight[comp]); } int[] lastDCvalue = new int[JpegInfo.NumberOfComponents]; int[] emptyArray = new int[64]; int[] coeff = GetCoeff(MinBlockWidth, MinBlockHeight); int coeffCount = coeff.Length; int i, j, r, c; int _changed = 0; int _embedded = 0; int _examined = 0; int _expected = 0; int _one = 0; int _large = 0; int _thrown = 0; int _zero = 0; logger.Info("got " + coeffCount + " DCT AC/DC coefficients"); for (i = 0; i < coeffCount; i++) { if (i % 64 == 0) continue; else if (coeff[i] == 1 || coeff[i] == -1) _one++; else if (coeff[i] == 0) _zero++; } _large = coeffCount - _zero - _one - coeffCount / 64; _expected = _large + (int)(0.49 * _one); // logger.Info("one=" + _one); logger.Info("large=" + _large); // logger.Info("expected capacity: " + _expected + " bits"); logger.Info("expected capacity with"); for (i = 1; i < 8; i++) { int usable, changed, n; n = (1 << i) - 1; usable = _expected * i / n - _expected * i / n % n; changed = coeffCount - _zero - coeffCount / 64; changed = changed * i / n - changed * i / n % n; changed = n * changed / (n + 1) / i; // changed = _large - _large % (n + 1); changed = (changed + _one + _one / 2 - _one / (n + 1)) / (n + 1); usable /= 8; if (usable == 0) break; if (i == 1) logger.Info("default"); else logger.Info("(1, " + n + ", " + i + ")"); logger.Info(" code: " + usable + " bytes (efficiency: " + usable * 8 / changed + "." + usable * 80 / changed % 10 + " bits per change)"); } // westfeld if (this.embeddedData != null) { // Now we embed the secret data in the permutated sequence. logger.Info("Permutation starts"); F5Random random = new F5Random(Encoding.ASCII.GetBytes(this.password)); Permutation permutation = new Permutation(coeffCount, random); int nextBitToEmbed = 0; int byteToEmbed = Convert.ToInt32(this.embeddedData.Length); int availableBitsToEmbed = 0; // We start with the length information. Well, // the length information it is more than one // byte, so this first "byte" is 32 bits long. /*try { byteToEmbed = this.embeddedData.available(); } catch (final Exception e) { e.printStackTrace(); }*/ logger.Info("Embedding of " + (byteToEmbed * 8 + 32) + " bits (" + byteToEmbed + "+4 bytes) "); // We use the most significant byte for the 1 of n // code, and reserve one extra bit for future use. if (byteToEmbed > 0x007fffff) { byteToEmbed = 0x007fffff; } // We calculate n now for (i = 1; i < 8; i++) { int usable; this.n = (1 << i) - 1; usable = _expected * i / this.n - _expected * i / this.n % this.n; usable /= 8; if (usable == 0) { break; } if (usable < byteToEmbed + 4) { break; } } int k = i - 1; this.n = (1 << k) - 1; switch (this.n) { case 0: logger.Info("using default code, file will not fit"); this.n++; break; case 1: logger.Info("using default code"); break; default: logger.Info("using (1, " + this.n + ", " + k + ") code"); break; } byteToEmbed |= k << 24; // store k in the status word // Since shuffling cannot hide the distribution, the // distribution of all bits to embed is unified by // adding a pseudo random bit-string. We continue the random // we used for Permutation, initially seeked with password. byteToEmbed ^= random.GetNextByte(); byteToEmbed ^= random.GetNextByte() << 8; byteToEmbed ^= random.GetNextByte() << 16; byteToEmbed ^= random.GetNextByte() << 24; nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed = 31; _embedded++; for (i = 0; i < permutation.Length; i++) { int shuffled_index = permutation.GetShuffled(i); if (shuffled_index % 64 == 0 || coeff[shuffled_index] == 0) continue; var cc = coeff[shuffled_index]; _examined += 1; if (cc > 0 && (cc & 1) != nextBitToEmbed) { coeff[shuffled_index]--; _changed++; } else if (cc < 0 && (cc & 1) == nextBitToEmbed) { coeff[shuffled_index]++; _changed++; } if (coeff[shuffled_index] != 0) { if (availableBitsToEmbed == 0) { if (n > 1 || embeddedData.Available == 1) break; byteToEmbed = embeddedData.Read(); byteToEmbed ^= random.GetNextByte(); availableBitsToEmbed = 8; } nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; _embedded++; } else _thrown++; } if (n > 1) { bool isLastByte = false; FilteredCollection filtered_index = permutation.Filter(coeff, i + 1); while (!isLastByte) { int kBitsToEmbed = 0; for (i = 0; i < k; i++) { if (availableBitsToEmbed == 0) { if (embeddedData.Available == 0) { isLastByte = true; break; } byteToEmbed = embeddedData.Read(); byteToEmbed ^= random.GetNextByte(); availableBitsToEmbed = 8; } nextBitToEmbed = byteToEmbed & 1; byteToEmbed >>= 1; availableBitsToEmbed--; kBitsToEmbed |= nextBitToEmbed << i; _embedded++; } List<int> codeWord = filtered_index.Offer(this.n); int extractedBit; while (true) { int vhash = 0; int count = codeWord.Count; for (i = 0; i < count; i++) { int index = codeWord[i]; extractedBit = coeff[index] > 0 ? coeff[index] & 1 : (1 - (coeff[index] & 1)); if (extractedBit == 1) { vhash ^= i + 1; } } i = vhash ^ kBitsToEmbed; if (i == 0) break; i--; if (coeff[codeWord[i]] < 0) coeff[codeWord[i]]++; else coeff[codeWord[i]]--; _changed++; if (coeff[codeWord[i]] == 0) { _thrown++; codeWord.RemoveAt(i); codeWord.Add(filtered_index.Offer()); } } } } } logger.Info("Starting Huffman Encoding."); shuffledIndex = 0; for (r = 0; r < MinBlockHeight; r++) { for (c = 0; c < MinBlockWidth; c++) { for (comp = 0; comp < JpegInfo.NumberOfComponents; comp++) { for (i = 0; i < JpegObj.VsampFactor[comp]; i++) { for (j = 0; j < JpegObj.HsampFactor[comp]; j++) { Array.Copy(coeff, shuffledIndex, emptyArray, 0, 64); this.huffman.HuffmanBlockEncoder(this.output, emptyArray, lastDCvalue[comp], this.JpegObj.DCtableNumber[comp], this.JpegObj.ACtableNumber[comp]); lastDCvalue[comp] = emptyArray[0]; shuffledIndex += 64; } } } } } this.huffman.FlushBuffer(this.output); }
/// <summary> /// extract length information /// </summary> private void CalcEmbeddedLength(Permutation permutation, int[] coeff) { this.extractedFileLength = 0; this.pos = -1; int i = 0; while (i < 32 && ++this.pos < coeff.Length) { this.shuffledIndex = permutation.GetShuffled(this.pos); this.extractedBit = ExtractBit(coeff); if (this.extractedBit == -1) continue; this.extractedFileLength |= this.extractedBit << i++; } // remove pseudo random pad this.extractedFileLength ^= this.random.GetNextByte(); this.extractedFileLength ^= this.random.GetNextByte() << 8; this.extractedFileLength ^= this.random.GetNextByte() << 16; this.extractedFileLength ^= this.random.GetNextByte() << 24; }