summaryrefslogtreecommitdiffstats
path: root/src/network/ssl/qpassworddigestor.cpp
blob: a6c6e7666c3dfddbd3e312b3d508717cd7da206f (plain)
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
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qpassworddigestor.h"

#include <QtCore/QDebug>
#include <QtCore/QMessageAuthenticationCode>
#include <QtCore/QtEndian>

#include <limits>

QT_BEGIN_NAMESPACE
namespace QPasswordDigestor {

/*!
    \namespace QPasswordDigestor
    \inmodule QtNetwork

    \brief The QPasswordDigestor namespace contains functions which you can use
    to generate hashes or keys.
*/

/*!
    \since 5.12

    Returns a hash computed using the PBKDF1-algorithm as defined in
    \l {RFC 8018, section 5.1}.

    The function takes the \a data and \a salt, and then hashes it repeatedly
    for \a iterations iterations using the specified hash \a algorithm. If the
    resulting hash is longer than \a dkLen then it is truncated before it is
    returned.

    This function only supports SHA-1 and MD5! The max output size is 160 bits
    (20 bytes) when using SHA-1, or 128 bits (16 bytes) when using MD5.
    Specifying a value for \a dkLen which is greater than this results in a
    warning and an empty QByteArray is returned. To programmatically check this
    limit you can use \l {QCryptographicHash::hashLength}. Furthermore: the
    \a salt must always be 8 bytes long!

    \note This function is provided for use with legacy applications and all
    new applications are recommended to use \l {deriveKeyPbkdf2} {PBKDF2}.

    \sa deriveKeyPbkdf2, QCryptographicHash, QCryptographicHash::hashLength
*/
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algorithm,
                                            const QByteArray &data, const QByteArray &salt,
                                            int iterations, quint64 dkLen)
{
    // https://tools.ietf.org/html/rfc8018#section-5.1

    if (algorithm != QCryptographicHash::Sha1
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
        && algorithm != QCryptographicHash::Md5
#endif
    ) {
        qWarning("The only supported algorithms for pbkdf1 are SHA-1 and MD5!");
        return QByteArray();
    }

    if (salt.size() != 8) {
        qWarning("The salt must be 8 bytes long!");
        return QByteArray();
    }
    if (iterations < 1 || dkLen < 1)
        return QByteArray();

    if (dkLen > quint64(QCryptographicHash::hashLength(algorithm))) {
        qWarning() << "Derived key too long:\n"
                   << algorithm << "was chosen which produces output of length"
                   << QCryptographicHash::hashLength(algorithm) << "but" << dkLen
                   << "was requested.";
        return QByteArray();
    }

    QCryptographicHash hash(algorithm);
    hash.addData(data);
    hash.addData(salt);
    QByteArray key = hash.result();

    for (int i = 1; i < iterations; i++) {
        hash.reset();
        hash.addData(key);
        key = hash.result();
    }
    return key.left(dkLen);
}

/*!
    \since 5.12

    Derive a key using the PBKDF2-algorithm as defined in
    \l {RFC 8018, section 5.2}.

    This function takes the \a data and \a salt, and then applies HMAC-X, where
    the X is \a algorithm, repeatedly. It internally concatenates intermediate
    results to the final output until at least \a dkLen amount of bytes have
    been computed and it will execute HMAC-X \a iterations times each time a
    concatenation is required. The total number of times it will execute HMAC-X
    depends on \a iterations, \a dkLen and \a algorithm and can be calculated
    as
    \c{iterations * ceil(dkLen / QCryptographicHash::hashLength(algorithm))}.

    \sa deriveKeyPbkdf1, QMessageAuthenticationCode, QCryptographicHash
*/
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
                                            const QByteArray &data, const QByteArray &salt,
                                            int iterations, quint64 dkLen)
{
    // https://tools.ietf.org/html/rfc8018#section-5.2

    // The RFC recommends checking that 'dkLen' is not greater than '(2^32 - 1) * hLen'
    int hashLen = QCryptographicHash::hashLength(algorithm);
    const quint64 maxLen = quint64(std::numeric_limits<quint32>::max() - 1) * hashLen;
    if (dkLen > maxLen) {
        qWarning().nospace() << "Derived key too long:\n"
                             << algorithm << " was chosen which produces output of length "
                             << maxLen << " but " << dkLen << " was requested.";
        return QByteArray();
    }

    if (iterations < 1 || dkLen < 1)
        return QByteArray();

    QByteArray key;
    quint32 currentIteration = 1;
    QMessageAuthenticationCode hmac(algorithm, data);
    QByteArray index(4, Qt::Uninitialized);
    while (quint64(key.length()) < dkLen) {
        hmac.addData(salt);

        qToBigEndian(currentIteration, index.data());
        hmac.addData(index);

        QByteArray u = hmac.result();
        hmac.reset();
        QByteArray tkey = u;
        for (int iter = 1; iter < iterations; iter++) {
            hmac.addData(u);
            u = hmac.result();
            hmac.reset();
            std::transform(tkey.cbegin(), tkey.cend(), u.cbegin(), tkey.begin(),
                           std::bit_xor<char>());
        }
        key += tkey;
        currentIteration++;
    }
    return key.left(dkLen);
}
} // namespace QPasswordDigestor
QT_END_NAMESPACE