forked from turnkeylinux/tklbam
-
Notifications
You must be signed in to change notification settings - Fork 0
/
keypacket.py
117 lines (88 loc) · 3.31 KB
/
keypacket.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#
# Copyright (c) 2010-2013 Liraz Siri <[email protected]>
#
# This file is part of TKLBAM (TurnKey GNU/Linux BAckup and Migration).
#
# TKLBAM is open source 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.
#
import os
import hashlib
import base64
import struct
from Crypto.Cipher import AES
KEY_VERSION = 1
SALT_LEN = 4
KILO_REPEATS_HASH = 80
KILO_REPEATS_CIPHER = 80
FINGERPRINT_LEN = 6
class Error(Exception):
pass
def _pad(s):
padded_len = ((len(s) + 2 - 1) | 0xF) + 1
padding_len = padded_len - len(s) - 2
return os.urandom(padding_len) + s + struct.pack("!H", len(s))
def _unpad(padded):
len, = struct.unpack("!H", padded[-2:])
return padded[-(2 + len) :-2]
def _repeat(f, input, count):
for x in xrange(count):
input = f(input)
return input
def _cipher_key(passphrase, repeats):
cipher_key = _repeat(lambda k: hashlib.sha256(k).digest(),
passphrase, repeats)
return cipher_key
def _cipher(cipher_key):
return AES.new(cipher_key, mode=AES.MODE_CBC, IV='\0' * 16)
def fmt(secret, passphrase):
salt = os.urandom(SALT_LEN)
if not passphrase:
hash_repeats = cipher_repeats = 1
else:
hash_repeats = KILO_REPEATS_HASH * 1000 + 1
cipher_repeats = KILO_REPEATS_CIPHER * 1000 + 1
cipher_key = _cipher_key(passphrase, hash_repeats)
plaintext = salt + hashlib.sha1(secret).digest() + secret
ciphertext = _repeat(lambda v: _cipher(cipher_key).encrypt(v),
_pad(plaintext), cipher_repeats)
fingerprint = hashlib.sha1(secret).digest()[:FINGERPRINT_LEN]
packet = struct.pack("!BHH", KEY_VERSION,
hash_repeats / 1000,
cipher_repeats / 1000) + fingerprint + ciphertext
return base64.b64encode(packet)
def _parse(packet):
try:
packet = base64.b64decode(packet)
version, khr, kcr = struct.unpack("!BHH", packet[:5])
except (TypeError, struct.error), e:
raise Error("can't parse key packet: " + str(e))
minimum_len = (5 + FINGERPRINT_LEN + 16)
if len(packet) < minimum_len:
raise Error("key packet length (%d) smaller than minimum (%d)" % (len(packet), minimum_len))
if version != KEY_VERSION:
raise Error("unknown key version (%d)" % version)
fingerprint = packet[5:5 + FINGERPRINT_LEN]
ciphertext = packet[5 + FINGERPRINT_LEN:]
return khr, kcr, fingerprint, ciphertext
def parse(packet, passphrase):
khr, kcr, fingerprint, ciphertext = _parse(packet)
if not passphrase:
hash_repeats = cipher_repeats = 1
else:
hash_repeats = khr * 1000 + 1
cipher_repeats = kcr * 1000 + 1
cipher_key = _cipher_key(passphrase, hash_repeats)
decrypted = _repeat(lambda v: _cipher(cipher_key).decrypt(v),
ciphertext,
cipher_repeats)
decrypted = _unpad(decrypted)
digest = decrypted[SALT_LEN:SALT_LEN+20]
secret = decrypted[SALT_LEN+20:]
if digest != hashlib.sha1(secret).digest():
raise Error("error decrypting key")
return secret
def fingerprint(packet):
return base64.b16encode(_parse(packet)[2])