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.
131 I represent a secure (ish) password store.
133 I can store short secrets, associated with textual names, in a way which
134 doesn't leak too much information about them.
136 I implement (some of the) Python mapping protocol.
138 Here's how we use the underlying GDBM key/value storage to keep track of
139 the necessary things. Password entries have keys whose name begins with
140 `$'; other keys have specific meanings, as follows.
142 cipher Names the Catacomb cipher selected.
144 hash Names the Catacomb hash function selected.
146 key Cipher and MAC keys, each prefixed by a 16-bit big-endian
147 length and concatenated, encrypted using the master
150 mac Names the Catacomb message authentication code selected.
152 magic A magic string for obscuring password tag names.
154 salt The salt for hashing the passphrase.
156 tag The master passphrase's tag, for the Pixie's benefit.
158 Password entries are assigned keys of the form `$' || H(MAGIC || TAG); the
159 corresponding value consists of a pair (TAG, PASSWD), prefixed with 16-bit
160 lengths, concatenated, padded to a multiple of 256 octets, and encrypted
161 using the stored keys.
164 def __init__(me, file, mode = 'r'):
166 Initialize a PW object from the GDBM database in FILE.
168 MODE can be `r' for read-only access to the underlying database, or `w'
169 for read-write access. Requests the database password from the Pixie,
170 which may cause interaction.
173 ## Open the database.
174 me.db = _G.open(file, mode)
176 ## Find out what crypto to use.
177 c = _C.gcciphers[me.db['cipher']]
178 h = _C.gchashes[me.db['hash']]
179 m = _C.gcmacs[me.db['mac']]
181 ## Request the passphrase and extract the master keys.
183 ppk = PPK(_C.ppread(tag), c, h, m, me.db['salt'])
185 b = _C.ReadBuffer(ppk.decrypt(me.db['key']))
191 if not b.endp: raise ValueError, 'trailing junk'
193 ## Set the key, and stash it and the tag-hashing secret.
194 me.k = Crypto(c, h, m, me.ck, me.mk)
195 me.magic = me.k.decrypt(me.db['magic'])
198 def create(cls, file, c, h, m, tag):
200 Create and initialize a new, empty, database FILE.
202 We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
203 and a Pixie passphrase TAG.
205 This doesn't return a working object: it just creates the database file
206 and gets out of the way.
209 ## Set up the cryptography.
210 pp = _C.ppread(tag, _C.PMODE_VERIFY)
211 ppk = PPK(pp, c, h, m)
212 ck = _C.rand.block(c.keysz.default)
213 mk = _C.rand.block(c.keysz.default)
214 k = Crypto(c, h, m, ck, mk)
216 ## Set up and initialize the database.
217 db = _G.open(file, 'n', 0600)
219 db['salt'] = ppk.salt
220 db['cipher'] = c.name
223 db['key'] = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
224 db['magic'] = k.encrypt(_C.rand.block(h.hashsz))
226 def keyxform(me, key):
228 Transform the KEY (actually a password tag) into a GDBM record key.
230 return '$' + me.k.h().hash(me.magic).hash(key).done()
234 Change the database password.
236 Requests the new password from the Pixie, which will probably cause
241 ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
242 me.k.c.__class__, me.k.h, me.k.m.__class__)
244 ppk.encrypt(_C.WriteBuffer().putblk16(me.ck).putblk16(me.mk))
245 me.db['salt'] = ppk.salt
247 def pack(me, key, value):
249 Pack the KEY and VALUE into a ciphertext, and return it.
252 b.putblk16(key).putblk16(value)
253 b.zero(((b.size + 255) & ~255) - b.size)
254 return me.k.encrypt(b)
258 Unpack a ciphertext CT and return a (KEY, VALUE) pair.
260 Might raise DecryptError, of course.
262 b = _C.ReadBuffer(me.k.decrypt(ct))
269 def __getitem__(me, key):
271 Return the password for the given KEY.
274 return me.unpack(me.db[me.keyxform(key)])[1]
278 def __setitem__(me, key, value):
280 Associate the password VALUE with the KEY.
282 me.db[me.keyxform(key)] = me.pack(key, value)
284 def __delitem__(me, key):
286 Forget all about the KEY.
289 del me.db[me.keyxform(key)]
295 Iterate over the known password tags.
299 if k[0] == '$': yield me.unpack(me.db[k])[0]
302 ###----- That's all, folks --------------------------------------------------