1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtCore module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qpassworddigestor.h" |
41 | |
42 | #include <QtCore/QDebug> |
43 | #include <QtCore/QMessageAuthenticationCode> |
44 | #include <QtCore/QtEndian> |
45 | |
46 | #include <limits> |
47 | |
48 | QT_BEGIN_NAMESPACE |
49 | namespace QPasswordDigestor { |
50 | |
51 | /*! |
52 | \namespace QPasswordDigestor |
53 | \inmodule QtNetwork |
54 | |
55 | \brief The QPasswordDigestor namespace contains functions which you can use |
56 | to generate hashes or keys. |
57 | */ |
58 | |
59 | /*! |
60 | \since 5.12 |
61 | |
62 | Returns a hash computed using the PBKDF1-algorithm as defined in |
63 | \l {https://tools.ietf.org/html/rfc8018#section-5.1} {RFC 8018}. |
64 | |
65 | The function takes the \a data and \a salt, and then hashes it repeatedly |
66 | for \a iterations iterations using the specified hash \a algorithm. If the |
67 | resulting hash is longer than \a dkLen then it is truncated before it is |
68 | returned. |
69 | |
70 | This function only supports SHA-1 and MD5! The max output size is 160 bits |
71 | (20 bytes) when using SHA-1, or 128 bits (16 bytes) when using MD5. |
72 | Specifying a value for \a dkLen which is greater than this results in a |
73 | warning and an empty QByteArray is returned. To programmatically check this |
74 | limit you can use \l {QCryptographicHash::hashLength}. Furthermore: the |
75 | \a salt must always be 8 bytes long! |
76 | |
77 | \note This function is provided for use with legacy applications and all |
78 | new applications are recommended to use \l {deriveKeyPbkdf2} {PBKDF2}. |
79 | |
80 | \sa deriveKeyPbkdf2, QCryptographicHash, QCryptographicHash::hashLength |
81 | */ |
82 | Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algorithm, |
83 | const QByteArray &data, const QByteArray &salt, |
84 | int iterations, quint64 dkLen) |
85 | { |
86 | // https://tools.ietf.org/html/rfc8018#section-5.1 |
87 | |
88 | if (algorithm != QCryptographicHash::Sha1 |
89 | #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 |
90 | && algorithm != QCryptographicHash::Md5 |
91 | #endif |
92 | ) { |
93 | qWarning("The only supported algorithms for pbkdf1 are SHA-1 and MD5!" ); |
94 | return QByteArray(); |
95 | } |
96 | |
97 | if (salt.size() != 8) { |
98 | qWarning("The salt must be 8 bytes long!" ); |
99 | return QByteArray(); |
100 | } |
101 | if (iterations < 1 || dkLen < 1) |
102 | return QByteArray(); |
103 | |
104 | if (dkLen > quint64(QCryptographicHash::hashLength(algorithm))) { |
105 | qWarning() << "Derived key too long:\n" |
106 | << algorithm << "was chosen which produces output of length" |
107 | << QCryptographicHash::hashLength(algorithm) << "but" << dkLen |
108 | << "was requested." ; |
109 | return QByteArray(); |
110 | } |
111 | |
112 | QCryptographicHash hash(algorithm); |
113 | hash.addData(data); |
114 | hash.addData(salt); |
115 | QByteArray key = hash.result(); |
116 | |
117 | for (int i = 1; i < iterations; i++) { |
118 | hash.reset(); |
119 | hash.addData(key); |
120 | key = hash.result(); |
121 | } |
122 | return key.left(dkLen); |
123 | } |
124 | |
125 | /*! |
126 | \since 5.12 |
127 | |
128 | Derive a key using the PBKDF2-algorithm as defined in |
129 | \l {https://tools.ietf.org/html/rfc8018#section-5.2} {RFC 8018}. |
130 | |
131 | This function takes the \a data and \a salt, and then applies HMAC-X, where |
132 | the X is \a algorithm, repeatedly. It internally concatenates intermediate |
133 | results to the final output until at least \a dkLen amount of bytes have |
134 | been computed and it will execute HMAC-X \a iterations times each time a |
135 | concatenation is required. The total number of times it will execute HMAC-X |
136 | depends on \a iterations, \a dkLen and \a algorithm and can be calculated |
137 | as |
138 | \c{iterations * ceil(dkLen / QCryptographicHash::hashLength(algorithm))}. |
139 | |
140 | \sa deriveKeyPbkdf1, QMessageAuthenticationCode, QCryptographicHash |
141 | */ |
142 | Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm, |
143 | const QByteArray &data, const QByteArray &salt, |
144 | int iterations, quint64 dkLen) |
145 | { |
146 | // https://tools.ietf.org/html/rfc8018#section-5.2 |
147 | |
148 | // The RFC recommends checking that 'dkLen' is not greater than '(2^32 - 1) * hLen' |
149 | int hashLen = QCryptographicHash::hashLength(algorithm); |
150 | const quint64 maxLen = quint64(std::numeric_limits<quint32>::max() - 1) * hashLen; |
151 | if (dkLen > maxLen) { |
152 | qWarning().nospace() << "Derived key too long:\n" |
153 | << algorithm << " was chosen which produces output of length " |
154 | << maxLen << " but " << dkLen << " was requested." ; |
155 | return QByteArray(); |
156 | } |
157 | |
158 | if (iterations < 1 || dkLen < 1) |
159 | return QByteArray(); |
160 | |
161 | QByteArray key; |
162 | quint32 currentIteration = 1; |
163 | QMessageAuthenticationCode hmac(algorithm, data); |
164 | QByteArray index(4, Qt::Uninitialized); |
165 | while (quint64(key.length()) < dkLen) { |
166 | hmac.addData(salt); |
167 | |
168 | qToBigEndian(currentIteration, index.data()); |
169 | hmac.addData(index); |
170 | |
171 | QByteArray u = hmac.result(); |
172 | hmac.reset(); |
173 | QByteArray tkey = u; |
174 | for (int iter = 1; iter < iterations; iter++) { |
175 | hmac.addData(u); |
176 | u = hmac.result(); |
177 | hmac.reset(); |
178 | std::transform(tkey.cbegin(), tkey.cend(), u.cbegin(), tkey.begin(), |
179 | std::bit_xor<char>()); |
180 | } |
181 | key += tkey; |
182 | currentIteration++; |
183 | } |
184 | return key.left(dkLen); |
185 | } |
186 | } // namespace QPasswordDigestor |
187 | QT_END_NAMESPACE |
188 | |