File: crypto.cpp

package info (click to toggle)
portabase 2.0%2Bgit20110117-1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 6,692 kB
  • sloc: cpp: 32,047; sh: 2,675; ansic: 2,320; makefile: 343; xml: 20; python: 16; asm: 10
file content (288 lines) | stat: -rw-r--r-- 8,805 bytes parent folder | download | duplicates (2)
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/*
 * crypto.cpp
 *
 * (c) 2003-2004,2008-2010 by Jeremy Bowman <jmbowman@alum.mit.edu>
 *
 * This program is free 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 2 of the License, or
 * (at your option) any later version.
 */

/** @file crypto.cpp
 * Source file for Crypto
 */

#include <stdio.h>
#include <stdlib.h>

#include <QCryptographicHash>
#include "bytestream.h"
#include "crypto.h"

/**
 * Constructor.
 *
 * @param outer Metakit storage object representing the actual PortaBase file
 * @param inner Metakit storage object representing the encrypted content
 */
Crypto::Crypto(c4_Storage *outer, c4_Storage *inner) : QObject(),
  container(outer), content(inner), crIv("_criv"), crHash("_crhash"),
  crData("_crdata")
{
    if (!Crypto::prngInitialized) {
        rk_isaac_randomseed(&Crypto::prngState);
        Crypto::prngInitialized = true;
    }
    crypto = container->GetAs("_crypto[_criv:B,_crhash:B,_crdata:B]");
}

rk_isaac_state Crypto::prngState;
bool Crypto::prngInitialized = false;

/**
 * Attempt to open the encrypted content in a PortaBase file of the specified
 * version.
 *
 * @param version The file format version of the PortaBase file being opened
 * @return An error message if unsuccessful, or an empty string otherwise
 */
QString Crypto::open(int version)
{
    // load raw data from the file
    c4_Bytes ivField = crIv (crypto[0]);
    initVector = QByteArray((const char*)ivField.Contents(), ivField.Size());

    c4_Bytes hashField = crHash (crypto[0]);
    dataHash = QByteArray((const char*)hashField.Contents(), hashField.Size());
    invertIfNecessary(dataHash, version);

    c4_Bytes dataField = crData (crypto[0]);
    QByteArray data((const char*)dataField.Contents(), dataField.Size());

    // decrypt the data
    bool passError = false;
    QByteArray decrypted = decrypt(data, &passError);
    if (passError) {
        return tr("Incorrect password");
    }
    if (decrypted.size() <= 0) {
        return tr("Error in decrypting data");
    }

    // load the decrypted data into the storage object
    ByteStream input(decrypted);
    if (!content->LoadFrom(input)) {
        return tr("Error in loading data");
    }
    return "";
}

/**
 * Encrypt the current content of the in-memory database and save it to file.
 */
void Crypto::save()
{
    // serialize the storage object to a byte array
    ByteStream output;
    content->SaveTo(output);
    QByteArray rawData = output.getContent();

    // encrypt the data
    QByteArray encrypted = encrypt(rawData);

    // delete any old data from the file, add the new data
    if (crypto.GetSize() > 0) {
        crypto.RemoveAt(0);
    }
    c4_Row row;
    crIv (row) = c4_Bytes(initVector.data(), initVector.size());
    crHash (row) = c4_Bytes(dataHash.data(), dataHash.size());
    crData (row) = c4_Bytes(encrypted.data(), encrypted.size());
    crypto.Add(row);
}

/**
 * Set the password required to open the encrypted content.  Used when
 * creating a new file or opening an existing one (and used by
 * changePassword() when changing the password for an existing file).
 *
 * @param pass The password (or at least what the user believes it to be)
 * @param newPass True for a new password, false if just opening a file
 * @return An error message if the password is deemed inadequate, an empty
 *         string otherwise
 */
QString Crypto::setPassword(const QString &pass, bool newPass)
{
    if (newPass && pass.length() < 6) {
        return tr("Password must be at least 6 characters long");
    }
    password = pass;
    QByteArray utf8pass = pass.toUtf8();
    QByteArray passHash = QCryptographicHash::hash(utf8pass,
                                                   QCryptographicHash::Sha1);
    blowfish.setup(passHash);
    return "";
}

/**
 * Change the password of an existing file.  Requires the existing password to
 * be given correctly in order to succeed.
 *
 * @param oldPass The value entered by the user as the current password
 * @param newPass The new desired password
 * @return An error message if the operation failed, an empty string otherwise
 */
QString Crypto::changePassword(const QString &oldPass, const QString &newPass)
{
    if (oldPass != password) {
        return tr("Incorrect password");
    }
    return setPassword(newPass, true);
}

/**
 * Pad the end of the provided data so that the resulting size is an even
 * multiple of the Blowfish algorithm's block size.  A byte value of 1 is
 * always appended, followed by enough zero bytes to reach the desired size.
 *
 * @param data The data to be padded
 * @return The data with any necessary padding added
 */
QByteArray Crypto::pad(const QByteArray &data)
{
    int dataSize = data.size();
    if (dataSize <= 0) {
        return QByteArray();
    }
    int padding = Blowfish::blockSize - ((dataSize + 1) % Blowfish::blockSize);
    if (padding == (int)Blowfish::blockSize) {
        padding = 0;
    }
    QByteArray result(data);
    result.append(1);
    if (padding > 0) {
        result.append(QByteArray(padding, 0));
    }
    return result;
}

/**
 * Encrypt the provided data.
 *
 * @param data The data to be encrypted
 * @return The encrypted data
 */
QByteArray Crypto::encrypt(const QByteArray &data)
{
    if (data.size() <= 0) {
        return QByteArray();
    }

    // pad out to an even number of blocks
    QByteArray padded = pad(data);
    if (padded.size() <= 0) {
        return QByteArray();
    }

    // get the hash value of the padded data for later verification
    dataHash = QCryptographicHash::hash(padded, QCryptographicHash::Sha1);

    // set the CBC initialization vector
    initVector.resize(Blowfish::blockSize);
    rk_isaac_fill(initVector.data(), initVector.size(), &Crypto::prngState);

    // encrypt the data
    return blowfish.encrypt(padded, initVector);
}

/**
 * Decrypt the provided data.  If the SHA-1 hash of the result doesn't match
 * the one stored for the original data before encryption, the boolean
 * addressed by passError is set to true; this indicates that the supplied
 * password was incorrect.
 *
 * @param data The data to be decrypted
 * @param passError The address of a boolean used to indicate a password error
 * @return The decrypted data
 */
QByteArray Crypto::decrypt(const QByteArray &data, bool *passError)
{
    *passError = false;
    if (data.size() <= 0) {
        return QByteArray();
    }

    // decrypt the data
    QByteArray result = blowfish.decrypt(data, initVector);

    // calculate and test the hash value of the decrypted data
    QByteArray testHash = QCryptographicHash::hash(result,
                                                   QCryptographicHash::Sha1);
    if (testHash != dataHash) {
        *passError = true;
        return QByteArray();
    }

    // remove any padding and return the result
    int resultSize = unpaddedLength(result);
    result.resize(resultSize);
    return result;
}

/**
 * Determine how many bytes of the provided array are actual data, after
 * removing any padding which was added to the end (in order to achieve a
 * suitable size for Blowfish encryption).
 *
 * @param data The data to be inspected
 * @return The number of bytes of actual data
 */
int Crypto::unpaddedLength(const QByteArray &data)
{
    if (data.size() <= 0) {
        return 0;
    }
    int result = data.size();
    // skip zeroes used as padding
    while (data.at(result - 1) == (char)0 && result > 1) {
        result--;
    }
    // skip the "1" used to mark the start of the padding
    result--;
    return result;
}

/**
 * Print the given label to the console, followed by a hexadecimal
 * representation of the given byte array.  Used for debugging purposes.
 *
 * @param label The text label to send to the console
 * @param data The binary data to be displayed
 */
void Crypto::printBytes(QString label, const QByteArray &data)
{
    printf("%s:\n", label.toLocal8Bit().data());
    printf("%s\n", data.toHex().data());
}

/**
 * For files generated using an older version of the encryption code, reverse
 * the endianness of the supplied SHA-1 hash of the data which was stored in
 * the file.  Applies for file format versions 9 and lower.
 *
 * @param dataHash The data to be potentially converted
 * @param version The format version number of the PortaBase file being used
 */
void Crypto::invertIfNecessary(QByteArray dataHash, int version)
{
    // upgraded to Beecrypt 3.0.0 in PortaBase 1.8 / file version 10
    if (version >= 10) {
        return;
    }
    quint32 *temp = (quint32*)dataHash.data();
    int count = dataHash.size() / sizeof(quint32);
    for (int i = 0; i < count; i++) {
        temp[i] = swapu32(temp[i]);
    }
}