Project

Profile

Help

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

github / src / safe.py @ 05b173a7

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
    pyaes
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
import pyaes
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, "wb") 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 = pyaes.AESModeOfOperationCBC(key, iv=init_vector)
122

    
123
        bs = self.block_size
124
        if isinstance(plaintext, str):
125
            plaintext = plaintext.encode("utf-8")
126

    
127
        return init_vector + cipher.encrypt(plaintext + \
128
                b" " * (bs - (len(plaintext) % bs)))
129

    
130
    def decrypt(self, ciphertext, salt):
131
        """Reconstruct the cipher object and decrypt.
132

133
        This method will not preserve trailing whitespace in the
134
        retrieved value.
135

136
        """
137
        # Prepare cipher key
138
        key = PBKDF2(self.passphrase, salt).read(self.key_size)
139

    
140
        # Extract IV
141
        init_vector = ciphertext[:self.iv_size]
142
        ciphertext = ciphertext[self.iv_size:]
143

    
144
        cipher = pyaes.AESModeOfOperationCBC(key, iv=init_vector)
145

    
146
        decrypted = cipher.decrypt(ciphertext).rstrip(b" ")
147
        return decrypted.decode("utf-8")
148

    
149
    def load(self):
150
        """Load the data from the 'secret' file if exists."""
151
        if os.path.exists(self.secret):
152
            with open(self.secret, "rb") as file:
153
                upic = pickle.Unpickler(file, encoding="utf-8")
154
                self.data = upic.load()
155

    
156
            if not isinstance(self.data, dict):
157
                raise ValueError("the data contained in the file " \
158
                            "'{}' is not a dictionary".format(self.secret))
159

    
160
    def retrieve(self, key, *default):
161
        """Retrieve and decrypt the specified key.
162

163
        If the key isn't present in the dictionary, either
164
        return default if specified, or raise a KeyError.
165

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

168
        """
169
        if key not in self.data:
170
            if default:
171
                return default[0]
172

    
173
            raise KeyError(key)
174

    
175
        value = self.data[key]
176
        if isinstance(value, (bytes, str)):
177
            salt = self.get_salt_from_key(key)
178
            return self.decrypt(value, salt)
179

    
180
        return value
181

    
182
    def store(self, key, value):
183
        """Store the key in the file.
184

185
        If the key already exists, replaces it.
186
        If the value is not a string, it will be stored
187
        WITHOUT encryption.
188

189
        """
190
        if isinstance(value, str):
191
            salt = self.get_salt_from_key(key)
192
            crypted = self.encrypt(value, salt)
193
            self.data[key] = crypted
194
        else:
195
            self.data[key] = value
196

    
197
        # Write the new data in the file
198
        self.save()
199

    
200
    def save(self):
201
        """Save the data in the secret file."""
202
        with open(self.secret, "wb") as file:
203
            pic = pickle.Pickler(file)
204
            pic.dump(self.data)
(14-14/20)