#include <QSSLDevice.h>
Inheritance diagram for FX::QSSLDevice:
Thanks to the wonderful flexibility of the OpenSSL encryption library, TnFOX can offer completely integrated encryption facilities which can work with any FX::QIODevice. While the most common use will be with FX::QBlkSocket, you could just as easily apply it to file data or indeed anything else. Synchronous devices work the full SSL/TLS negotiation protocol whereas file devices simply apply symmetric or asymmetric encryption based on the FX::FXSSLKey you provide.
QSSLDevice offers SSL v2/v3 & TLS v1 protocols with RC2, RC4, DES, Blowfish, IDEA, 3DES & AES symmetric encryption and RSA asymmetric encryption (ie; public-key) to any bit length. Furthermore RSA, Diffie-Hellman and DSS authentication methods are available. Within these, you have the strongest available encryption currently known.
Unfortunately usage of strong encryption in certain ways is illegal in many illiberal countries (such as the US and some countries in Europe). Some countries won't let you export it, many limit the maximum key length, most require key escrow (where your private key can be obtained by court order and failure to comply results in prison) and in some usage is completely illegal altogether. Where things get even more fun is that you can do something with encryption legal in your country of residence but if you take a holiday to the US say, there they can put you in prison for a very long time.
Algorithm | Legal State | Where |
---|---|---|
SSL itself | Royalty free unless you sue Netscape | ? |
DES | Royalty free | ? |
3DES | Royalty free | ? |
Blowfish | Unpatented | Everywhere |
IDEA | Patented till 2011 | USA,Europe |
AES | Royalty free? | Everywhere? |
RC5 | Patented | USA,Japan,Europe in 2003 |
RSA | Unpatented | Patent expired in 2000 |
Diffie-Hellman | Patent expired in 1997? | ? |
DSA/DSS | Royalty free | Everywhere |
The first QSSLDevice created in the process will take the longest as required data structures are cached in-memory and the random number generator seeded with 4096 bits of randomness. Like most FX::QIODevice's, QSSLDevice is thread-safe[1] though FX::FXSSLKey is not. If the OpenSSL library was not available at compile-time, the first QSSLDevice created throws an exception of code QSSLDEVICE_NOTENABLED.
At the time of writing (August 2003), the minimum symmetric encryption key length should be 128 bits and the minimum asymmetric encryption key length should be 2048 to 3072 bits. If your local legal situation permits it, higher is better though bear in mind that with asymmetric encryption especially, bigger keys means substantial increases in encoding and decoding time.
Algorithm | Strength | Notes |
---|---|---|
DES | 56 bit | You don't want to use this except for legacy applications |
3DES | 112 bit | Triple DES is 168 bit but due to a weakness is effectively 112 bit It's also very slow thus probably best to avoid this too |
Blowfish | 32-448 | Especially useful for file encryption as it's fast |
IDEA | 128 | Patented for commercial use |
AES | 128/192/256 | The official US government symmetric encryption standard |
RSA | 512+ | The traditional asymmetric algorithm |
DH (Diffie-Hellman) | 512+ | The default for non-authenticated connections |
DSA/DSS | 512+ | Used only for authentication |
Furthermore there are two hashing algorithms available: MD5 and SHA1. MD5 has a collision weakness so use SHA1 where possible. SHA1 was formerly the US government standard cryptographic hash.
The default settings create a SSL v3 & TLS only capable device with ciphers set to "HIGH:@STRENGTH"
ie; only the strongest encryption ordered by strength (which are currently SHA1 with AES(256) or 3DES and RSA or DH) but with non-authenticated protocols (ie; certificates are optional). These settings aren't very compatible with most existing servers on the internet so using setCiphers() you may wish to set "DEFAULT:@STRENGTH"
(all less non-authenticated protocols) or "ALL:@STRENGTH"
(all protocols including non-authenticated). See the OpenSSL documentation for example cipher strings and how they should be formatted.
There is some basic support for certificates. You can specify a private key file and certificate file using setPrivateKeyFile() and setCertificateFile() plus you can compare FX::FXNetwork::dnsReverseLookup() with peerHostNameByCertificate(). All files must be in PEM/X509 format.
All encryption methods are weaker if the data they are encrypting has something known about it eg; it is HTML or ASCII text. Another source of weakness is verbosity - the more data there is to analyse, the easier to crack. The simple solution to this is to apply Lempel-Ziv compression on the data before encrypting using something like FX::QGZipDevice. See the examples below.
FX::Secure::Randomness is a high quality source of randomness and thus can take a very long time to generate 4096 bits. If less than 4096 bits is available then to prevent applications just sitting around waiting for it, QSSLDevice uses a number of sources of instantaneous entropy to seed the cryptographically secure Pseudo-Random Number Generator (PNRG):
/dev/urandom
(FX::Secure::Randomness uses /dev/random
)
It is best to use TLS v1 (which is SSL v3.1), then SSL v3 and lastly SSL v2 only if you absolutely have to due to compatibility reasons. Because SSL v2 has security issues, by default it is disabled though you can still connect to SSL v2 servers (you can change this in the constructor parameters). If you enable everything, a negotiation procedure is followed at the start of the connection to agree the best available protocol.
If you don't bother with certificates, the default settings of QSSLDevice use anonymous Diffie-Hellman for key exchange using an internal list of primes for generation of unique keys. A new key is generated per new connection. Obviously this mechanism is liable to man-in-the-middle attack (ie; you can't be sure who you're connecting to is actually who you think), but if you merely want your data going over the wire to be unintelligible, this is very sufficient.
[1]: OpenSSL itself is only partially threadsafe - in particular, it is not threadsafe when multiple threads use a SSL connection at the same time (which unfortunately TnFOX requires as this is what the synchronous i/o model requires). QSSLDevice sets some conservative options in OpenSSL to prevent packet fragmentation (which would cause reads during writes or writes during reads) and also serialises all reads and writes ie; only one read and write may happen at once - but both a read and a write concurrently. This appears to be safe from testing, but internal changes to OpenSSL may cause future breakage.
Most of the focus so far in this documentation has been for encrypting communications. However, if you have some data to which you want to FXRESTRICT access, QSSLDevice can also apply straight off symmetric or asymmetric encryption to raw data.
Asymmetric encryption has been implemented as symmetric encryption but encrypting the symmetric key with asymmetric encryption and embedding it with the data. This makes things substantially easier never mind improving performance. You typically use asymmetric encryption if your connection to the destination of the data is insecure (eg; internet, postal mail, telephone etc) whereby your recipient generates a RSA public/private key pair and sends you the public part as a PEM or X509 format file. Read that into a FX::FXSSLPKey and write out your data. Now only your intended recipient can read the data.
For symmetric encryption, your two likely choices for ciphers are Blowfish and AES. Both are relatively fast (~40Mb/sec on my machine) and both scale well with key bit size (AES is 40% slower with 256 bit keys than 128 bit). The other ciphers have been left out as they have known weaknesses or are patented.
Strongly consider setting the FX::QIODeviceFlags::IO_ShredTruncate bit when opening any secure file.
Now OFB and CTR modes are weaker than CFB and CBC as the plaintext has no effect on the encryption - it is determined entirely by starting conditions. CTR mode has the advantage of instant seeks whereas OFB must be iterated from beginning to the seek point on each seek - so I have opted for CTR mode despite that it is probably slightly weaker.
Performance-wise, throughput is heavily dependent on the speed at which your processor can XOR portions of memory. There is an SSE2 instruction for XORing 16 bytes at a time, but it requires 16 byte aligned memory which is highly unlikely in general purpose usage. This implementation does make use of memory alignment up to eight (for which the src, dest AND offset inside encrypted stream buffer must all be multiples of eight) but profiling shows that even that is rarely used compared against the four byte aligned XOR on 32 bit systems. If however you are using a cipher with a large (>16) block size, you could see substantial speed increases if you always pass a buffer to readBlock() and writeBlock() which is eight byte aligned.
Usage is as with all things in TnFOX, ridiculously easy:
Communication-type use (synchronous):
QBlkSocket myserversocket; ... QBlkSocket newsocketraw=myserversocket.waitForConnection(); // Perhaps spin off a new thread, or authenticate using FX::FXSRP first // You will need a try...catch() block as negotiation may fail QSSLDevice newsocket(&newsocketraw); newsocket.create(newsocketraw.mode()); newsocket.read(NULL, 0); // Just negotiate, don't read if(newsocket.peerHostNameByCertificate()!=FXNetwork::dnsReverseLookup(newsocketraw.peerAddress())) reject; ...
File-type use:
QMemMap myfileraw("myencryptedfile.txt"); QSSLDevice myfile(&myfileraw); // Get password from user into FXString mypassword myfile.setKey(FXSSLKey(352, FXSSLKey::Blowfish, mypassword)); myfile.open(IO_ReadOnly); // Read what you like as normal ...
QMemMap myencryptedfile("myencryptedfile.bin"); QSSLDevice myfileencryptor(&myencryptedfile); myfileencryptor.setKey(thekey); QGZipDevice myfilecompressor(&myfileencryptor); QIODevice *myfile=&myfilecompressor; FXStream s(myfile); myfile->open(IO_WriteOnly); s << "Some text to compress, then encrypt, then write to a memory mapped file"; myfile->close();
Just especially as a note to myself who keeps forgetting how I'm supposed to use asymmetric encryption, here's how you do it:
FXSSLPKey &thekey; FXSSLPKey pthekey(thekey.publicKey()); QMemMap myencryptedfile("myencryptedfile.bin"); QSSLDevice myfileencryptor(&myencryptedfile); FXSSLKey tempkey(128, FXSSLKey::AES); tempkey.setAsymmetricKey(&pthekey); myfileencryptor.setKey(tempkey); QGZipDevice myfilecompressor(&myfileencryptor); QIODevice *myfile=&myfilecompressor; FXStream s(myfile); myfile->open(IO_WriteOnly); s << "Some text to compress, then encrypt, then write to a memory mapped file"; myfile->close();
FXSSLPKey &thekey; QSSLDevice &myfileencryptor; myfileencryptor.setKey(FXSSLKey().setAsymmetricKey(&thekey)); ...
QSSLDevice uses a proprietary file format for its secure files. Sorry about this, I did look at the OpenPGP file format and concluded it was too much hassle. I just wanted a basic secure file format with no bells or whistles.
Cryptoanalysis: As I previously mentioned, I've never touched cryptography before writing this class and while I have learned lots in the past few weeks, I cannot say I am experienced. What I have done is work on the basis that the less information the attacker has, the better. I've also used what the internet says is best practice though I really need the book by Bruce Schneier (if I had the money, I would). Below I present an analysis of my file format to aid others in finding any weaknesses I may have introduced:
An attacker can not know from the file format:
This salting of the hash is disabled however when salting of the key is enabled because of performance reasons. Salting of the key is performed by EORing in random data prior to encryption and up to eight bytes may be used (ie; 64 bits). Key salting should be enabled when the entropic quality of the key is low eg; if it is generated from plaintext.
For keys generated from plaintext, by default the hashing function(s) in FXSSLKey::generateFromText() is run 65536 times and 16 bits of salting set. This means an average of (80^passwordlen)*65536*O(pwhash)*65536*O(hash)/2 which with 128 bit key and a seven character password is 9 with twenty-two zeros after it Tiger hashs. If a straight off attack were used because of the unsalted hash, that would be 17 with thirty-seven zeros after it Tiger hashs - basically, we sacrifice brute-force attack strength for strength in key generation from plaintext. Also of course attacking the
cipher directly is made much harder due to greater key entropy.
This class uses the excellent OpenSSL library developed by Eric Young, Tim Hudson & the OpenSSL team. Since this library does not include any actual OpenSSL code, TnFOX does not need to state:
This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)
Definition at line 670 of file QSSLDevice.h.
Public Types | |
typedef FXfval | Offset |
Default | |
Unix | |
MacOS | |
MSDOS | |
NoTranslation | |
UTF8 | |
UTF16 | |
UTF16LE | |
UTF32 | |
UTF32LE | |
enum | CRLFType { Default, Unix, MacOS, MSDOS } |
enum | UnicodeType { NoTranslation, UTF8, UTF16, UTF16LE, UTF32, UTF32LE } |
Public Member Functions | |
QSSLDevice (QIODevice *encrypteddev=0, bool enablev2=false) | |
~QSSLDevice () | |
QIODevice * | encryptedDev () const throw () |
void | setEncryptedDev (QIODevice *dev) |
const FXSSLKey & | key () const |
void | setKey (const FXSSLKey &key) |
bool | SSLv2Available () const throw () |
void | setSSLv2Available (bool a) |
bool | SSLv3Available () const throw () |
void | setSSLv3Available (bool a) |
FXString | ciphers () const |
void | setCiphers (const FXString &list) |
bool | usingSSLv2 () const |
bool | usingSSLv3 () const |
bool | usingTLSv1 () const |
FXString | peerHostNameByCertificate () const |
FXString | cipherName () const |
FXuint | cipherBits () const |
FXString | cipherDescription () const |
void | renegotiate () |
FXuint | fileHeaderLen () const throw () |
virtual bool | isSynchronous () const |
virtual bool | create (FXuint mode=IO_ReadWrite) |
virtual bool | open (FXuint mode=IO_ReadWrite) |
virtual void | close () |
virtual void | flush () |
virtual FXfval | size () const |
virtual void | truncate (FXfval size) |
virtual FXfval | at () const |
virtual bool | at (FXfval newpos) |
virtual bool | atEnd () const |
virtual const FXACL & | permissions () const |
virtual void | setPermissions (const FXACL &) |
virtual FXuval | readBlock (char *data, FXuval maxlen) |
virtual FXuval | writeBlock (const char *data, FXuval maxlen) |
virtual FXuval | readBlockFrom (char *data, FXuval maxlen, FXfval pos) |
virtual FXuval | writeBlockTo (FXfval pos, const char *data, FXuval maxlen) |
virtual int | ungetch (int c) |
FXuval | readBlockFrom (FXuchar *data, FXuval maxlen, FXfval pos) |
FXuval | writeBlockTo (FXfval pos, const FXuchar *data, FXuval maxlen) |
FXuint | flags () const |
FXuint | mode () const |
FXuint | state () const |
CRLFType | crlfFormat () const |
void | setCRLFFormat (CRLFType type) |
UnicodeType | unicodeTranslation () const |
void | setUnicodeTranslation (UnicodeType type) |
bool | isBuffered () const |
bool | isRaw () const |
bool | isTranslated () const |
bool | isUTF16Translated () const |
bool | isUTF32Translated () const |
bool | isReadable () const |
bool | isWriteable () const |
bool | isWritable () const |
bool | isReadWrite () const |
bool | isClosed () const |
bool | isInactive () const |
bool | isOpen () const |
FXuval | readBlock (FXuchar *data, FXuval maxlen) |
FXuval | writeBlock (const FXuchar *data, FXuval maxlen) |
virtual FXuval | readLine (char *data, FXuval maxlen) |
virtual int | getch () |
virtual int | putch (int c) |
FXfval | shredData (FXfval offset, FXfval len=(FXfval)-1) |
Static Public Member Functions | |
static void | setCertificateFile (const FXString &path) |
static void | setPrivateKeyFile (const FXString &path, const FXString &password) |
static const FXString & | strongestAnonCipher () |
static const FXString & | fastestAnonCipher () |
static bool | waitForData (QIODeviceS **signalled, FXuint no, QIODeviceS **list, FXuint waitfor=FXINFINITE) |
static FXuint | waitForDataMax () throw () |
static UnicodeType | determineUnicodeType (FXuchar *data, FXuval len) throw () |
static FXuval | applyCRLF (FXuchar *FXRESTRICT output, const FXuchar *FXRESTRICT input, FXuval outputlen, FXuval &inputlen, CRLFType crlftype=Default, UnicodeType utftype=NoTranslation) |
static FXuval | removeCRLF (FXuchar *FXRESTRICT output, const FXuchar *FXRESTRICT input, FXuval outputlen, FXuval &inputlen, UnicodeType utftype=NoTranslation) |
Protected Member Functions | |
void | setFlags (int f) |
void | setMode (int m) |
void | setState (int s) |
Protected Attributes | |
FXfval | ioIndex |
Friends | |
FXAPI FXStream & | operator<< (FXStream &s, QIODevice &i) |
FXAPI FXStream & | operator>> (FXStream &s, QIODevice &i) |