Simple encryption: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
(Decode HTML entity numbers)
(Updated the article, added support for qt5)
Line 18: Line 18:
== Introducing SimpleCrypt ==
== Introducing SimpleCrypt ==


The idea of <code>SimpleCrypt</code> is to provide some basic cryptographic capabilities for simple encryption. Only symmetric encryption is supported (that is: only with the same key for encryption and decryption), and there is no API for streaming data in and out like you often see with more advanced cyphers. If you need stronger cryptography, streaming cyphers or asymmetric keys, take a look at [http://delta.affinix.com/qca/ QCA] instead.
----


The <code>SimpleCrypt</code> class takes a 64 bits key in the form of a quint64. If you use a fixed key build into your program, you can do something like this to initialize your <code>SimpleCrypt</code> object:
The idea of SimpleCrypt is to provide some basic cryptographic capabilities for simple encryption. Only symmetric encryption is supported (= same key for encryption and decryption required), and there is no API for streaming data in and out like you often see with more advanced cyphers. If you need stronger cryptography, streaming cyphers or asymmetric keys, take a look at [http://delta.affinix.com/qca/ QCA] instead.
 
The SimpleCrypt class takes a 64 bits key in the form of a quint64. If you use a fixed key build into your program, you can do something like this to initialize your SimpleCrypt object:


<code>
<code>
Line 26: Line 28:
</code>
</code>
You can also use the <code>setKey()</code> method to set a key on the class.
But you can also use the
<code>
SimpleCrypt crypto();
crypto.setKey(0x0c2ad4a4acb9f023)</code> method to set a key on the class.


=== Selecting a suitable key ===
=== Selecting a suitable key ===


To get such random numbers, you might use [http://www.random.org/integers/?num=10&min=0&max=65535&col=5&base=16&format=html&rnd=new this service] and pick four of the 2 byte hexadecimals that you concatenate together as done above. That works better than trying to make up something semi-random yourself. Of course, you can also use other means to get to a quint64 key, such as using some hash of a password and reducing that to 64 bits.
To get such random numbers, you might use [https://www.random.org/integers/?num=10&min=0&max=65535&col=5&base=16&format=html&rnd=new this service] and pick four of the 2 byte hexadecimals that you concatenate together as done above. That works better than trying to make up something semi-random yourself. Of course, you can also use other means to get to a quint64 key, such as using some hash of a password and reducing that to 64 bits.


=== SimpleCrypt in use ===
=== SimpleCrypt in use ===


Once setup, you can use <code>SimpleCrypt</code> to actually encrypt or decrypt data. For the encrypt functions, both the ''plaintext'' that you input as the ''cyphertext'' that is being outputted can be either a <code>QString</code> or a <code>QByteArray</code>. The reverse applies to the decrypt functions. That results in four encrypt methods, and four corresponding decrypt methods:
Once setup, you can use SimpleCrypt to actually encrypt or decrypt data. For the encrypt functions, both the ''plaintext'' that you input as the ''cyphertext'' that is being outputted can be either a ''QString'' or a ''QByteArray''. The reverse applies to the decrypt functions. That results in four encrypt methods, and four corresponding decrypt methods:


===== Encryption methods =====
===== Encryption methods =====
Line 60: Line 65:
</code>
</code>


The code above encrypts a <code>QString</code> to a <code>QString</code>, and then decrypts that string again to a new <code>QString</code>.
The code above encrypts a "QString" to a "QString", and then decrypts that string again to a new "QString".


==== Use with binary data ====
==== Use with binary data ====
Line 89: Line 94:
</code>
</code>


Note the use of the data protection feature. I would recommend that you use at least the <code>ProtectionChecksum</code> level of protection (enabled by default) if you work with binary data, as streaming invalid binary data into your program can lead to big problems.
Note the use of the data protection feature. I would recommend that you use at least the ProtectionChecksum level of protection (enabled by default) if you work with binary data, as streaming invalid binary data into your program can lead to big problems.


On the other end, you can now do:
On the other end, you can now do:
Line 116: Line 121:


== Compression ==
== Compression ==
----


For long strings, it can be beneficial to use compression before encryption. Not only does the length of the string decrease, the input will be a bit more random-looking too, resulting in a harder to break cyphertext (at least, in principle, see the warning at the top of this page.) However, for short strings, applying compression can lead to an ''increase'' in the string length.
For long strings, it can be beneficial to use compression before encryption. Not only does the length of the string decrease, the input will be a bit more random-looking too, resulting in a harder to break cyphertext (at least, in principle, see the warning at the top of this page.) However, for short strings, applying compression can lead to an ''increase'' in the string length.


<code>SimpleCrypt</code> supports three modes for compression before encryption. You can force to always use compression, to never use compression, or to automatically choose the shortest version (either the compressed one or the uncompressed one). The last option is the default option. Note that setting this only affects ''encryption''. For decryption, <code>SimpleCrypt</code> automatically decompresses the data if compression was used for the encryption.
SimpleCrypt supports three modes for compression before encryption. You can force to always use compression, to never use compression, or to automatically choose the shortest version (either the compressed one or the uncompressed one). The last option is the default option. Note that setting this only affects ''encryption''. For decryption, SimpleCrypt automatically decompresses the data if compression was used for the encryption.


== The code ==
== The code ==


simplecrypt.h
----
 
The source code of SimpleCrypt consists of two files: the header (.h) and the source (.cpp).
 
'''simplecrypt.h'''
<source lang="cpp">
<source lang="cpp">
/*
/*
Copyright © 2011, Andre Somers
Copyright (c) 2011, Andre Somers
All rights reserved.
All rights reserved.


Redistribution and use in source and binary forms, with or without
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
      notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
    * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
      notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
      documentation and/or other materials provided with the distribution.
* Neither the name of the Rathenau Instituut, Andre Somers nor the
    * Neither the name of the Rathenau Instituut, Andre Somers nor the
names of its contributors may be used to endorse or promote products
      names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
      derived from this software without specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
Line 146: Line 157:
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
Line 159: Line 170:


/**
/**
@short Simple encryption and decryption of strings and byte arrays
  @short Simple encryption and decryption of strings and byte arrays


This class provides a simple implementation of encryption and decryption
  This class provides a simple implementation of encryption and decryption
of strings and byte arrays.
  of strings and byte arrays.


@warning The encryption provided by this class is NOT strong encryption. It may
  @warning The encryption provided by this class is NOT strong encryption. It may
help to shield things from curious eyes, but it will NOT stand up to someone
  help to shield things from curious eyes, but it will NOT stand up to someone
determined to break the encryption. Don't say you were not warned.
  determined to break the encryption. Don't say you were not warned.


The class uses a 64 bit key. Simply create an instance of the class, set the key,
  The class uses a 64 bit key. Simply create an instance of the class, set the key,
and use the encryptToString() method to calculate an encrypted version of the input string.
  and use the encryptToString() method to calculate an encrypted version of the input string.
To decrypt that string again, use an instance of SimpleCrypt initialized with
  To decrypt that string again, use an instance of SimpleCrypt initialized with
the same key, and call the decryptToString() method with the encrypted string. If the key
  the same key, and call the decryptToString() method with the encrypted string. If the key
matches, the decrypted version of the string will be returned again.
  matches, the decrypted version of the string will be returned again.


If you do not provide a key, or if something else is wrong, the encryption and
  If you do not provide a key, or if something else is wrong, the encryption and
decryption function will return an empty string or will return a string containing nonsense.
  decryption function will return an empty string or will return a string containing nonsense.
lastError() will return a value indicating if the method was succesful, and if not, why not.
  lastError() will return a value indicating if the method was succesful, and if not, why not.


SimpleCrypt is prepared for the case that the encryption and decryption
  SimpleCrypt is prepared for the case that the encryption and decryption
algorithm is changed in a later version, by prepending a version identifier to the cypertext.
  algorithm is changed in a later version, by prepending a version identifier to the cypertext.
*/
  */
class SimpleCrypt
class SimpleCrypt
{
{
public:
public:
/**
    /**
CompressionMode describes if compression will be applied to the data to be
      CompressionMode describes if compression will be applied to the data to be
encrypted.
      encrypted.
*/
      */
enum CompressionMode {
    enum CompressionMode {
CompressionAuto, /*[[Image:< Only apply compression if that results in a shorter plaintext. */
        CompressionAuto,   /*!< Only apply compression if that results in a shorter plaintext. */
         CompressionAlways,  /*|]]< Always apply compression. Note that for short inputs, a compression may result in longer data */
         CompressionAlways,  /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
CompressionNever /*!< Never apply compression. */
        CompressionNever   /*!< Never apply compression. */
};
    };
/**
    /**
IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
      IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
or wrong decryption keys.
      or wrong decryption keys.


Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
      Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
increases the length of the resulting cypertext, but makes it possible to check if the plaintext
      increases the length of the resulting cypertext, but makes it possible to check if the plaintext
appears to be valid after decryption.
      appears to be valid after decryption.
*/
    */
enum IntegrityProtectionMode {
    enum IntegrityProtectionMode {
ProtectionNone, /*[[Image:< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
        ProtectionNone,   /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
         ProtectionChecksum,/*|]]< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
         ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
ProtectionHash /*[[Image:< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
        ProtectionHash     /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
     };
     };
     /**
     /**
Line 210: Line 221:
       */
       */
     enum Error {
     enum Error {
         ErrorNoError,        /*|]]< No error occurred. */
         ErrorNoError,        /*!< No error occurred. */
ErrorNoKeySet, /*[[Image:< No key was set. You can not encrypt or decrypt without a valid key. */
        ErrorNoKeySet,       /*!< No key was set. You can not encrypt or decrypt without a valid key. */
         ErrorUnknownVersion,  /*|]]< The version of this data is unknown, or the data is otherwise not valid. */
         ErrorUnknownVersion,  /*!< The version of this data is unknown, or the data is otherwise not valid. */
ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
        ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
};
    };


/**
    /**
Constructor.
      Constructor.


Constructs a SimpleCrypt instance without a valid key set on it.
      Constructs a SimpleCrypt instance without a valid key set on it.
*/
    */
SimpleCrypt();
    SimpleCrypt();
/**
    /**
Constructor.
      Constructor.


Constructs a SimpleCrypt instance and initializes it with the given </code>arg key.
      Constructs a SimpleCrypt instance and initializes it with the given @arg key.
*/
    */
explicit SimpleCrypt(quint64 key);
    explicit SimpleCrypt(quint64 key);


/**
    /**
(Re-) initializes the key with the given <code>arg key.
      (Re-) initializes the key with the given @arg key.
*/
      */
void setKey(quint64 key);
    void setKey(quint64 key);
/**
    /**
Returns true if SimpleCrypt has been initialized with a key.
      Returns true if SimpleCrypt has been initialized with a key.
*/
      */
bool hasKey() const {return !m_keyParts.isEmpty();}
    bool hasKey() const {return !m_keyParts.isEmpty();}


/**
    /**
Sets the compression mode to use when encrypting data. The default mode is Auto.
      Sets the compression mode to use when encrypting data. The default mode is Auto.


Note that decryption is not influenced by this mode, as the decryption recognizes
      Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
      what mode was used when encrypting.
*/
      */
void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
    void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
/**
    /**
Returns the CompressionMode that is currently in use.
      Returns the CompressionMode that is currently in use.
*/
      */
CompressionMode compressionMode() const {return m_compressionMode;}
    CompressionMode compressionMode() const {return m_compressionMode;}


/**
    /**
Sets the integrity mode to use when encrypting data. The default mode is Checksum.
      Sets the integrity mode to use when encrypting data. The default mode is Checksum.


Note that decryption is not influenced by this mode, as the decryption recognizes
      Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
      what mode was used when encrypting.
*/
      */
void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
    void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
/**
    /**
Returns the IntegrityProtectionMode that is currently in use.
      Returns the IntegrityProtectionMode that is currently in use.
*/
      */
IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
    IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}


/**
    /**
Returns the last error that occurred.
      Returns the last error that occurred.
*/
      */
Error lastError() const {return m_lastError;}
    Error lastError() const {return m_lastError;}


/**
    /**
Encrypts the </code>arg plaintext string with the key the class was initialized with, and returns
      Encrypts the @arg plaintext string with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
      a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the string, so it can be stored easily in a text format.
      actual result of the string, so it can be stored easily in a text format.
*/
      */
QString encryptToString(const QString& plaintext) ;
    QString encryptToString(const QString& plaintext) ;
/**
    /**
Encrypts the <code>arg plaintext QByteArray with the key the class was initialized with, and returns
      Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
      a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the encryption, so it can be stored easily in a text format.
      actual result of the encryption, so it can be stored easily in a text format.
*/
      */
QString encryptToString(QByteArray plaintext) ;
    QString encryptToString(QByteArray plaintext) ;
/**
    /**
Encrypts the </code>arg plaintext string with the key the class was initialized with, and returns
      Encrypts the @arg plaintext string with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
      a binary cyphertext in a QByteArray the result.


This method returns a byte array, that is useable for storing a binary format. If you need
      This method returns a byte array, that is useable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
      a string you can store in a text file, use encryptToString() instead.
*/
      */
QByteArray encryptToByteArray(const QString& plaintext) ;
    QByteArray encryptToByteArray(const QString& plaintext) ;
/**
    /**
Encrypts the <code>arg plaintext QByteArray with the key the class was initialized with, and returns
      Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
      a binary cyphertext in a QByteArray the result.


This method returns a byte array, that is useable for storing a binary format. If you need
      This method returns a byte array, that is useable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
      a string you can store in a text file, use encryptToString() instead.
*/
      */
QByteArray encryptToByteArray(QByteArray plaintext) ;
    QByteArray encryptToByteArray(QByteArray plaintext) ;


/**
    /**
Decrypts a cyphertext string encrypted with this class with the set key back to the
      Decrypts a cyphertext string encrypted with this class with the set key back to the
plain text version.
      plain text version.


If an error occured, such as non-matching keys between encryption and decryption,
      If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
      an empty string or a string containing nonsense may be returned.
*/
      */
QString decryptToString(const QString& cyphertext) ;
    QString decryptToString(const QString& cyphertext) ;
/**
    /**
Decrypts a cyphertext string encrypted with this class with the set key back to the
      Decrypts a cyphertext string encrypted with this class with the set key back to the
plain text version.
      plain text version.


If an error occured, such as non-matching keys between encryption and decryption,
      If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
      an empty string or a string containing nonsense may be returned.
*/
      */
QByteArray decryptToByteArray(const QString& cyphertext) ;
    QByteArray decryptToByteArray(const QString& cyphertext) ;
/**
    /**
Decrypts a cyphertext binary encrypted with this class with the set key back to the
      Decrypts a cyphertext binary encrypted with this class with the set key back to the
plain text version.
      plain text version.


If an error occured, such as non-matching keys between encryption and decryption,
      If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
      an empty string or a string containing nonsense may be returned.
*/
      */
QString decryptToString(QByteArray cypher) ;
    QString decryptToString(QByteArray cypher) ;
/**
    /**
Decrypts a cyphertext binary encrypted with this class with the set key back to the
      Decrypts a cyphertext binary encrypted with this class with the set key back to the
plain text version.
      plain text version.


If an error occured, such as non-matching keys between encryption and decryption,
      If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
      an empty string or a string containing nonsense may be returned.
*/
      */
QByteArray decryptToByteArray(QByteArray cypher) ;
    QByteArray decryptToByteArray(QByteArray cypher) ;


//enum to describe options that have been used for the encryption. Currently only one, but
    //enum to describe options that have been used for the encryption. Currently only one, but
//that only leaves room for future extensions like adding a cryptographic hash…
    //that only leaves room for future extensions like adding a cryptographic hash...
enum CryptoFlag{CryptoFlagNone = 0,
    enum CryptoFlag{CryptoFlagNone = 0,
CryptoFlagCompression = 0x01,
                    CryptoFlagCompression = 0x01,
CryptoFlagChecksum = 0x02,
                    CryptoFlagChecksum = 0x02,
CryptoFlagHash = 0x04
                    CryptoFlagHash = 0x04
};
                  };
Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
    Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
private:
private:


void splitKey();
    void splitKey();


quint64 m_key;
    quint64 m_key;
QVector<char> m_keyParts;
    QVector<char> m_keyParts;
CompressionMode m_compressionMode;
    CompressionMode m_compressionMode;
IntegrityProtectionMode m_protectionMode;
    IntegrityProtectionMode m_protectionMode;
Error m_lastError;
    Error m_lastError;
};
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
Line 353: Line 364:
</source>
</source>


simplecrypt.cpp
'''simplecrypt.cpp'''
<source lang="cpp">
<source lang="cpp">
/*
/*
Copyright © 2011, Andre Somers
Copyright (c) 2011, Andre Somers
All rights reserved.
All rights reserved.


Redistribution and use in source and binary forms, with or without
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
      notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
    * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
      notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
      documentation and/or other materials provided with the distribution.
* Neither the name of the Rathenau Instituut, Andre Somers nor the
    * Neither the name of the Rathenau Instituut, Andre Somers nor the
names of its contributors may be used to endorse or promote products
      names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
      derived from this software without specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
Line 376: Line 387:
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
Line 387: Line 398:
#include <QDateTime>
#include <QDateTime>
#include <QCryptographicHash>
#include <QCryptographicHash>
#include <QDataStream>


SimpleCrypt::SimpleCrypt():
SimpleCrypt::SimpleCrypt():
m_key(0),
    m_key(0),
m_compressionMode(CompressionAuto),
    m_compressionMode(CompressionAuto),
m_protectionMode(ProtectionChecksum),
    m_protectionMode(ProtectionChecksum),
m_lastError(ErrorNoError)
    m_lastError(ErrorNoError)
{
{
qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
}
}


SimpleCrypt::SimpleCrypt(quint64 key):
SimpleCrypt::SimpleCrypt(quint64 key):
m_key(key),
    m_key(key),
m_compressionMode(CompressionAuto),
    m_compressionMode(CompressionAuto),
m_protectionMode(ProtectionChecksum),
    m_protectionMode(ProtectionChecksum),
m_lastError(ErrorNoError)
    m_lastError(ErrorNoError)
{
{
qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
splitKey();
    splitKey();
}
}


void SimpleCrypt::setKey(quint64 key)
void SimpleCrypt::setKey(quint64 key)
{
{
m_key = key;
    m_key = key;
splitKey();
    splitKey();
}
}


void SimpleCrypt::splitKey()
void SimpleCrypt::splitKey()
{
{
m_keyParts.clear();
    m_keyParts.clear();
m_keyParts.resize(8);
    m_keyParts.resize(8);
for (int i=0;i<8;i+'') {
    for (int i=0;i<8;i++) {
quint64 part = m_key;
        quint64 part = m_key;
for (int j=i; j>0; j—)
        for (int j=i; j>0; j--)
part = part >> 8;
            part = part >> 8;
part = part & 0xff;
        part = part & 0xff;
m_keyParts[i] = static_cast<char>(part);
        m_keyParts[i] = static_cast<char>(part);
}
    }
}
}


QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
{
{
QByteArray plaintextArray = plaintext.toUtf8();
    QByteArray plaintextArray = plaintext.toUtf8();
return encryptToByteArray(plaintextArray);
    return encryptToByteArray(plaintextArray);
}
}


QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
{
{
if (m_keyParts.isEmpty()) {
    if (m_keyParts.isEmpty()) {
qWarning() << "No key set.";
        qWarning() << "No key set.";
m_lastError = ErrorNoKeySet;
        m_lastError = ErrorNoKeySet;
return QByteArray();
        return QByteArray();
}
    }




QByteArray ba = plaintext;
    QByteArray ba = plaintext;


CryptoFlags flags = CryptoFlagNone;
    CryptoFlags flags = CryptoFlagNone;
if (m_compressionMode CompressionAlways) {
    if (m_compressionMode == CompressionAlways) {
         ba = qCompress(ba, 9); //maximum compression
         ba = qCompress(ba, 9); //maximum compression
         flags |= CryptoFlagCompression;
         flags |= CryptoFlagCompression;
     } else if (m_compressionMode CompressionAuto) {
     } else if (m_compressionMode == CompressionAuto) {
QByteArray compressed = qCompress(ba, 9);
        QByteArray compressed = qCompress(ba, 9);
if (compressed.count() < ba.count()) {
        if (compressed.count() < ba.count()) {
ba = compressed;
            ba = compressed;
flags |= CryptoFlagCompression;
            flags |= CryptoFlagCompression;
}
        }
}
    }


QByteArray integrityProtection;
    QByteArray integrityProtection;
if (m_protectionMode ProtectionChecksum) {
    if (m_protectionMode == ProtectionChecksum) {
         flags |= CryptoFlagChecksum;
         flags |= CryptoFlagChecksum;
         QDataStream s(&integrityProtection, QIODevice::WriteOnly);
         QDataStream s(&integrityProtection, QIODevice::WriteOnly);
         s << qChecksum(ba.constData(), ba.length());
         s << qChecksum(ba.constData(), ba.length());
     } else if (m_protectionMode ProtectionHash) {
     } else if (m_protectionMode == ProtectionHash) {
flags |= CryptoFlagHash;
        flags |= CryptoFlagHash;
QCryptographicHash hash(QCryptographicHash::Sha1);
        QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(ba);
        hash.addData(ba);


integrityProtection''= hash.result();
        integrityProtection += hash.result();
}
    }


//prepend a random char to the string
    //prepend a random char to the string
char randomChar = char(qrand() & 0xFF);
    char randomChar = char(qrand() & 0xFF);
ba = randomChar + integrityProtection + ba;
    ba = randomChar + integrityProtection + ba;


int pos(0);
    int pos(0);
char lastChar(0);
    char lastChar(0);


int cnt = ba.count();
    int cnt = ba.count();


while (pos < cnt) {
    while (pos < cnt) {
ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
        ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
lastChar = ba.at(pos);
        lastChar = ba.at(pos);
+''pos;
        ++pos;
}
    }


QByteArray resultArray;
    QByteArray resultArray;
resultArray.append(char(0x03)); //version for future updates to algorithm
    resultArray.append(char(0x03)); //version for future updates to algorithm
resultArray.append(char(flags)); //encryption flags
    resultArray.append(char(flags)); //encryption flags
resultArray.append(ba);
    resultArray.append(ba);


m_lastError = ErrorNoError;
    m_lastError = ErrorNoError;
return resultArray;
    return resultArray;
}
}


QString SimpleCrypt::encryptToString(const QString& plaintext)
QString SimpleCrypt::encryptToString(const QString& plaintext)
{
{
QByteArray plaintextArray = plaintext.toUtf8();
    QByteArray plaintextArray = plaintext.toUtf8();
QByteArray cypher = encryptToByteArray(plaintextArray);
    QByteArray cypher = encryptToByteArray(plaintextArray);
QString cypherString = QString::fromAscii(cypher.toBase64());
    QString cypherString = QString::fromLatin1(cypher.toBase64());
return cypherString;
    return cypherString;
}
}


QString SimpleCrypt::encryptToString(QByteArray plaintext)
QString SimpleCrypt::encryptToString(QByteArray plaintext)
{
{
QByteArray cypher = encryptToByteArray(plaintext);
    QByteArray cypher = encryptToByteArray(plaintext);
QString cypherString = QString::fromAscii(cypher.toBase64());
    QString cypherString = QString::fromLatin1(cypher.toBase64());
return cypherString;
    return cypherString;
}
}


QString SimpleCrypt::decryptToString(const QString &cyphertext)
QString SimpleCrypt::decryptToString(const QString &cyphertext)
{
{
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toAscii());
    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
    QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
    QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());


return plaintext;
    return plaintext;
}
}


QString SimpleCrypt::decryptToString(QByteArray cypher)
QString SimpleCrypt::decryptToString(QByteArray cypher)
{
{
QByteArray ba = decryptToByteArray(cypher);
    QByteArray ba = decryptToByteArray(cypher);
QString plaintext = QString::fromUtf8(ba, ba.size());
    QString plaintext = QString::fromUtf8(ba, ba.size());


return plaintext;
    return plaintext;
}
}


QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
{
{
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toAscii());
    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
QByteArray ba = decryptToByteArray(cyphertextArray);
    QByteArray ba = decryptToByteArray(cyphertextArray);


return ba;
    return ba;
}
}


QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
{
{
if (m_keyParts.isEmpty()) {
    if (m_keyParts.isEmpty()) {
qWarning() << "No key set.";
        qWarning() << "No key set.";
m_lastError = ErrorNoKeySet;
        m_lastError = ErrorNoKeySet;
return QByteArray();
        return QByteArray();
}
    }


QByteArray ba = cypher;
    QByteArray ba = cypher;


if( cypher.count() < 3 )
    if( cypher.count() < 3 )
return QByteArray();
        return QByteArray();


char version = ba.at(0);
    char version = ba.at(0);


if (version !=3) { //we only work with version 3
    if (version !=3) { //we only work with version 3
m_lastError = ErrorUnknownVersion;
        m_lastError = ErrorUnknownVersion;
qWarning() << "Invalid version or not a cyphertext.";
        qWarning() << "Invalid version or not a cyphertext.";
return QByteArray();
        return QByteArray();
}
    }


CryptoFlags flags = CryptoFlags(ba.at(1));
    CryptoFlags flags = CryptoFlags(ba.at(1));


ba = ba.mid(2);
    ba = ba.mid(2);
int pos(0);
    int pos(0);
int cnt(ba.count());
    int cnt(ba.count());
char lastChar = 0;
    char lastChar = 0;


while (pos < cnt) {
    while (pos < cnt) {
char currentChar = ba[pos];
        char currentChar = ba[pos];
ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
        ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
lastChar = currentChar;
        lastChar = currentChar;
''+pos;
        ++pos;
}
    }


ba = ba.mid(1); //chop off the random number at the start
    ba = ba.mid(1); //chop off the random number at the start


bool integrityOk(true);
    bool integrityOk(true);
if (flags.testFlag(CryptoFlagChecksum)) {
    if (flags.testFlag(CryptoFlagChecksum)) {
if (ba.length() < 2) {
        if (ba.length() < 2) {
m_lastError = ErrorIntegrityFailed;
            m_lastError = ErrorIntegrityFailed;
return QByteArray();
            return QByteArray();
}
        }
quint16 storedChecksum;
        quint16 storedChecksum;
{
        {
QDataStream s(&ba, QIODevice::ReadOnly);
            QDataStream s(&ba, QIODevice::ReadOnly);
s >> storedChecksum;
            s >> storedChecksum;
}
        }
ba = ba.mid(2);
        ba = ba.mid(2);
quint16 checksum = qChecksum(ba.constData(), ba.length());
        quint16 checksum = qChecksum(ba.constData(), ba.length());
integrityOk = (checksum storedChecksum);
        integrityOk = (checksum == storedChecksum);
     } else if (flags.testFlag(CryptoFlagHash)) {
     } else if (flags.testFlag(CryptoFlagHash)) {
         if (ba.length() < 20) {
         if (ba.length() < 20) {
Line 592: Line 604:
         QCryptographicHash hash(QCryptographicHash::Sha1);
         QCryptographicHash hash(QCryptographicHash::Sha1);
         hash.addData(ba);
         hash.addData(ba);
         integrityOk = (hash.result() storedHash);
         integrityOk = (hash.result() == storedHash);
}
    }


if (!integrityOk) {
    if (!integrityOk) {
m_lastError = ErrorIntegrityFailed;
        m_lastError = ErrorIntegrityFailed;
return QByteArray();
        return QByteArray();
}
    }


if (flags.testFlag(CryptoFlagCompression))
    if (flags.testFlag(CryptoFlagCompression))
ba = qUncompress(ba);
        ba = qUncompress(ba);


m_lastError = ErrorNoError;
    m_lastError = ErrorNoError;
return ba;
    return ba;
}
}
</source>
</source>


You can copy/paste the code above into your own simplecrypt.h and simplecrypt.cpp files to use it.
You can copy/paste the code above into your own '''simplecrypt.h''' and '''simplecrypt.cpp''' files to use it.


==Details on the algorithm==
==Details on the algorithm==


The algorithm and data format used in this class, have been detailed on a [[ SimpleCrypt_algorithm_details | separate page]]. On that page, you can find byte-for-byte descriptions of how the cypher text is constructed from the plain text.
----
 
The algorithm and data format used in this class, have been detailed on a [[ SimpleCrypt_algorithm_details | separate page]]. On that page, you can find byte-for-byte descriptions of how the cyphertext is constructed from the plain text.




==Future extensions==
==Future extensions==


I, or somebody else, may choose to extend <code>SimpleCrypt</code> in the future, or fix a weakness of some kind. To make that possible without loosing the option to read your older encrypted data, the generated cyphertext contains a version number. The idea is that future versions of this code will have a higher version number, but will keep supporting the decryption of cyphertexts with a lower version number. The current version number is 2. Note that these version numbers need not coincide with code version numbers, as a change in the code does not always result in a change in the actual encryption.
----
 
I, or somebody else, may choose to extend SimpleCrypt in the future, or fix a weakness of some kind. To make that possible without losing the option to read your older encrypted data, the generated cyphertext contains a version number. The idea is that future versions of this code will have a higher version number, but will keep supporting the decryption of cyphertexts with a lower version number. The current version number is 2. Note that these version numbers need not coincide with code version numbers, as a change in the code does not always result in a change in the actual encryption.


==Versions==
==Versions==


The current code is version 3 of the code.  
----
 
The latest version of the code is 3.1
 
====Version 3.1====
 
''' The code is now compatible with Qt5 and higher


====Version 3====
====Version 3====
Line 631: Line 652:


* Renamed flags to avoid clashes
* Renamed flags to avoid clashes
* Upped version number to 2, but did not retain backwards comparability
* Upped version number to 2, but did not retain backwards compatibility
* Added optional data integrity protection using either a checksum or a cryptographic hash
* Added optional data integrity protection using either a checksum or a cryptographic hash
* Added error reporting (making is necessary to make the encryption and decryption functions non-const)
* Added error reporting (making is necessary to make the encryption and decryption functions non-const)
Line 638: Line 659:


This marks the first public release.
This marks the first public release.
== Feedback ==

Revision as of 15:10, 15 March 2015

This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

Simple encryption with SimpleCrypt

Sometimes, you have to store some information that you may want to protect against casual observation. Think about passwords for remote services, for instance. Strong cryptography is obviously the best solution, but it can be hard to use those in the right way, they tend to pull in more libraries, and frankly, it may just be overkill for the situation.

A word of warning

The class presented in this article does not provide strong encryption!

It will protect your data from casual observers, but it will not stand up to dedicated hackers trying to break your secrets. I am not a cryptography expert. Use the code here at your own risk. I am not to be held responsible for any damages that may, directly or indirectly, happen because of your use of this code. Don't say you were not warned.

Introducing SimpleCrypt


The idea of SimpleCrypt is to provide some basic cryptographic capabilities for simple encryption. Only symmetric encryption is supported (= same key for encryption and decryption required), and there is no API for streaming data in and out like you often see with more advanced cyphers. If you need stronger cryptography, streaming cyphers or asymmetric keys, take a look at QCA instead.

The SimpleCrypt class takes a 64 bits key in the form of a quint64. If you use a fixed key build into your program, you can do something like this to initialize your SimpleCrypt object:

SimpleCrypt crypto(Q_UINT64_C(0x0c2ad4a4acb9f023)); //some random number

But you can also use the

SimpleCrypt crypto();
crypto.setKey(0x0c2ad4a4acb9f023)

method to set a key on the class.

Selecting a suitable key

To get such random numbers, you might use this service and pick four of the 2 byte hexadecimals that you concatenate together as done above. That works better than trying to make up something semi-random yourself. Of course, you can also use other means to get to a quint64 key, such as using some hash of a password and reducing that to 64 bits.

SimpleCrypt in use

Once setup, you can use SimpleCrypt to actually encrypt or decrypt data. For the encrypt functions, both the plaintext that you input as the cyphertext that is being outputted can be either a QString or a QByteArray. The reverse applies to the decrypt functions. That results in four encrypt methods, and four corresponding decrypt methods:

Encryption methods
  • QString encryptToString(const QString& plaintext)
    
  • QString encryptToString(QByteArray plaintext)
    
  • QByteArray encryptToByteArray(const QString& plaintext)
    
  • QByteArray encryptToByteArray(QByteArray plaintext)
    
Decryption methods
  • QString decryptToString(const QString& plaintext)
    
  • QString decryptToString(QByteArray plaintext)
    
  • QByteArray decryptToByteArray(const QString& plaintext)
    
  • QByteArray decryptToByteArray(QByteArray plaintext)
    

Use with QStrings

 SimpleCrypt crypto(Q_UINT64_C(0x0c2ad4a4acb9f023)); //some random number
 QString testString("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
 QString result = crypto.encryptToString(testString);
 QString decrypted = crypto.decryptToString(result);
 qDebug() << testString << endl << result << endl << decrypted;

The code above encrypts a "QString" to a "QString", and then decrypts that string again to a new "QString".

Use with binary data

If you want to encrypt your own binary data, you might do something like this:

 //setup our objects
 SimpleCrypt crypto(Q_UINT64_C(0x0c2ad4a4acb9f023)); //some random number
 crypto.setCompressionMode(SimpleCrypt::CompressionAlways); //always compress the data, see section below
 crypto.setIntegrityProtectionMode(SimpleCrypt::ProtectionHash); //properly protect the integrity of the data
 QBuffer buffer;
 buffer.open(QIODevice::WriteOnly);
 QDataStream s(&buffer);
 //stream the data into our buffer
 s.setVersion(QDataStream::Qt_4_7);
 s << myString; //stream in a string
 s << myUint16; //stream in an unsigned integer
 s << myImage; //stream in an image
 s << someMoreData;
 // … etc.

QByteArray myCypherText = crypto.encryptToByteArray(buffer.data());
 if (crypto.lastError() == SimpleCrypt::ErrorNoError) {
 // do something relevant with the cyphertext, such as storing it or sending it over a socket to another machine.
 }
 buffer.close();

Note the use of the data protection feature. I would recommend that you use at least the ProtectionChecksum level of protection (enabled by default) if you work with binary data, as streaming invalid binary data into your program can lead to big problems.

On the other end, you can now do:

 SimpleCrypt crypto(Q_UINT64_C(0x0c2ad4a4acb9f023)); //same random number: key should match encryption key
 QByteArray plaintext = crypto.decryptToByteArray(myCypherText);
 if (!crypto.lastError() == SimpleCrypt::ErrorNoError) {
 // check why we have an error, use the error code from crypto.lastError() for that
 return;
 }

QBuffer buffer(&plaintext);
 buffer.open(QIODevice::ReadOnly);
 QDataStream s(&buffer);
 s.setVersion(QDataStream::Qt_4_7);
 s >> myString; //stream in a string
 s >> myUint16; //stream in an unsigned integer
 s >> myImage; //stream in an image
 s >> someMoreData;
 //etc.

buffer.close();

The code above might crash if the integrity of the data is not guaranteed, for instance by the use of a wrong password. Using the highest level of protection is probably OK here, as we have quite a big blob of data already, so adding the overhead of adding a 20 byte SHA1 cryptographic hash is justifiable.

Compression


For long strings, it can be beneficial to use compression before encryption. Not only does the length of the string decrease, the input will be a bit more random-looking too, resulting in a harder to break cyphertext (at least, in principle, see the warning at the top of this page.) However, for short strings, applying compression can lead to an increase in the string length.

SimpleCrypt supports three modes for compression before encryption. You can force to always use compression, to never use compression, or to automatically choose the shortest version (either the compressed one or the uncompressed one). The last option is the default option. Note that setting this only affects encryption. For decryption, SimpleCrypt automatically decompresses the data if compression was used for the encryption.

The code


The source code of SimpleCrypt consists of two files: the header (.h) and the source (.cpp).

simplecrypt.h

/*
Copyright (c) 2011, Andre Somers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Rathenau Instituut, Andre Somers nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

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

#ifndef SIMPLECRYPT_H
#define SIMPLECRYPT_H
#include <QString>
#include <QVector>
#include <QFlags>

/**
  @short Simple encryption and decryption of strings and byte arrays

  This class provides a simple implementation of encryption and decryption
  of strings and byte arrays.

  @warning The encryption provided by this class is NOT strong encryption. It may
  help to shield things from curious eyes, but it will NOT stand up to someone
  determined to break the encryption. Don't say you were not warned.

  The class uses a 64 bit key. Simply create an instance of the class, set the key,
  and use the encryptToString() method to calculate an encrypted version of the input string.
  To decrypt that string again, use an instance of SimpleCrypt initialized with
  the same key, and call the decryptToString() method with the encrypted string. If the key
  matches, the decrypted version of the string will be returned again.

  If you do not provide a key, or if something else is wrong, the encryption and
  decryption function will return an empty string or will return a string containing nonsense.
  lastError() will return a value indicating if the method was succesful, and if not, why not.

  SimpleCrypt is prepared for the case that the encryption and decryption
  algorithm is changed in a later version, by prepending a version identifier to the cypertext.
  */
class SimpleCrypt
{
public:
    /**
      CompressionMode describes if compression will be applied to the data to be
      encrypted.
      */
    enum CompressionMode {
        CompressionAuto,    /*!< Only apply compression if that results in a shorter plaintext. */
        CompressionAlways,  /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
        CompressionNever    /*!< Never apply compression. */
    };
    /**
      IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
      or wrong decryption keys.

      Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
      increases the length of the resulting cypertext, but makes it possible to check if the plaintext
      appears to be valid after decryption.
    */
    enum IntegrityProtectionMode {
        ProtectionNone,    /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
        ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
        ProtectionHash     /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
    };
    /**
      Error describes the type of error that occured.
      */
    enum Error {
        ErrorNoError,         /*!< No error occurred. */
        ErrorNoKeySet,        /*!< No key was set. You can not encrypt or decrypt without a valid key. */
        ErrorUnknownVersion,  /*!< The version of this data is unknown, or the data is otherwise not valid. */
        ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
    };

    /**
      Constructor.

      Constructs a SimpleCrypt instance without a valid key set on it.
     */
    SimpleCrypt();
    /**
      Constructor.

      Constructs a SimpleCrypt instance and initializes it with the given @arg key.
     */
    explicit SimpleCrypt(quint64 key);

    /**
      (Re-) initializes the key with the given @arg key.
      */
    void setKey(quint64 key);
    /**
      Returns true if SimpleCrypt has been initialized with a key.
      */
    bool hasKey() const {return !m_keyParts.isEmpty();}

    /**
      Sets the compression mode to use when encrypting data. The default mode is Auto.

      Note that decryption is not influenced by this mode, as the decryption recognizes
      what mode was used when encrypting.
      */
    void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
    /**
      Returns the CompressionMode that is currently in use.
      */
    CompressionMode compressionMode() const {return m_compressionMode;}

    /**
      Sets the integrity mode to use when encrypting data. The default mode is Checksum.

      Note that decryption is not influenced by this mode, as the decryption recognizes
      what mode was used when encrypting.
      */
    void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
    /**
      Returns the IntegrityProtectionMode that is currently in use.
      */
    IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}

    /**
      Returns the last error that occurred.
      */
    Error lastError() const {return m_lastError;}

    /**
      Encrypts the @arg plaintext string with the key the class was initialized with, and returns
      a cyphertext the result. The result is a base64 encoded version of the binary array that is the
      actual result of the string, so it can be stored easily in a text format.
      */
    QString encryptToString(const QString& plaintext) ;
    /**
      Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
      a cyphertext the result. The result is a base64 encoded version of the binary array that is the
      actual result of the encryption, so it can be stored easily in a text format.
      */
    QString encryptToString(QByteArray plaintext) ;
    /**
      Encrypts the @arg plaintext string with the key the class was initialized with, and returns
      a binary cyphertext in a QByteArray the result.

      This method returns a byte array, that is useable for storing a binary format. If you need
      a string you can store in a text file, use encryptToString() instead.
      */
    QByteArray encryptToByteArray(const QString& plaintext) ;
    /**
      Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
      a binary cyphertext in a QByteArray the result.

      This method returns a byte array, that is useable for storing a binary format. If you need
      a string you can store in a text file, use encryptToString() instead.
      */
    QByteArray encryptToByteArray(QByteArray plaintext) ;

    /**
      Decrypts a cyphertext string encrypted with this class with the set key back to the
      plain text version.

      If an error occured, such as non-matching keys between encryption and decryption,
      an empty string or a string containing nonsense may be returned.
      */
    QString decryptToString(const QString& cyphertext) ;
    /**
      Decrypts a cyphertext string encrypted with this class with the set key back to the
      plain text version.

      If an error occured, such as non-matching keys between encryption and decryption,
      an empty string or a string containing nonsense may be returned.
      */
    QByteArray decryptToByteArray(const QString& cyphertext) ;
    /**
      Decrypts a cyphertext binary encrypted with this class with the set key back to the
      plain text version.

      If an error occured, such as non-matching keys between encryption and decryption,
      an empty string or a string containing nonsense may be returned.
      */
    QString decryptToString(QByteArray cypher) ;
    /**
      Decrypts a cyphertext binary encrypted with this class with the set key back to the
      plain text version.

      If an error occured, such as non-matching keys between encryption and decryption,
      an empty string or a string containing nonsense may be returned.
      */
    QByteArray decryptToByteArray(QByteArray cypher) ;

    //enum to describe options that have been used for the encryption. Currently only one, but
    //that only leaves room for future extensions like adding a cryptographic hash...
    enum CryptoFlag{CryptoFlagNone = 0,
                    CryptoFlagCompression = 0x01,
                    CryptoFlagChecksum = 0x02,
                    CryptoFlagHash = 0x04
                   };
    Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
private:

    void splitKey();

    quint64 m_key;
    QVector<char> m_keyParts;
    CompressionMode m_compressionMode;
    IntegrityProtectionMode m_protectionMode;
    Error m_lastError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)

#endif // SimpleCrypt_H

simplecrypt.cpp

/*
Copyright (c) 2011, Andre Somers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Rathenau Instituut, Andre Somers nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "simplecrypt.h"
#include <QByteArray>
#include <QtDebug>
#include <QtGlobal>
#include <QDateTime>
#include <QCryptographicHash>
#include <QDataStream>

SimpleCrypt::SimpleCrypt():
    m_key(0),
    m_compressionMode(CompressionAuto),
    m_protectionMode(ProtectionChecksum),
    m_lastError(ErrorNoError)
{
    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
}

SimpleCrypt::SimpleCrypt(quint64 key):
    m_key(key),
    m_compressionMode(CompressionAuto),
    m_protectionMode(ProtectionChecksum),
    m_lastError(ErrorNoError)
{
    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
    splitKey();
}

void SimpleCrypt::setKey(quint64 key)
{
    m_key = key;
    splitKey();
}

void SimpleCrypt::splitKey()
{
    m_keyParts.clear();
    m_keyParts.resize(8);
    for (int i=0;i<8;i++) {
        quint64 part = m_key;
        for (int j=i; j>0; j--)
            part = part >> 8;
        part = part & 0xff;
        m_keyParts[i] = static_cast<char>(part);
    }
}

QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
{
    QByteArray plaintextArray = plaintext.toUtf8();
    return encryptToByteArray(plaintextArray);
}

QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
{
    if (m_keyParts.isEmpty()) {
        qWarning() << "No key set.";
        m_lastError = ErrorNoKeySet;
        return QByteArray();
    }


    QByteArray ba = plaintext;

    CryptoFlags flags = CryptoFlagNone;
    if (m_compressionMode == CompressionAlways) {
        ba = qCompress(ba, 9); //maximum compression
        flags |= CryptoFlagCompression;
    } else if (m_compressionMode == CompressionAuto) {
        QByteArray compressed = qCompress(ba, 9);
        if (compressed.count() < ba.count()) {
            ba = compressed;
            flags |= CryptoFlagCompression;
        }
    }

    QByteArray integrityProtection;
    if (m_protectionMode == ProtectionChecksum) {
        flags |= CryptoFlagChecksum;
        QDataStream s(&integrityProtection, QIODevice::WriteOnly);
        s << qChecksum(ba.constData(), ba.length());
    } else if (m_protectionMode == ProtectionHash) {
        flags |= CryptoFlagHash;
        QCryptographicHash hash(QCryptographicHash::Sha1);
        hash.addData(ba);

        integrityProtection += hash.result();
    }

    //prepend a random char to the string
    char randomChar = char(qrand() & 0xFF);
    ba = randomChar + integrityProtection + ba;

    int pos(0);
    char lastChar(0);

    int cnt = ba.count();

    while (pos < cnt) {
        ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
        lastChar = ba.at(pos);
        ++pos;
    }

    QByteArray resultArray;
    resultArray.append(char(0x03));  //version for future updates to algorithm
    resultArray.append(char(flags)); //encryption flags
    resultArray.append(ba);

    m_lastError = ErrorNoError;
    return resultArray;
}

QString SimpleCrypt::encryptToString(const QString& plaintext)
{
    QByteArray plaintextArray = plaintext.toUtf8();
    QByteArray cypher = encryptToByteArray(plaintextArray);
    QString cypherString = QString::fromLatin1(cypher.toBase64());
    return cypherString;
}

QString SimpleCrypt::encryptToString(QByteArray plaintext)
{
    QByteArray cypher = encryptToByteArray(plaintext);
    QString cypherString = QString::fromLatin1(cypher.toBase64());
    return cypherString;
}

QString SimpleCrypt::decryptToString(const QString &cyphertext)
{
    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
    QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
    QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());

    return plaintext;
}

QString SimpleCrypt::decryptToString(QByteArray cypher)
{
    QByteArray ba = decryptToByteArray(cypher);
    QString plaintext = QString::fromUtf8(ba, ba.size());

    return plaintext;
}

QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
{
    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
    QByteArray ba = decryptToByteArray(cyphertextArray);

    return ba;
}

QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
{
    if (m_keyParts.isEmpty()) {
        qWarning() << "No key set.";
        m_lastError = ErrorNoKeySet;
        return QByteArray();
    }

    QByteArray ba = cypher;

    if( cypher.count() < 3 )
        return QByteArray();

    char version = ba.at(0);

    if (version !=3) {  //we only work with version 3
        m_lastError = ErrorUnknownVersion;
        qWarning() << "Invalid version or not a cyphertext.";
        return QByteArray();
    }

    CryptoFlags flags = CryptoFlags(ba.at(1));

    ba = ba.mid(2);
    int pos(0);
    int cnt(ba.count());
    char lastChar = 0;

    while (pos < cnt) {
        char currentChar = ba[pos];
        ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
        lastChar = currentChar;
        ++pos;
    }

    ba = ba.mid(1); //chop off the random number at the start

    bool integrityOk(true);
    if (flags.testFlag(CryptoFlagChecksum)) {
        if (ba.length() < 2) {
            m_lastError = ErrorIntegrityFailed;
            return QByteArray();
        }
        quint16 storedChecksum;
        {
            QDataStream s(&ba, QIODevice::ReadOnly);
            s >> storedChecksum;
        }
        ba = ba.mid(2);
        quint16 checksum = qChecksum(ba.constData(), ba.length());
        integrityOk = (checksum == storedChecksum);
    } else if (flags.testFlag(CryptoFlagHash)) {
        if (ba.length() < 20) {
            m_lastError = ErrorIntegrityFailed;
            return QByteArray();
        }
        QByteArray storedHash = ba.left(20);
        ba = ba.mid(20);
        QCryptographicHash hash(QCryptographicHash::Sha1);
        hash.addData(ba);
        integrityOk = (hash.result() == storedHash);
    }

    if (!integrityOk) {
        m_lastError = ErrorIntegrityFailed;
        return QByteArray();
    }

    if (flags.testFlag(CryptoFlagCompression))
        ba = qUncompress(ba);

    m_lastError = ErrorNoError;
    return ba;
}

You can copy/paste the code above into your own simplecrypt.h and simplecrypt.cpp files to use it.

Details on the algorithm


The algorithm and data format used in this class, have been detailed on a separate page. On that page, you can find byte-for-byte descriptions of how the cyphertext is constructed from the plain text.


Future extensions


I, or somebody else, may choose to extend SimpleCrypt in the future, or fix a weakness of some kind. To make that possible without losing the option to read your older encrypted data, the generated cyphertext contains a version number. The idea is that future versions of this code will have a higher version number, but will keep supporting the decryption of cyphertexts with a lower version number. The current version number is 2. Note that these version numbers need not coincide with code version numbers, as a change in the code does not always result in a change in the actual encryption.

Versions


The latest version of the code is 3.1

Version 3.1

The code is now compatible with Qt5 and higher

Version 3

Fixed embarrassing mistake with only using 4 out of 8 of the key bytes. Thanks to Gerolf for noticing!

Version 2

  • Renamed flags to avoid clashes
  • Upped version number to 2, but did not retain backwards compatibility
  • Added optional data integrity protection using either a checksum or a cryptographic hash
  • Added error reporting (making is necessary to make the encryption and decryption functions non-const)

Version 1

This marks the first public release.