mirror of
https://github.com/borgbackup/borg.git
synced 2025-03-19 18:36:07 +00:00
Improved AES counter storage
This commit is contained in:
parent
5b28d428b7
commit
49744c0457
2 changed files with 42 additions and 22 deletions
|
@ -36,11 +36,9 @@ class Manifest(object):
|
|||
raise ValueError('Invalid manifest version')
|
||||
manifest.archives = m['archives']
|
||||
manifest.config = m['config']
|
||||
key.post_manifest_load(manifest.config)
|
||||
return manifest, key
|
||||
|
||||
def write(self):
|
||||
self.key.pre_manifest_write(self)
|
||||
data = msgpack.packb({
|
||||
'version': 1,
|
||||
'archives': self.archives,
|
||||
|
|
62
darc/key.py
62
darc/key.py
|
@ -59,12 +59,6 @@ class KeyBase(object):
|
|||
def decrypt(self, id, data):
|
||||
pass
|
||||
|
||||
def post_manifest_load(self, config):
|
||||
pass
|
||||
|
||||
def pre_manifest_write(self, manifest):
|
||||
pass
|
||||
|
||||
|
||||
class PlaintextKey(KeyBase):
|
||||
TYPE = PLAINTEXT
|
||||
|
@ -97,13 +91,6 @@ class PlaintextKey(KeyBase):
|
|||
|
||||
class AESKeyBase(KeyBase):
|
||||
|
||||
def post_manifest_load(self, config):
|
||||
iv = bytes_to_long(config['aes_counter']) + 100
|
||||
self.counter = Counter.new(64, initial_value=iv, prefix=PREFIX)
|
||||
|
||||
def pre_manifest_write(self, manifest):
|
||||
manifest.config['aes_counter'] = long_to_bytes(self.counter.next_value(), 8)
|
||||
|
||||
def id_hash(self, data):
|
||||
"""Return HMAC hash using the "id" HMAC key
|
||||
"""
|
||||
|
@ -130,6 +117,12 @@ class AESKeyBase(KeyBase):
|
|||
raise IntegrityError('Chunk id verification failed')
|
||||
return data
|
||||
|
||||
def extract_iv(self, payload):
|
||||
if payload[0] != self.TYPE:
|
||||
raise IntegrityError('Invalid encryption envelope')
|
||||
nonce = bytes_to_long(payload[33:41])
|
||||
return nonce
|
||||
|
||||
def init_from_random_data(self, data):
|
||||
self.enc_key = data[0:32]
|
||||
self.enc_hmac_key = data[32:64]
|
||||
|
@ -177,6 +170,8 @@ class PassphraseKey(AESKeyBase):
|
|||
key.init(store, passphrase)
|
||||
try:
|
||||
key.decrypt(None, manifest_data)
|
||||
iv = key.extract_iv(manifest_data)
|
||||
key.counter = Counter.new(64, initial_value=iv + 1000, prefix=PREFIX)
|
||||
return key
|
||||
except IntegrityError:
|
||||
passphrase = getpass(prompt)
|
||||
|
@ -197,6 +192,8 @@ class KeyfileKey(AESKeyBase):
|
|||
passphrase = os.environ.get('DARC_PASSPHRASE', '')
|
||||
while not key.load(path, passphrase):
|
||||
passphrase = getpass(prompt)
|
||||
iv = key.extract_iv(manifest_data)
|
||||
key.counter = Counter.new(64, initial_value=iv + 1000, prefix=PREFIX)
|
||||
return key
|
||||
|
||||
@classmethod
|
||||
|
@ -260,7 +257,7 @@ class KeyfileKey(AESKeyBase):
|
|||
'store_id': self.store_id,
|
||||
'enc_key': self.enc_key,
|
||||
'enc_hmac_key': self.enc_hmac_key,
|
||||
'id_key': self.enc_key,
|
||||
'id_key': self.id_key,
|
||||
'chunk_seed': self.chunk_seed,
|
||||
}
|
||||
data = self.encrypt_key_file(msgpack.packb(key), passphrase)
|
||||
|
@ -308,14 +305,23 @@ class KeyfileKey(AESKeyBase):
|
|||
|
||||
class KeyTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.tmppath = tempfile.mkdtemp()
|
||||
os.environ['DARC_KEYS_DIR'] = self.tmppath
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmppath)
|
||||
|
||||
class MockStore(object):
|
||||
class _Location(object):
|
||||
orig = '/some/place'
|
||||
|
||||
_location = _Location()
|
||||
id = '\0' * 32
|
||||
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
self.keys_path = os.path.join(self.tmpdir, 'keys')
|
||||
os.mkdir(self.keys_path)
|
||||
os.environ['DARC_KEYS_DIR'] = self.keys_path
|
||||
os.environ['DARC_KEYS_DIR'] = self.tmpdir
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
@ -331,20 +337,36 @@ class KeyTestCase(unittest.TestCase):
|
|||
store = Location(tempfile.mkstemp()[1])
|
||||
os.environ['DARC_PASSPHRASE'] = 'test'
|
||||
key = KeyfileKey.create(self.MockStore(), MockArgs())
|
||||
key = KeyfileKey.detect(self.MockStore(), None)
|
||||
self.assertEqual(bytes_to_long(key.counter()), 1)
|
||||
manifest = key.encrypt('')
|
||||
iv = key.extract_iv(manifest)
|
||||
key2 = KeyfileKey.detect(self.MockStore(), manifest)
|
||||
self.assertEqual(bytes_to_long(key2.counter()), iv + 1000)
|
||||
# Key data sanity check
|
||||
self.assertEqual(len(set([key2.id_key, key2.enc_key, key2.enc_hmac_key])), 3)
|
||||
self.assertEqual(key2.chunk_seed == 0, False)
|
||||
data = 'foo'
|
||||
self.assertEqual(data, key.decrypt(key.id_hash(data), key.encrypt(data)))
|
||||
self.assertEqual(data, key2.decrypt(key.id_hash(data), key.encrypt(data)))
|
||||
|
||||
def test_passphrase(self):
|
||||
os.environ['DARC_PASSPHRASE'] = 'test'
|
||||
key = PassphraseKey.create(self.MockStore(), None)
|
||||
self.assertEqual(bytes_to_long(key.counter()), 1)
|
||||
self.assertEqual(key.id_key.encode('hex'), 'f28e915da78a972786da47fee6c4bd2960a421b9bdbdb35a7942eb82552e9a72')
|
||||
self.assertEqual(key.enc_hmac_key.encode('hex'), '169c6082f209e524ea97e2c75318936f6e93c101b9345942a95491e9ae1738ca')
|
||||
self.assertEqual(key.enc_key.encode('hex'), 'c05dd423843d4dd32a52e4dc07bb11acabe215917fc5cf3a3df6c92b47af79ba')
|
||||
self.assertEqual(key.chunk_seed, -324662077)
|
||||
manifest = key.encrypt('')
|
||||
iv = key.extract_iv(manifest)
|
||||
key2 = PassphraseKey.detect(self.MockStore(), manifest)
|
||||
self.assertEqual(bytes_to_long(key2.counter()), iv + 1000)
|
||||
self.assertEqual(key.id_key, key2.id_key)
|
||||
self.assertEqual(key.enc_hmac_key, key2.enc_hmac_key)
|
||||
self.assertEqual(key.enc_key, key2.enc_key)
|
||||
self.assertEqual(key.chunk_seed, key2.chunk_seed)
|
||||
data = 'foo'
|
||||
self.assertEqual(key.id_hash(data).encode('hex'), '016c27cd40dc8e84f196f3b43a9424e8472897e09f6935d0d3a82fb41664bad7')
|
||||
self.assertEqual(data, key.decrypt(key.id_hash(data), key.encrypt(data)))
|
||||
self.assertEqual(data, key2.decrypt(key2.id_hash(data), key.encrypt(data)))
|
||||
|
||||
|
||||
def suite():
|
||||
|
|
Loading…
Add table
Reference in a new issue