zindosteg 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/zindosteg/aes.cpp +73 -0
- data/ext/zindosteg/aes.h +36 -0
- data/ext/zindosteg/bmp.cpp +100 -0
- data/ext/zindosteg/bmp.h +39 -0
- data/ext/zindosteg/device.cpp +244 -0
- data/ext/zindosteg/device.h +69 -0
- data/ext/zindosteg/extconf.rb +10 -0
- data/ext/zindosteg/file_utils.h +36 -0
- data/ext/zindosteg/hmac.h +82 -0
- data/ext/zindosteg/jpeg.cpp +111 -0
- data/ext/zindosteg/jpeg.h +39 -0
- data/ext/zindosteg/jpeg_helpers.cpp +149 -0
- data/ext/zindosteg/jpeg_helpers.h +54 -0
- data/ext/zindosteg/key_generator.cpp +36 -0
- data/ext/zindosteg/key_generator.h +23 -0
- data/ext/zindosteg/loader.cpp +72 -0
- data/ext/zindosteg/permutator.cpp +129 -0
- data/ext/zindosteg/permutator.h +39 -0
- data/ext/zindosteg/png_provider.cpp +304 -0
- data/ext/zindosteg/png_provider.h +87 -0
- data/ext/zindosteg/provider.h +40 -0
- data/ext/zindosteg/steg_defs.h +24 -0
- data/ext/zindosteg/steg_endian.h +168 -0
- data/ext/zindosteg/zindosteg.cpp +704 -0
- data/lib/zindosteg/version.rb +3 -0
- data/lib/zindosteg.rb +18 -0
- data/zindosteg.gemspec +28 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01ac48210a386fca7fec50d18a609ced3f2122f0087f1fc6ead5a14970e89968
|
4
|
+
data.tar.gz: 4d11432c88bb155006a58f9f9dda92d292afb7dd5a4b671cf086b5a54467cc69
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '06884588dc026719a0e8f86774075432e18a459f7aa586677499bfcce31d5664705e1d06eeab09f52ddf4f8126a50e15fc26acda500c852309d7532ae87285d9'
|
7
|
+
data.tar.gz: c860ce6d6f4c160fcf6d5b02066b661777fd41abde8189b5affc9a0b8eef3785e8a608f1a09079001ce3d94d2bb20d346ab92557d7836ca25cd3d66003b4ba24
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Nephi Allred
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Zindosteg
|
2
|
+
|
3
|
+
Steganography is the art and science of hiding data inside another set of data. The hidden data is known as the "payload", and could be any kind of data. The data that holds the hidden data is called the "carrier" and is normally an image, sound, or video file. The goal is to modify the carrier file in such a way that to human eyes it appears the same as before, but the subtle differences actually encode bits (0s and 1s) that can be reassembled into the payload.
|
4
|
+
|
5
|
+
This gem is a Ruby interface to the Zindosteg C++ library, which uses a variant of the F5 data hiding technique.
|
6
|
+
|
7
|
+
## Features
|
8
|
+
* Encryption: The payload is encrypted with AES-256-CTR using a key derived from the password using the carrier file as salt.
|
9
|
+
* Data integrity: In order to protect against accidental corruption or intentional tampering, the payload is stored with an HMAC.
|
10
|
+
* Scattering: Instead of placing the payload sequentially inside the carrier file, the order of the hidden bits is determined by a pseudo-random number generator seeded with the password.
|
11
|
+
|
12
|
+
## Supported Carrier Types
|
13
|
+
Zindosteg supports JPEG, PNG, and BMP carrier files. PNG files must have at least 8 bit depth, and not be "palette" type. BMP files must be 24-bit.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'zindosteg'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle install
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install zindosteg
|
30
|
+
|
31
|
+
Note: To compile the native extensions, you may need to install JPEG, PNG, and OpenSSL development packages.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
Zindosteg is designed to mimic Ruby's `File` class as closely as possible. The basic idea is that you get a `Zindosteg` instance by "opening" the carrier file with a password. Then you can read and write to the `Zindosteg` object in the same way you normally would to a `File` object, using the same methods (e.g. `read`, `write`, `readlines`, `eof?`, `seek`, `tell`, etc.). You can even pass the `Zindosteg` instance to functions that expect `File` objects and everything should work.
|
35
|
+
|
36
|
+
### Examples
|
37
|
+
```
|
38
|
+
# Open a new carrier for writing (overwriting any existing payload)
|
39
|
+
file = ::Zindosteg::File.open("carrier.jpeg", "secretpassword", "w")
|
40
|
+
|
41
|
+
# Write the payload to it
|
42
|
+
file.write("This is my secret payload.")
|
43
|
+
|
44
|
+
# Close to finalize
|
45
|
+
file.close
|
46
|
+
|
47
|
+
# Open an existing carrier file for reading
|
48
|
+
file = ::Zindosteg::File.open("carrier.jpeg", "secretpassword", "r")
|
49
|
+
|
50
|
+
# The mode parameter is "r" by default so this is equivalent to the above:
|
51
|
+
file = ::Zindosteg::File.open("carrier.jpeg", "secretpassword")
|
52
|
+
|
53
|
+
# Read the payload
|
54
|
+
file.read
|
55
|
+
|
56
|
+
# Or you could read it as an array of lines.
|
57
|
+
file.readlines
|
58
|
+
|
59
|
+
# Use all the regular File methods as you would normally
|
60
|
+
file.seek(0)
|
61
|
+
file.tell
|
62
|
+
file.size
|
63
|
+
file.eof?
|
64
|
+
# etc
|
65
|
+
|
66
|
+
# Use the `capacity` method to see the maximum number of bytes that the carrier file can hide.
|
67
|
+
file.capacity
|
68
|
+
|
69
|
+
# All the standard modes for opening files are supported:
|
70
|
+
file = ::Zindosteg::File.open("carrier.jpeg", "secretpassword", "w+") # Opens for reading and writing, truncating any existing payload
|
71
|
+
|
72
|
+
file = ::Zindosteg::File.open("carrier.jpeg", "secretpassword", "a") # Opens for appending
|
73
|
+
|
74
|
+
# etc
|
75
|
+
|
76
|
+
# If you open for reading (or appending) and either (1) an incorrect password is given, or (2) there is no existing payload, or (3) the carrier file has been corrupted or tampered with, then a exception will be thrown.
|
77
|
+
|
78
|
+
```
|
79
|
+
|
80
|
+
## Development
|
81
|
+
|
82
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
83
|
+
|
84
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zindorsky/zindosteg-ruby.
|
89
|
+
|
90
|
+
## License
|
91
|
+
|
92
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "zindosteg"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#include "./aes.h"
|
2
|
+
#include <string.h>
|
3
|
+
#include <algorithm>
|
4
|
+
#include "steg_endian.h"
|
5
|
+
|
6
|
+
namespace zindorsky {
|
7
|
+
namespace crypto {
|
8
|
+
|
9
|
+
aes::aes(byte const* key, int keysize) { rekey(key,keysize); }
|
10
|
+
void aes::encrypt(byte const* in, byte * out) const { AES_encrypt(in,out,&ekey_); }
|
11
|
+
void aes::decrypt(byte const* in, byte * out) const { AES_decrypt(in,out,&dkey_); }
|
12
|
+
void aes::rekey(byte const* key, int keysize) { AES_set_encrypt_key(key,keysize*8,&ekey_); AES_set_decrypt_key(key,keysize*8,&dkey_); }
|
13
|
+
|
14
|
+
namespace {
|
15
|
+
|
16
|
+
void add128(byte * a, int p)
|
17
|
+
{
|
18
|
+
std::int64_t a0,a1;
|
19
|
+
endian::read_be(a+8,a0);
|
20
|
+
a1 = a0+p;
|
21
|
+
endian::write_be(a1,a+8);
|
22
|
+
if( (p<0 && a1>a0) || (p>0 && a1<a0) ) {
|
23
|
+
endian::read_be(a,a0);
|
24
|
+
endian::write_be(a0+(p<0?-1:1),a);
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
} //namespace
|
29
|
+
|
30
|
+
aes_ctr_mode::aes_ctr_mode(byte const* key, int keysize, byte const* iv)
|
31
|
+
: key_(key,keysize)
|
32
|
+
, pos_(0)
|
33
|
+
, buffpos_(0)
|
34
|
+
{
|
35
|
+
memcpy(iv_,iv,sizeof(iv_));
|
36
|
+
key_.encrypt(iv_,buff_);
|
37
|
+
}
|
38
|
+
|
39
|
+
void aes_ctr_mode::crypt(void const* inv, void * outv, size_t length)
|
40
|
+
{
|
41
|
+
byte const* in = static_cast<byte const*>(inv);
|
42
|
+
byte * out = static_cast<byte *>(outv);
|
43
|
+
while(length > 0) {
|
44
|
+
size_t todo = std::min(length, 16-buffpos_);
|
45
|
+
for(size_t i=0; i<todo; ++i) {
|
46
|
+
*out++ = *in++ ^ buff_[buffpos_++];
|
47
|
+
}
|
48
|
+
length -= todo;
|
49
|
+
pos_ += todo;
|
50
|
+
if(buffpos_ >= 16) {
|
51
|
+
for(int i=15; i>=0; --i) {
|
52
|
+
if( ++iv_[i] != 0 ) {
|
53
|
+
break;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
buffpos_ = 0;
|
57
|
+
key_.encrypt(iv_,buff_);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
void aes_ctr_mode::seek(std::streampos pos)
|
63
|
+
{
|
64
|
+
auto block = pos_/16, newblock = pos/16;
|
65
|
+
buffpos_ = static_cast<size_t>(pos)%16;
|
66
|
+
if(block != newblock) {
|
67
|
+
add128(iv_,static_cast<int>(newblock-block));
|
68
|
+
key_.encrypt(iv_,buff_);
|
69
|
+
}
|
70
|
+
pos_ = pos;
|
71
|
+
}
|
72
|
+
|
73
|
+
}} //namespace zindorsky::crypto
|
data/ext/zindosteg/aes.h
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "steg_defs.h"
|
4
|
+
#include <openssl/aes.h>
|
5
|
+
#include <ios>
|
6
|
+
|
7
|
+
namespace zindorsky {
|
8
|
+
namespace crypto {
|
9
|
+
|
10
|
+
class aes {
|
11
|
+
public:
|
12
|
+
aes(byte const* key, int keysize);
|
13
|
+
void encrypt(byte const* in, byte * out) const;
|
14
|
+
void decrypt(byte const* in, byte * out) const;
|
15
|
+
void rekey(byte const* key, int keysize);
|
16
|
+
|
17
|
+
private:
|
18
|
+
AES_KEY ekey_;
|
19
|
+
AES_KEY dkey_;
|
20
|
+
};
|
21
|
+
|
22
|
+
class aes_ctr_mode {
|
23
|
+
public:
|
24
|
+
aes_ctr_mode(byte const* key, int keysize, byte const* iv);
|
25
|
+
void crypt(void const* in, void * out, size_t length);
|
26
|
+
void seek(std::streampos pos);
|
27
|
+
std::streampos tell() const { return pos_; }
|
28
|
+
|
29
|
+
private:
|
30
|
+
aes key_;
|
31
|
+
byte iv_[16], buff_[16];
|
32
|
+
std::streampos pos_;
|
33
|
+
size_t buffpos_;
|
34
|
+
};
|
35
|
+
|
36
|
+
}} //namespace zindorsky::steganography
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#include "bmp.h"
|
2
|
+
#include "steg_endian.h"
|
3
|
+
#include "file_utils.h"
|
4
|
+
#include <cstdint>
|
5
|
+
|
6
|
+
namespace zindorsky {
|
7
|
+
namespace steganography {
|
8
|
+
|
9
|
+
bmp_provider::bmp_provider( filesystem::path const& filename )
|
10
|
+
: bmp_provider( utils::load_from_file(filename) )
|
11
|
+
{
|
12
|
+
}
|
13
|
+
|
14
|
+
bmp_provider::bmp_provider(byte const* data, size_t size)
|
15
|
+
: bmp_provider( byte_vector(data, data+size) )
|
16
|
+
{
|
17
|
+
}
|
18
|
+
|
19
|
+
bmp_provider::bmp_provider(byte_vector const& data)
|
20
|
+
: bmp_provider(data.data(), data.size())
|
21
|
+
{
|
22
|
+
}
|
23
|
+
|
24
|
+
bmp_provider::bmp_provider(byte_vector && data)
|
25
|
+
: file_(std::move(data))
|
26
|
+
{
|
27
|
+
if (file_.size() < 54) {
|
28
|
+
throw invalid_carrier();
|
29
|
+
}
|
30
|
+
|
31
|
+
byte const* header = &file_[0];
|
32
|
+
|
33
|
+
int bits_per_pixel = (int(header[29])<<8) | header[28];
|
34
|
+
//only 24-bit BMPs for now (others use a palette, which makes steganography more difficult)
|
35
|
+
if( bits_per_pixel != 24 ) {
|
36
|
+
throw std::runtime_error("unsupported BMP format");
|
37
|
+
}
|
38
|
+
|
39
|
+
uint32_t data_offset, col_count, row_count;
|
40
|
+
endian::read_le(&header[10], data_offset);
|
41
|
+
endian::read_le(&header[18], col_count);
|
42
|
+
endian::read_le(&header[22], row_count);
|
43
|
+
|
44
|
+
row_sz_ = (col_count*bits_per_pixel + 7)/8;
|
45
|
+
row_count_ = row_count;
|
46
|
+
if( row_sz_ % 4 == 0 ) {
|
47
|
+
slack_sz_ = 0;
|
48
|
+
} else {
|
49
|
+
slack_sz_ = 4 - row_sz_%4;
|
50
|
+
}
|
51
|
+
|
52
|
+
data_ = file_.data() + data_offset;
|
53
|
+
}
|
54
|
+
|
55
|
+
provider_t::index_t bmp_provider::size() const
|
56
|
+
{
|
57
|
+
return row_sz_ * row_count_;
|
58
|
+
}
|
59
|
+
|
60
|
+
byte & bmp_provider::access_indexed_data( index_t index )
|
61
|
+
{
|
62
|
+
return *(data_ + logical_to_physical(index));
|
63
|
+
}
|
64
|
+
|
65
|
+
byte const& bmp_provider::access_indexed_data( index_t index ) const
|
66
|
+
{
|
67
|
+
return *(data_ + logical_to_physical(index));
|
68
|
+
}
|
69
|
+
|
70
|
+
byte_vector bmp_provider::commit_to_memory()
|
71
|
+
{
|
72
|
+
return file_;
|
73
|
+
}
|
74
|
+
|
75
|
+
void bmp_provider::commit_to_file(filesystem::path const& file)
|
76
|
+
{
|
77
|
+
utils::save_to_file(file, file_);
|
78
|
+
}
|
79
|
+
|
80
|
+
byte_vector bmp_provider::salt() const
|
81
|
+
{
|
82
|
+
byte salt[8]={0};
|
83
|
+
for(std::size_t i=0; i<row_count_; ++i) {
|
84
|
+
salt[ i%sizeof(salt) ] += access_indexed_data(i*row_sz_ + i%row_sz_)>>1;
|
85
|
+
}
|
86
|
+
|
87
|
+
return byte_vector(salt,salt+sizeof(salt));
|
88
|
+
}
|
89
|
+
|
90
|
+
std::size_t bmp_provider::logical_to_physical( provider_t::index_t index ) const
|
91
|
+
{
|
92
|
+
if( slack_sz_ == 0 ) {
|
93
|
+
return static_cast<std::size_t>(index);
|
94
|
+
}
|
95
|
+
std::size_t row = static_cast<std::size_t>(index / row_sz_), col = static_cast<std::size_t>(index % row_sz_);
|
96
|
+
return row*(row_sz_+slack_sz_) + col;
|
97
|
+
}
|
98
|
+
|
99
|
+
}} //namespace zindorsky::steganography
|
100
|
+
|
data/ext/zindosteg/bmp.h
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include "provider.h"
|
4
|
+
#include <vector>
|
5
|
+
|
6
|
+
namespace zindorsky {
|
7
|
+
namespace steganography {
|
8
|
+
|
9
|
+
class bmp_provider : public provider_t {
|
10
|
+
public:
|
11
|
+
explicit bmp_provider( filesystem::path const& filename );
|
12
|
+
bmp_provider(byte const* data, size_t size);
|
13
|
+
explicit bmp_provider(byte_vector const& data);
|
14
|
+
explicit bmp_provider(byte_vector && data);
|
15
|
+
//Copyable
|
16
|
+
bmp_provider(bmp_provider const&) = default;
|
17
|
+
bmp_provider & operator = (bmp_provider const&) = default;
|
18
|
+
//Movable
|
19
|
+
bmp_provider(bmp_provider &&) = default;
|
20
|
+
bmp_provider & operator = (bmp_provider &&) = default;
|
21
|
+
|
22
|
+
static std::string format() { return "BMP"; }
|
23
|
+
|
24
|
+
virtual index_t size() const override;
|
25
|
+
virtual byte & access_indexed_data( index_t index ) override;
|
26
|
+
virtual byte const& access_indexed_data( index_t index ) const override;
|
27
|
+
virtual byte_vector commit_to_memory() override;
|
28
|
+
virtual void commit_to_file(filesystem::path const& file) override;
|
29
|
+
virtual byte_vector salt() const override;
|
30
|
+
|
31
|
+
private:
|
32
|
+
byte_vector file_;
|
33
|
+
byte *data_;
|
34
|
+
std::size_t row_sz_, row_count_, slack_sz_;
|
35
|
+
|
36
|
+
std::size_t logical_to_physical( index_t index ) const;
|
37
|
+
};
|
38
|
+
|
39
|
+
}} //namespace zindorsky::steganography
|
@@ -0,0 +1,244 @@
|
|
1
|
+
#include "device.h"
|
2
|
+
#include <cassert>
|
3
|
+
#include "steg_endian.h"
|
4
|
+
|
5
|
+
namespace zindorsky {
|
6
|
+
namespace steganography {
|
7
|
+
|
8
|
+
enum { max_length_sz = 9, nybble_span = 15, byte_span = nybble_span*2, };
|
9
|
+
|
10
|
+
device_t::device_t( filesystem::path const& carrier_file, std::string const& password, bool open_existing_payload, bool throw_on_open_existing_fail )
|
11
|
+
: device_t( provider_t::load(carrier_file), password, open_existing_payload, throw_on_open_existing_fail )
|
12
|
+
{
|
13
|
+
carrier_file_ = carrier_file;
|
14
|
+
}
|
15
|
+
|
16
|
+
device_t::device_t( std::unique_ptr<provider_t> provider, std::string const& password, bool open_existing_payload, bool throw_on_open_existing_fail )
|
17
|
+
: provider_( std::move(provider) )
|
18
|
+
, shuffler_(provider_->size() / nybble_span, crypto::key_generator(password,provider_->salt()))
|
19
|
+
, max_sz_( provider_->size() / byte_span - max_length_sz )
|
20
|
+
, payload_sz_( 0 )
|
21
|
+
, pos_(0)
|
22
|
+
, dirty_(false)
|
23
|
+
{
|
24
|
+
if (!provider_) {
|
25
|
+
throw invalid_carrier();
|
26
|
+
}
|
27
|
+
|
28
|
+
if( max_sz_ <= 0 ) {
|
29
|
+
throw payload_extraction_error();
|
30
|
+
}
|
31
|
+
if( open_existing_payload ) {
|
32
|
+
payload_sz_ = read_payload_length(throw_on_open_existing_fail);
|
33
|
+
}
|
34
|
+
if( payload_sz_ > max_sz_ ) {
|
35
|
+
if (throw_on_open_existing_fail) {
|
36
|
+
throw payload_extraction_error();
|
37
|
+
} else {
|
38
|
+
payload_sz_ = 0;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
std::streamsize device_t::read(char * s, std::streamsize n)
|
44
|
+
{
|
45
|
+
if(!s || n<=0) {
|
46
|
+
return 0;
|
47
|
+
}
|
48
|
+
if( pos_ >= payload_sz_ ) {
|
49
|
+
return std::char_traits<char_type>::eof();
|
50
|
+
}
|
51
|
+
std::streamsize r=0;
|
52
|
+
while(n > 0 && pos_ < payload_sz_) {
|
53
|
+
*s = static_cast<char>(get_byte(pos_));
|
54
|
+
++s;
|
55
|
+
pos_ += 1;
|
56
|
+
++r;
|
57
|
+
--n;
|
58
|
+
}
|
59
|
+
return r;
|
60
|
+
}
|
61
|
+
|
62
|
+
std::streamsize device_t::write(char const* s, std::streamsize n)
|
63
|
+
{
|
64
|
+
if(!s || n<=0) {
|
65
|
+
return 0;
|
66
|
+
}
|
67
|
+
if( pos_ >= max_sz_ ) {
|
68
|
+
return std::char_traits<char_type>::eof();
|
69
|
+
}
|
70
|
+
std::streamsize r=0;
|
71
|
+
while(n > 0 && pos_ < max_sz_) {
|
72
|
+
byte b = static_cast<byte>(*s);
|
73
|
+
put_byte(b,pos_);
|
74
|
+
++s;
|
75
|
+
pos_ += 1;
|
76
|
+
++r;
|
77
|
+
--n;
|
78
|
+
}
|
79
|
+
if( pos_ > payload_sz_ ) {
|
80
|
+
payload_sz_ = pos_;
|
81
|
+
dirty_ = true;
|
82
|
+
}
|
83
|
+
return r;
|
84
|
+
}
|
85
|
+
|
86
|
+
std::streampos device_t::seek(std::streamoff off, std::ios::seekdir way)
|
87
|
+
{
|
88
|
+
std::streampos newpos;
|
89
|
+
switch(way) {
|
90
|
+
case std::ios::cur: newpos = pos_; break;
|
91
|
+
case std::ios::end: newpos = payload_sz_; break;
|
92
|
+
default: newpos = 0; break;
|
93
|
+
}
|
94
|
+
newpos += off;
|
95
|
+
if( newpos < 0 ) {
|
96
|
+
throw std::ios::failure("underseek");
|
97
|
+
}
|
98
|
+
if( newpos > max_sz_ ) {
|
99
|
+
newpos = max_sz_;
|
100
|
+
}
|
101
|
+
return pos_ = newpos;
|
102
|
+
}
|
103
|
+
|
104
|
+
void device_t::close()
|
105
|
+
{
|
106
|
+
if (dirty_ && !carrier_file_.empty()) {
|
107
|
+
write_to_file(carrier_file_);
|
108
|
+
dirty_ = false;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
void device_t::flush()
|
113
|
+
{
|
114
|
+
if (dirty_ && !carrier_file_.empty()) {
|
115
|
+
write_to_file(carrier_file_);
|
116
|
+
dirty_ = false;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
void device_t::write_to_file(filesystem::path const& outfile)
|
121
|
+
{
|
122
|
+
if (dirty_) {
|
123
|
+
write_payload_length();
|
124
|
+
}
|
125
|
+
provider_->commit_to_file(outfile);
|
126
|
+
dirty_ = false;
|
127
|
+
}
|
128
|
+
|
129
|
+
byte_vector device_t::write_to_memory()
|
130
|
+
{
|
131
|
+
if (dirty_) {
|
132
|
+
write_payload_length();
|
133
|
+
}
|
134
|
+
dirty_ = false;
|
135
|
+
return provider_->commit_to_memory();
|
136
|
+
}
|
137
|
+
|
138
|
+
byte device_t::get_byte(std::streampos const& pos, provider_t::index_t * lo_start, provider_t::index_t * hi_start) const
|
139
|
+
{
|
140
|
+
assert( pos < max_sz_ + max_length_sz );
|
141
|
+
|
142
|
+
provider_t::index_t index = shuffler_[pos*2]*nybble_span;
|
143
|
+
if(lo_start) { *lo_start = index; }
|
144
|
+
|
145
|
+
byte cl=0;
|
146
|
+
for(byte i=1; i<=nybble_span; ++i) {
|
147
|
+
if( provider_->access_indexed_data( index++ ) & 1 ) {
|
148
|
+
cl ^= i;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
index = shuffler_[pos*2+1]*nybble_span;
|
153
|
+
if(hi_start) { *hi_start = index; }
|
154
|
+
|
155
|
+
byte ch=0;
|
156
|
+
for(byte i=1; i<=nybble_span; ++i) {
|
157
|
+
if( provider_->access_indexed_data( index++ ) & 1 ) {
|
158
|
+
ch ^= i;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
return (ch<<4)|cl;
|
162
|
+
}
|
163
|
+
|
164
|
+
void device_t::put_byte(byte b, std::streampos const& pos)
|
165
|
+
{
|
166
|
+
assert( pos < max_sz_ + max_length_sz );
|
167
|
+
|
168
|
+
provider_t::index_t lo_start, hi_start;
|
169
|
+
byte c = get_byte(pos,&lo_start,&hi_start);
|
170
|
+
byte cl = c&15, ch = c>>4, bl = b&15, bh = b>>4;
|
171
|
+
if(bl != cl) {
|
172
|
+
provider_->access_indexed_data( lo_start + (bl^cl) - 1 ) ^= 1;
|
173
|
+
dirty_ = true;
|
174
|
+
}
|
175
|
+
if(bh != ch) {
|
176
|
+
provider_->access_indexed_data( hi_start + (bh^ch) - 1 ) ^= 1;
|
177
|
+
dirty_ = true;
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
std::streamsize device_t::truncate()
|
182
|
+
{
|
183
|
+
if(payload_sz_ != pos_) {
|
184
|
+
dirty_ = true;
|
185
|
+
}
|
186
|
+
return payload_sz_ = pos_;
|
187
|
+
}
|
188
|
+
|
189
|
+
std::streamsize device_t::read_payload_length(bool throw_on_fail) const
|
190
|
+
{
|
191
|
+
std::streampos pos = max_sz_+max_length_sz-1;
|
192
|
+
std::streamsize sz = 0;
|
193
|
+
unsigned int shift=0;
|
194
|
+
byte b;
|
195
|
+
do {
|
196
|
+
if(shift+7 > sizeof(std::streampos)*8) {
|
197
|
+
if (throw_on_fail) {
|
198
|
+
throw payload_extraction_error();
|
199
|
+
} else {
|
200
|
+
return 0;
|
201
|
+
}
|
202
|
+
}
|
203
|
+
b = get_byte(pos);
|
204
|
+
pos -= 1;
|
205
|
+
sz |= static_cast<std::streampos>(b&0x7f)<<shift;
|
206
|
+
shift += 7;
|
207
|
+
} while(b&0x80);
|
208
|
+
|
209
|
+
return sz;
|
210
|
+
}
|
211
|
+
|
212
|
+
void device_t::write_payload_length()
|
213
|
+
{
|
214
|
+
if (payload_sz_ < 0) {
|
215
|
+
throw payload_extraction_error();
|
216
|
+
}
|
217
|
+
std::streampos pos = max_sz_+max_length_sz-1;
|
218
|
+
std::streamsize sz = payload_sz_;
|
219
|
+
do {
|
220
|
+
byte b = static_cast<byte>( sz&0x7f );
|
221
|
+
sz >>= 7;
|
222
|
+
if( sz > 0 ) {
|
223
|
+
b |= 0x80;
|
224
|
+
}
|
225
|
+
put_byte(b,pos);
|
226
|
+
pos -= 1;
|
227
|
+
} while( sz>0 );
|
228
|
+
}
|
229
|
+
|
230
|
+
byte_vector device_t::salt_for_encryption() const
|
231
|
+
{
|
232
|
+
if (!provider_) {
|
233
|
+
return{};
|
234
|
+
}
|
235
|
+
byte_vector salt = provider_->salt();
|
236
|
+
//Though it's probably fine, for safety, make the salt different than the salt used by the shuffler.
|
237
|
+
if(!salt.empty()) {
|
238
|
+
salt.front() += 1;
|
239
|
+
}
|
240
|
+
return salt;
|
241
|
+
}
|
242
|
+
|
243
|
+
}} //namespace zindorsky::steganography
|
244
|
+
|