Project

Profile

Help

How to connect?
Download (6.66 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / safe.py @ 96664dff

1
# Copyright (c) 2016, LE GOFF Vincent
2
# All rights reserved.
3

    
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are met:
6

    
7
# * Redistributions of source code must retain the above copyright notice, this
8
#   list of conditions and the following disclaimer.
9

    
10
# * Redistributions in binary form must reproduce the above copyright notice,
11
#   this list of conditions and the following disclaimer in the documentation
12
#   and/or other materials provided with the distribution.
13

    
14
# * Neither the name of ytranslate nor the names of its
15
#   contributors may be used to endorse or promote products derived from
16
#   this software without specific prior written permission.
17

    
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

    
29

    
30
"""This file contains the 'safe' system of CocoMUD, ways to crypt/encrypt.
31

32
This feature requires:
33
    pbkdf2
34
    Crypto
35

36
The module contains a class named 'Safe', that should be insantiated
37
in order to manipulate the encrypting
38
/decrypting mechanism.  This class requires a passphrase in
39
argument.  You can insantiate it as follows:
40
>>> from safe import Safe
41
>>> safe = Safe(file=".passphrase")
42
>>> # (If the file doesn't exist, it will be created with an auto-generated
43
>>> # passphrase.)
44
>>> # Alternatively you can specify the passphrase directly
45
>>> safe = Safe(passphrase="Dsm18fvdjP9sz801,9DJA.1356gndYJz987v")
46
>>> # Store encrypted data
47
>>> safe.store("login", "kredh")
48
>>> safe.store("password", "YoudWishIToldYou")
49
>>> # Retrieve the data (can be later)
50
login = safe.retrieve("login")
51
password = safe.retrieve("password")
52

53
Note that datas that is not a string (like a bool or float) will be
54
saved as unprotected data.  If you want to save it encrypted, you can
55
convert it to string.
56

57
"""
58

    
59
import base64
60
import os
61
import pickle
62

    
63
from Crypto.Cipher import AES
64
from pbkdf2 import PBKDF2
65

    
66
class Safe:
67

    
68
    """A safe object, to encrypt/decrypt information.
69

70
    The Safe class requires a passphrase to be created.  This is a
71
    string of characters that adds to the security of encryption.
72
    Obviously, it needs to remain similar to decrypt information that
73
    has been encrypted.  Other optional parameters are also possible:
74
        secret: the path of the file in which to store crypted data.
75

76

77
    """
78

    
79
    def __init__(self, passphrase=None, file=None, secret="data.crypt",
80
            load=True):
81
        self.salt_seed = 'mkhgts465wef4fwtdd'
82
        self.passphrase = passphrase
83
        self.secret = secret
84
        self.passphrase_size = 64
85
        self.key_size = 32
86
        self.block_size = 16
87
        self.iv_size = 16
88
        self.salt_size = 8
89
        self.data = {}
90

    
91
        if file and os.path.exists(file):
92
            with open(file, "r") as pass_file:
93
                self.passphrase = pass_file.read()
94

    
95
        if not self.passphrase:
96
            self.passphrase = base64.b64encode(os.urandom(
97
                    self.passphrase_size))
98
            if file:
99
                with open(file, "w") as pass_file:
100
                    pass_file.write(self.passphrase)
101

    
102
        # Load the secret file
103
        if load:
104
            self.load()
105

    
106
    def get_salt_from_key(self, key):
107
        return PBKDF2(key, self.salt_seed).read(self.salt_size)
108

    
109
    def encrypt(self, plaintext, salt):
110
        """Pad plaintext, then encrypt it.
111

112
        The encryption occurs with a new, randomly initialised cipher.
113
        This method will not preserve trailing whitespace in plaintext!.
114

115
        """
116
        # Initialise Cipher Randomly
117
        init_vector = os.urandom(self.iv_size)
118

    
119
        # Prepare cipher key
120
        key = PBKDF2(self.passphrase, salt).read(self.key_size)
121
        cipher = AES.new(key, AES.MODE_CBC, init_vector)
122

    
123
        bs = self.block_size
124
        return init_vector + cipher.encrypt(plaintext + \
125
                " " * (bs - (len(plaintext) % bs)))
126

    
127
    def decrypt(self, ciphertext, salt):
128
        """Reconstruct the cipher object and decrypt.
129

130
        This method will not preserve trailing whitespace in the
131
        retrieved value.
132

133
        """
134
        # Prepare cipher key
135
        key = PBKDF2(self.passphrase, salt).read(self.key_size)
136

    
137
        # Extract IV
138
        init_vector = ciphertext[:self.iv_size]
139
        ciphertext = ciphertext[self.iv_size:]
140

    
141
        cipher = AES.new(key, AES.MODE_CBC, init_vector)
142

    
143
        return cipher.decrypt(ciphertext).rstrip(" ")
144

    
145
    def load(self):
146
        """Load the data from the 'secret' file if exists."""
147
        if os.path.exists(self.secret):
148
            with open(self.secret, "rb") as file:
149
                upic = pickle.Unpickler(file)
150
                self.data = upic.load()
151
                if not isinstance(self.data, dict):
152
                    raise ValueError("the data contained in the file " \
153
                            "'{}' is not a dictionary".format(self.secret))
154

    
155
    def retrieve(self, key, *default):
156
        """Retrieve and decrypt the specified key.
157

158
        If the key isn't present in the dictionary, either
159
        return default if specified, or raise a KeyError.
160

161
        If the value at this location isn't a string, return it as is.
162

163
        """
164
        if key not in self.data:
165
            if default:
166
                return default[0]
167

    
168
            raise KeyError(key)
169

    
170
        value = self.data[key]
171
        if isinstance(value, basestring):
172
            salt = self.get_salt_from_key(key)
173
            return self.decrypt(value, salt)
174

    
175
        return value
176

    
177
    def store(self, key, value):
178
        """Store the key in the file.
179

180
        If the key already exists, replaces it.
181
        If the value is not a string or unicode, it will be stored
182
        WITHOUT encryption.
183

184
        """
185
        if isinstance(value, basestring):
186
            salt = self.get_salt_from_key(key)
187
            crypted = self.encrypt(value, salt)
188
            self.data[key] = crypted
189
        else:
190
            self.data[key] = value
191

    
192
        # Write the new data in the file
193
        with open(self.secret, "wb") as file:
194
            pic = pickle.Pickler(file)
195
            pic.dump(self.data)
(13-13/19)