Commit 3ddc62ff authored by Stefan Westerfeld's avatar Stefan Westerfeld

Merge branch 'secure-random'

parents 716b84da 5d1aa2c1
......@@ -46,6 +46,7 @@ AC_DEFUN([AC_FFTW_CHECK],
AC_SNDFILE_REQUIREMENTS
AC_FFTW_CHECK
AM_PATH_LIBGCRYPT
# need c++11 mode
AX_CXX_COMPILE_STDCXX_11(ext)
......
bin_PROGRAMS = audiowmark
COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc
COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc random.hh random.cc
audiowmark_SOURCES = audiowmark.cc wavdata.cc wavdata.hh fft.cc fft.hh $(COMMON_SRC)
audiowmark_LDFLAGS = $(SNDFILE_LIBS) $(FFTW_LIBS)
audiowmark_LDFLAGS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS)
noinst_PROGRAMS = testconvcode
noinst_PROGRAMS = testconvcode testrandom
testconvcode_SOURCES = testconvcode.cc $(COMMON_SRC)
testconvcode_LDFLAGS = $(LIBGCRYPT_LIBS)
testrandom_SOURCES = testrandom.cc $(COMMON_SRC)
testrandom_LDFLAGS = $(LIBGCRYPT_LIBS)
......@@ -8,6 +8,7 @@
#include "wavdata.hh"
#include "utils.hh"
#include "convcode.hh"
#include "random.hh"
#include <assert.h>
......@@ -30,7 +31,7 @@ namespace Params
static bool mix = true;
static bool hard = false; // hard decode bits? (soft decoding is better)
static int block_size = 32; // block size for mix step (non-linear bit storage)
static unsigned int seed = 0;
static int have_key = 0;
static size_t payload_size = 128; // number of payload bits for the watermark
}
......@@ -55,12 +56,16 @@ print_usage()
printf (" * compute bit error rate for decoding with original file\n");
printf (" audiowmark cmp-delta <input_wav> <watermarked_wav> <message_hex>\n");
printf ("\n");
printf (" * generate 128-bit watermarking key, to be used with --key option\n");
printf (" audiowmark gen-key <key_file>\n");
printf ("\n");
printf ("Global options:\n");
printf (" --frame-size frame size (must be power of 2) [%zd]\n", Params::frame_size);
printf (" --frames-per-bit number of frames per bit [%d]\n", Params::frames_per_bit);
printf (" --water-delta set watermarking delta [%.4f]\n", Params::water_delta);
printf (" --pre-scale set scaling used for normalization [%.3f]\n", Params::pre_scale);
printf (" --linear disable non-linear bit storage\n");
printf (" --key <file> load watermarking key from file\n");
}
static bool
......@@ -157,10 +162,16 @@ parse_options (int *argc_p,
{
Params::hard = true;
}
else if (check_arg (argc, argv, &i, "--seed", &opt_arg))
else if (check_arg (argc, argv, &i, "--test-key", &opt_arg))
{
Params::seed = atoi (opt_arg);
Params::have_key++;
Random::set_global_test_key (atoi (opt_arg));
}
else if (check_arg (argc, argv, &i, "--key", &opt_arg))
{
Params::have_key++;
Random::load_global_key (opt_arg);
}
}
/* resort argc/argv */
......@@ -241,49 +252,21 @@ get_frame (const WavData& wav_data, int f, int ch)
return result;
}
std::mt19937_64
init_rng (uint64_t seed)
{
const uint64_t prime = 3126986573;
std::mt19937_64 rng;
rng.seed (seed + prime * Params::seed);
return rng;
}
void
get_up_down (int f, vector<int>& up, vector<int>& down)
{
vector<int> used (Params::frame_size / 2);
std::mt19937_64 rng = init_rng (f); // use per frame random seed, may want to have cryptographically secure algorithm
vector<int> bands_reorder;
for (int i = Params::min_band; i <= Params::max_band; i++)
bands_reorder.push_back (i);
auto choose_bands = [&used, &rng] (vector<int>& bands) {
while (bands.size() < Params::bands_per_frame)
{
int p = rng() % (Params::max_band - Params::min_band) + Params::min_band;
if (!used[p])
{
bands.push_back (p);
used[p] = 1;
}
}
};
choose_bands (up);
choose_bands (down);
}
template<class T> void
gen_shuffle (vector<T>& result, int seed)
{
std::mt19937_64 rng = init_rng (seed); // should use cryptographically secure generator, properly seeded
Random random (f, Random::Stream::up_down); // use per frame random seed
random.shuffle (bands_reorder);
// Fisher–Yates shuffle
for (size_t i = 0; i < result.size() - 1; i++)
assert (2 * Params::bands_per_frame < bands_reorder.size());
for (size_t i = 0; i < Params::bands_per_frame; i++)
{
size_t j = i + rng() % (result.size() - i);
std::swap (result[i], result[j]);
up.push_back (bands_reorder[i]);
down.push_back (bands_reorder[Params::bands_per_frame + i]);
}
}
......@@ -295,7 +278,8 @@ randomize_bit_order (const vector<T>& bit_vec, bool encode)
for (size_t i = 0; i < bit_vec.size(); i++)
order.push_back (i);
gen_shuffle (order, /* seed */ 0);
Random random (/* seed */ 0, Random::Stream::bit_order);
random.shuffle (order);
vector<T> out_bits (bit_vec.size());
for (size_t i = 0; i < bit_vec.size(); i++)
......@@ -330,7 +314,9 @@ gen_mix_entries (int block)
for (size_t i = 0; i < up.size(); i++)
mix_entries.push_back ({ f, up[i], down[i] });
}
gen_shuffle (mix_entries, /* seed */ block);
Random random (/* seed */ block, Random::Stream::mix);
random.shuffle (mix_entries);
return mix_entries;
}
......@@ -893,11 +879,30 @@ get_snr (const string& origfile, const string& wmfile)
return 0;
}
int
gen_key (const string& outfile)
{
FILE *f = fopen (outfile.c_str(), "w");
if (!f)
{
fprintf (stderr, "audiowmark: error writing to file %s\n", outfile.c_str());
return 1;
}
fprintf (f, "# watermarking key for audiowmark\n\nkey %s\n", Random::gen_key().c_str());
fclose (f);
return 0;
}
int
main (int argc, char **argv)
{
parse_options (&argc, &argv);
if (Params::have_key > 1)
{
fprintf (stderr, "audiowmark: watermark key can at most be set once (--key / --test-key option)\n");
return 1;
}
string op = (argc >= 2) ? argv[1] : "";
if (op == "add" && argc == 5)
......@@ -932,9 +937,13 @@ main (int argc, char **argv)
{
return get_watermark_delta (argv[2], argv[3], argv[4]);
}
else if (op == "gen-key" && argc == 3)
{
return gen_key (argv[2]);
}
else
{
fprintf (stderr, "audiowmark: error parsing commandline args\n");
fprintf (stderr, "audiowmark: error parsing commandline args (use audiowmark -h)\n");
return 1;
}
}
......@@ -44,7 +44,7 @@ do
PATTERN=4e1243bd22c66e76c2ba9eddc1f91394
fi
audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --seed $SEED >/dev/null
audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED >/dev/null
if [ "x$TRANSFORM" == "xmp3" ]; then
if [ "x$2" == "x" ]; then
echo "need mp3 bitrate" >&2
......@@ -93,9 +93,9 @@ do
exit 1
fi
# blind decoding
audiowmark cmp ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --seed $SEED
audiowmark cmp ${AWM_FILE}.wav $PATTERN $AWM_PARAMS --test-key $SEED
# decoding with original
# audiowmark cmp-delta "$i" t.wav $PATTERN $AWM_PARAMS --seed $SEED
# audiowmark cmp-delta "$i" t.wav $PATTERN $AWM_PARAMS --test-key $SEED
done
done | grep bit_error_rate | {
if [ "x$AWM_REPORT" == "xber" ]; then
......
#include "random.hh"
#include "utils.hh"
#include <regex>
#include <assert.h>
using std::string;
using std::vector;
using std::regex;
using std::regex_match;
static vector<unsigned char> aes_key (16); // 128 bits
static constexpr auto GCRY_CIPHER = GCRY_CIPHER_AES128;
static void
uint64_to_buffer (uint64_t u,
unsigned char *buffer)
{
/* this has to be endian independent: use big endian order */
buffer[0] = u >> 56;
buffer[1] = u >> 48;
buffer[2] = u >> 40;
buffer[3] = u >> 32;
buffer[4] = u >> 24;
buffer[5] = u >> 16;
buffer[6] = u >> 8;
buffer[7] = u;
}
static uint64_t
uint64_from_buffer (unsigned char *buffer)
{
/* this has to be endian independent: use big endian order */
return (uint64_t (buffer[0]) << 56)
+ (uint64_t (buffer[1]) << 48)
+ (uint64_t (buffer[2]) << 40)
+ (uint64_t (buffer[3]) << 32)
+ (uint64_t (buffer[4]) << 24)
+ (uint64_t (buffer[5]) << 16)
+ (uint64_t (buffer[6]) << 8)
+ buffer[7];
}
static void
print (const string& label, const vector<unsigned char>& data)
{
printf ("%s: ", label.c_str());
for (auto ch : data)
printf ("%02x ", ch);
printf ("\n");
}
Random::Random (uint64_t seed, Stream stream)
{
vector<unsigned char> ctr = get_start_counter (seed, stream);
// print ("CTR", ctr);
gcry_error_t gcry_ret = gcry_cipher_open (&aes_ctr_cipher, GCRY_CIPHER, GCRY_CIPHER_MODE_CTR, 0);
die_on_error ("gcry_cipher_open", gcry_ret);
gcry_ret = gcry_cipher_setkey (aes_ctr_cipher, &aes_key[0], aes_key.size());
die_on_error ("gcry_cipher_setkey", gcry_ret);
gcry_ret = gcry_cipher_setctr (aes_ctr_cipher, &ctr[0], ctr.size());
die_on_error ("gcry_cipher_setctr", gcry_ret);
}
Random::~Random()
{
gcry_cipher_close (aes_ctr_cipher);
}
vector<unsigned char>
Random::get_start_counter (uint64_t seed, Stream stream)
{
gcry_error_t gcry_ret;
gcry_cipher_hd_t cipher_hd;
gcry_ret = gcry_cipher_open (&cipher_hd, GCRY_CIPHER, GCRY_CIPHER_MODE_ECB, 0);
die_on_error ("gcry_cipher_open", gcry_ret);
gcry_ret = gcry_cipher_setkey (cipher_hd, &aes_key[0], aes_key.size());
die_on_error ("gcry_cipher_setkey", gcry_ret);
vector<unsigned char> cipher_text (16);
vector<unsigned char> plain_text (16);
uint64_to_buffer (seed, &plain_text[0]);
plain_text[8] = uint8_t (stream);
// print ("SEED", plain_text);
gcry_ret = gcry_cipher_encrypt (cipher_hd, &cipher_text[0], cipher_text.size(),
&plain_text[0], plain_text.size());
die_on_error ("gcry_cipher_encrypt", gcry_ret);
gcry_cipher_close (cipher_hd);
return cipher_text;
}
void
Random::refill_buffer()
{
const size_t block_size = 256;
unsigned char zeros[block_size] = { 0, };
unsigned char cipher_text[block_size];
gcry_error_t gcry_ret = gcry_cipher_encrypt (aes_ctr_cipher, cipher_text, block_size, zeros, block_size);
die_on_error ("gcry_cipher_encrypt", gcry_ret);
// print ("AES OUT", {cipher_text, cipher_text + block_size});
buffer.clear();
for (size_t i = 0; i < block_size; i += 8)
buffer.push_back (uint64_from_buffer (cipher_text + i));
buffer_pos = 0;
}
void
Random::die_on_error (const char *func, gcry_error_t error)
{
if (error)
{
fprintf (stderr, "%s failed: %s/%s\n", func, gcry_strsource (error), gcry_strerror (error));
exit (1); /* can't recover here */
}
}
void
Random::set_global_test_key (uint64_t key)
{
uint64_to_buffer (key, &aes_key[0]);
}
void
Random::load_global_key (const string& key_file)
{
FILE *f = fopen (key_file.c_str(), "r");
if (!f)
{
fprintf (stderr, "audiowmark: error opening key file: '%s'\n", key_file.c_str());
exit (1);
}
const regex blank_re (R"(\s*(#.*)?[\r\n]+)");
const regex key_re (R"(\s*key\s+([0-9a-f]+)\s*(#.*)?[\r\n]+)");
char buffer[1024];
int line = 1;
int keys = 0;
while (fgets (buffer, 1024, f))
{
string s = buffer;
std::smatch match;
if (regex_match (s, blank_re))
{
/* blank line or comment */
}
else if (regex_match (s, match, key_re))
{
/* line containing aes key */
vector<unsigned char> key = hex_str_to_vec (match[1].str());
if (key.size() != aes_key.size())
{
fprintf (stderr, "audiowmark: wrong key length in key file '%s', line %d\n => required key length is %zd bits\n", key_file.c_str(), line, aes_key.size() * 8);
exit (1);
}
aes_key = key;
keys++;
}
else
{
fprintf (stderr, "audiowmark: parse error in key file '%s', line %d\n", key_file.c_str(), line);
exit (1);
}
line++;
}
fclose (f);
if (keys > 1)
{
fprintf (stderr, "audiowmark: key file '%s' contains more than one key\n", key_file.c_str());
exit (1);
}
if (keys == 0)
{
fprintf (stderr, "audiowmark: key file '%s' contains no key\n", key_file.c_str());
exit (1);
}
}
string
Random::gen_key()
{
vector<unsigned char> key (16);
gcry_randomize (&key[0], 16, /* long term key material strength */ GCRY_VERY_STRONG_RANDOM);
return vec_to_hex_str (key);
}
#ifndef AUDIOWMARK_RANDOM_HH
#define AUDIOWMARK_RANDOM_HH
#include <gcrypt.h>
#include <stdint.h>
#include <vector>
#include <string>
class Random
{
public:
enum class Stream {
up_down = 1,
mix = 2,
bit_order = 3
};
private:
gcry_cipher_hd_t aes_ctr_cipher;
std::vector<uint64_t> buffer;
size_t buffer_pos = 0;
std::vector<unsigned char> get_start_counter (uint64_t seed, Stream stream);
void die_on_error (const char *func, gcry_error_t error);
public:
Random (uint64_t seed, Stream stream);
~Random();
uint64_t
operator()()
{
if (buffer_pos == buffer.size())
refill_buffer();
return buffer[buffer_pos++];
}
void refill_buffer();
template<class T> void
shuffle (std::vector<T>& result)
{
// Fisher–Yates shuffle
for (size_t i = 0; i < result.size(); i++)
{
const uint64_t random_number = (*this)();
size_t j = i + random_number % (result.size() - i);
std::swap (result[i], result[j]);
}
}
static void set_global_test_key (uint64_t seed);
static void load_global_key (const std::string& key_file);
static std::string gen_key();
};
#endif /* AUDIOWMARK_RANDOM_HH */
#include "utils.hh"
#include "random.hh"
#include <sys/time.h>
using std::vector;
using std::string;
static double
gettime()
{
timeval tv;
gettimeofday (&tv, 0);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
int
main (int argc, char **argv)
{
Random rng (0xf00f1234b00b5678U, Random::Stream::bit_order);
for (size_t i = 0; i < 20; i++)
{
uint64_t x = rng();
printf ("%016lx\n", x);
}
uint64_t s = 0;
double t_start = gettime();
size_t runs = 25000000;
for (size_t i = 0; i < runs; i++)
{
s += rng();
}
double t_end = gettime();
printf ("s=%016lx\n\n", s);
printf ("%f Mvalues/sec\n", runs / (t_end - t_start) / 1000000);
}
......@@ -55,3 +55,37 @@ bit_vec_to_str (const vector<int>& bit_vec)
return bit_str;
}
vector<unsigned char>
hex_str_to_vec (const string& str)
{
vector<unsigned char> result;
if ((str.size() % 2) != 0) // even length
return vector<unsigned char>();
for (size_t i = 0; i < str.size() / 2; i++)
{
unsigned char h = from_hex_nibble (str[i * 2]);
unsigned char l = from_hex_nibble (str[i * 2 + 1]);
if (h >= 16 || l >= 16)
return vector<unsigned char>();
result.push_back ((h << 4) + l);
}
return result;
}
string
vec_to_hex_str (const vector<unsigned char>& vec)
{
string s;
for (auto byte : vec)
{
char buffer[256];
sprintf (buffer, "%02x", byte);
s += buffer;
}
return s;
}
......@@ -7,4 +7,7 @@
std::vector<int> bit_str_to_vec (const std::string& bits);
std::string bit_vec_to_str (const std::vector<int>& bit_vec);
std::vector<unsigned char> hex_str_to_vec (const std::string& str);
std::string vec_to_hex_str (const std::vector<unsigned char>& vec);
#endif /* AUDIOWMARK_UTILS_HH */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment