3 ### Management of a secure password database
5 ### (c) 2005 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the Python interface to Catacomb.
12 ### Catacomb/Python is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
17 ### Catacomb/Python is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ### GNU General Public License for more details.
22 ### You should have received a copy of the GNU General Public License along
23 ### with Catacomb/Python; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 ###--------------------------------------------------------------------------
32 ###--------------------------------------------------------------------------
33 ### Underlying cryptography.
35 class DecryptError (Exception):
37 I represent a failure to decrypt a message.
39 Usually this means that someone used the wrong key, though it can also
40 mean that a ciphertext has been modified.
44 class Crypto (object):
46 I represent a symmetric crypto transform.
48 There's currently only one transform implemented, which is the obvious
49 generic-composition construction: given a message m, and keys K0 and K1, we
50 choose an IV v, and compute:
52 * y = v || E(K0, v; m)
55 The final ciphertext is t || y.
58 def __init__(me, c, h, m, ck, mk):
60 Initialize the Crypto object with a given algorithm selection and keys.
62 We need a GCipher subclass C, a GHash subclass H, a GMAC subclass M, and
63 keys CK and MK for C and M respectively.
71 Encrypt the message PT and return the resulting ciphertext.
73 blksz = me.c.__class__.blksz
76 iv = _C.rand.block(blksz)
79 b.put(me.c.encrypt(pt))
80 t = me.m().hash(b).done()
81 return t + str(buffer(b))
85 Decrypt the ciphertext CT, returning the plaintext.
87 Raises DecryptError if anything goes wrong.
89 blksz = me.c.__class__.blksz
90 tagsz = me.m.__class__.tagsz
100 if t != h.done(): raise DecryptError
101 return me.c.decrypt(x)
105 I represent a crypto transform whose keys are derived from a passphrase.
107 The password is salted and hashed; the salt is available as the `salt'
111 def __init__(me, pp, c, h, m, salt = None):
113 Initialize the PPK object with a passphrase and algorithm selection.
115 We want a passphrase PP, a GCipher subclass C, a GHash subclass H, a GMAC
116 subclass M, and a SALT. The SALT may be None, if we're generating new
117 keys, indicating that a salt should be chosen randomly.
119 if not salt: salt = _C.rand.block(h.hashsz)
120 tag = '%s\0%s' % (pp, salt)
121 Crypto.__init__(me, c, h, m,
122 h().hash('cipher:' + tag).done(),
123 h().hash('mac:' + tag).done())
126 ###--------------------------------------------------------------------------
127 ### Password storage.
129 class PWIter (object):
131 I am an iterator over items in a password database.
133 I implement the usual Python iteration protocol.
136 def __init__(me, pw):
138 Initialize a PWIter object, to fetch items from PW.
141 me.k = me.pw.db.firstkey()
145 Return the next tag from the database.
147 Raises StopIteration if there are no more tags.
155 k = me.pw.db.nextkey(k)
156 me.k = me.pw.db.nextkey(k)
157 return me.pw.unpack(me.pw.db[k])[0]
161 I represent a secure (ish) password store.
163 I can store short secrets, associated with textual names, in a way which
164 doesn't leak too much information about them.
166 I implement (some of the) Python mapping protocol.
168 Here's how we use the underlying GDBM key/value storage to keep track of
169 the necessary things. Password entries have keys whose name begins with
170 `$'; other keys have specific meanings, as follows.
172 cipher Names the Catacomb cipher selected.
174 hash Names the Catacomb hash function selected.
176 key Cipher and MAC keys, each prefixed by a 16-bit big-endian
177 length and concatenated, encrypted using the master
180 mac Names the Catacomb message authentication code selected.
182 magic A magic string for obscuring password tag names.
184 salt The salt for hashing the passphrase.
186 tag The master passphrase's tag, for the Pixie's benefit.
188 Password entries are assigned keys of the form `$' || H(MAGIC || TAG); the
189 corresponding value consists of a pair (TAG, PASSWD), prefixed with 16-bit
190 lengths, concatenated, padded to a multiple of 256 octets, and encrypted
191 using the stored keys.
194 def __init__(me, file, mode = 'r'):
196 Initialize a PW object from the GDBM database in FILE.
198 MODE can be `r' for read-only access to the underlying database, or `w'
199 for read-write access. Requests the database password from the Pixie,
200 which may cause interaction.
203 ## Open the database.
204 me.db = _G.open(file, mode)
206 ## Find out what crypto to use.
207 c = _C.gcciphers[me.db['cipher']]
208 h = _C.gchashes[me.db['hash']]
209 m = _C.gcmacs[me.db['mac']]
211 ## Request the passphrase and extract the master keys.
213 ppk = PPK(_C.ppread(tag), c, h, m, me.db['salt'])
215 b = _C.ReadBuffer(ppk.decrypt(me.db['key']))
221 if not b.endp: raise ValueError, 'trailing junk'
223 ## Set the key, and stash it and the tag-hashing secret.
224 me.k = Crypto(c, h, m, me.ck, me.mk)
225 me.magic = me.k.decrypt(me.db['magic'])
228 def create(cls, file, c, h, m, tag):
230 Create and initialize a new, empty, database FILE.
232 We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
233 and a Pixie passphrase TAG.
235 This doesn't return a working object: it just creates the database file
236 and gets out of the way.
239 ## Set up the cryptography.
240 pp = _C.ppread(tag, _C.PMODE_VERIFY)
241 ppk = PPK(pp, c, h, m)
242 ck = _C.rand.block(c.keysz.default)
243 mk = _C.rand.block(c.keysz.default)
244 k = Crypto(c, h, m, ck, mk)
246 ## Set up and initialize the database.
247 db = _G.open(file, 'n', 0600)
249 db['salt'] = ppk.salt
250 db['cipher'] = c.name
253 db['key'] = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
254 db['magic'] = k.encrypt(_C.rand.block(h.hashsz))
256 def keyxform(me, key):
258 Transform the KEY (actually a password tag) into a GDBM record key.
260 return '$' + me.k.h().hash(me.magic).hash(key).done()
264 Change the database password.
266 Requests the new password from the Pixie, which will probably cause
271 ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
272 me.k.c.__class__, me.k.h, me.k.m.__class__)
274 ppk.encrypt(_C.WriteBuffer().putblk16(me.ck).putblk16(me.mk))
275 me.db['salt'] = ppk.salt
277 def pack(me, key, value):
279 Pack the KEY and VALUE into a ciphertext, and return it.
282 b.putblk16(key).putblk16(value)
283 b.zero(((b.size + 255) & ~255) - b.size)
284 return me.k.encrypt(b)
288 Unpack a ciphertext CT and return a (KEY, VALUE) pair.
290 Might raise DecryptError, of course.
292 b = _C.ReadBuffer(me.k.decrypt(ct))
299 def __getitem__(me, key):
301 Return the password for the given KEY.
304 return me.unpack(me.db[me.keyxform(key)])[1]
308 def __setitem__(me, key, value):
310 Associate the password VALUE with the KEY.
312 me.db[me.keyxform(key)] = me.pack(key, value)
314 def __delitem__(me, key):
316 Forget all about the KEY.
319 del me.db[me.keyxform(key)]
325 Iterate over the known password tags.
329 ###----- That's all, folks --------------------------------------------------