diff --git a/Makefile b/Makefile
index 6f0ba17..1ddd030 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,7 @@ install-share:
install -pDm755 ssh_filter_btrbk.sh "$(DESTDIR)$(SCRIPTDIR)/ssh_filter_btrbk.sh"
install -pDm755 contrib/cron/btrbk-mail "$(DESTDIR)$(SCRIPTDIR)/btrbk-mail"
install -pDm755 contrib/migration/raw_suffix2sidecar "$(DESTDIR)$(SCRIPTDIR)/raw_suffix2sidecar"
+ install -pDm755 contrib/crypt/kdf_pbkdf2.py "$(DESTDIR)$(SCRIPTDIR)/kdf_pbkdf2.py"
install-man:
@echo 'installing manpages...'
diff --git a/btrbk b/btrbk
index ba91849..33f8988 100755
--- a/btrbk
+++ b/btrbk
@@ -114,6 +114,10 @@ my %config_options = (
openssl_iv_size => { default => undef, accept => [ "no", accept_numeric => 1 ] },
openssl_keyfile => { default => undef, accept_file => { absolute => 1 } },
+ kdf_backend => { default => undef, accept_file => { absolute => 1 } },
+ kdf_keysize => { default => "32", accept_numeric => 1 },
+ kdf_keygen => { default => "once", accept => [ "once", "each" ] },
+
group => { default => undef, accept_regexp => qr/^$group_match(\s*,\s*$group_match)*$/, split => qr/\s*,\s*/ },
backend => { default => "btrfs-progs", accept => [ "btrfs-progs", "btrfs-progs-btrbk", "btrfs-progs-sudo" ] },
@@ -236,6 +240,7 @@ my %raw_info_sort = (
encrypt => 11,
cipher => 12,
iv => 13,
+ # kdf_* (generated by kdf_backend)
);
my %url_cache; # map URL to btr_tree node
@@ -261,6 +266,8 @@ my @transaction_log;
my %config_override;
my @tm_now; # current localtime ( sec, min, hour, mday, mon, year, wday, yday, isdst )
my %warn_once;
+my %kdf_vars;
+my $kdf_session_key;
$SIG{__DIE__} = sub {
@@ -1433,9 +1440,63 @@ sub btrfs_send_to_file($$$;$$)
$raw_info{iv} = $iv;
}
+ my $encrypt_key;
+ if($encrypt->{keyfile}) {
+ if($encrypt->{kdf_backend}) {
+ WARN "Both openssl_keyfile and kdf_backend are configured, ignoring kdf_backend!";
+ }
+ $encrypt_key = '$(cat ' . $encrypt->{keyfile} . ')';
+ }
+ elsif($encrypt->{kdf_backend}) {
+ if($encrypt->{kdf_keygen_each}) {
+ $kdf_session_key = undef;
+ %kdf_vars = ();
+ }
+ if($kdf_session_key) {
+ INFO "Reusing session key for: $vol_received->{PRINT}";
+ }
+ else {
+ # run kdf backend, set session key and vars
+ DEBUG "Generating session key for: $vol_received->{PRINT}";
+ my $kdf_backend_name = $encrypt->{kdf_backend};
+ $kdf_backend_name =~ s/^.*\///;
+
+ print STDOUT "\nGenerate session key for " . ($encrypt->{kdf_keygen_each} ? "\"$vol_received->{PRINT}\"" : "all raw backups") . ":\n";
+ my $kdf_values = run_cmd(cmd => [ $encrypt->{kdf_backend}, $encrypt->{kdf_keysize} ],
+ non_destructive => 1,
+ name => $kdf_backend_name
+ );
+ return undef unless(defined($kdf_values));
+ foreach(split("\n", $kdf_values)) {
+ chomp;
+ next if /^\s*$/; # ignore empty lines
+ if(/^KEY=([0-9a-fA-f]+)/) {
+ $kdf_session_key = $1;
+ }
+ elsif(/^([a-z_]+)=(.*)/) {
+ my $info_key = 'kdf_' . $1;
+ my $info_val = $2;
+ DEBUG "Adding raw_info from kdf_backend: $info_key=$info_val";
+ $kdf_vars{$info_key} = $info_val;
+ }
+ else {
+ ERROR "Ambiguous line from kdf_backend: $encrypt->{kdf_backend}";
+ return undef;
+ }
+ }
+ unless($kdf_session_key && (length($kdf_session_key) == ($encrypt->{kdf_keysize} * 2))) {
+ ERROR "Ambiguous key value from kdf_backend: $encrypt->{kdf_backend}";
+ return undef;
+ }
+ INFO "Generated session key for: $vol_received->{PRINT}";
+ }
+ $encrypt_key = $kdf_session_key;
+ %raw_info = ( %kdf_vars, %raw_info );
+ }
+
my @openssl_options = (
'-' . $encrypt->{ciphername},
- '-K $(cat ' . $encrypt->{keyfile} . ')',
+ '-K', $encrypt_key,
);
push @openssl_options, ('-iv', $iv) if($iv);
@@ -2852,6 +2913,9 @@ sub config_encrypt_hash($$)
iv_size => config_key($config, "openssl_iv_size"),
ciphername => config_key($config, "openssl_ciphername"),
keyfile => config_key($config, "openssl_keyfile"),
+ kdf_keygen_each => (config_key($config, "kdf_keygen") eq "each"),
+ kdf_backend => config_key($config, "kdf_backend"),
+ kdf_keysize => config_key($config, "kdf_keysize"),
};
}
diff --git a/contrib/crypt/kdf_pbkdf2.py b/contrib/crypt/kdf_pbkdf2.py
new file mode 100755
index 0000000..1f58d86
--- /dev/null
+++ b/contrib/crypt/kdf_pbkdf2.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# kdf_pbkdf2.py - (kdf_backend for btrbk)
+#
+# Copyright (c) 2017 Axel Burri
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# ---------------------------------------------------------------------
+# The official btrbk website is located at:
+# http://digint.ch/btrbk/
+#
+# Author:
+# Axel Burri
+# ---------------------------------------------------------------------
+
+import sys
+import os
+import getpass
+import hashlib
+
+def passprompt():
+ pprompt = lambda: (getpass.getpass("Passphrase: "), getpass.getpass("Retype passphrase: "))
+ p1, p2 = pprompt()
+ while p1 != p2:
+ print("No match, please try again", file=sys.stderr)
+ p1, p2 = pprompt()
+ return p1
+
+if len(sys.argv) <= 1:
+ print("Usage: {} ".format(sys.argv[0]), file=sys.stderr)
+ sys.exit(1)
+
+hash_name = "sha256"
+iterations = 300000
+dklen = int(sys.argv[1])
+salt = os.urandom(16)
+password = passprompt().encode("utf-8")
+
+dk = hashlib.pbkdf2_hmac(hash_name=hash_name, password=password, salt=salt, iterations=iterations, dklen=dklen)
+
+salt_hex = "".join(["{:02x}".format(x) for x in salt])
+dk_hex = "".join(["{:02x}".format(x) for x in dk])
+
+print("KEY=" + dk_hex);
+print("algoritm=pbkdf2_hmac");
+print("hash_name=" + hash_name);
+print("salt=" + salt_hex);
+print("iterations=" + str(iterations));
diff --git a/doc/btrbk.conf.5 b/doc/btrbk.conf.5
index 10414b4..daeb7c2 100644
--- a/doc/btrbk.conf.5
+++ b/doc/btrbk.conf.5
@@ -456,6 +456,12 @@ openssl_ciphername (defaults to "aes-256-cbc")
openssl_iv_size |no (depends on selected cipher)
.PP
openssl_keyfile |no
+.PP
+kdf_backend |no
+.PP
+kdf_keysize (defaults to "32")
+.PP
+kdf_keygen once|each (defaults to "once")
.RE
.PD
.PP