Simple Crypt IO Device: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[[Category:Snippets]]
[[Category:Snippets]]


[toc align_right="yes" depth="3"]
[toc align_right="yes" depth="3"]


= Writing a Custom I/O Device with encryption via SimpleCrypt class =
= Writing a Custom I/O Device with encryption via SimpleCrypt class =
Line 7: Line 7:
Creating a custom IO device was already described in [[Custom_IO_Device|Writing a Custom I/O Device]]. The encryption is used from [[Simple_encryption|Simple encryption with SimpleCrypt]].
Creating a custom IO device was already described in [[Custom_IO_Device|Writing a Custom I/O Device]]. The encryption is used from [[Simple_encryption|Simple encryption with SimpleCrypt]].


The example app can be found on gitorious: "qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide":https://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide .
The example app can be found on gitorious: "qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide":https://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide .


== Usage ==
== Usage ==
Line 13: Line 13:
The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:
The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:


<code><br /> QFile file&amp;amp;#40;&quot;output.dat&amp;quot;&amp;#41;;<br /> SimpleCryptDevice device(&amp;file); // stream to store the encrypted data
<code>
QFile file("output.dat");
SimpleCryptDevice device(&amp;file); // stream to store the encrypted data


device.setBlockSize(256);<br /> device.setKey(Q_UINT64_C(0x0c2ad4a4acb9f023));<br /> device.setCompressionMode(SimpleCrypt::CompressionAlways);<br /> device.setIntegrityProtectionMode(SimpleCrypt::ProtectionHash);<br /> device.open(QIODevice::WriteOnly);<br /> QTextStream stream(&amp;device);<br /> out &lt;&lt; &quot;Hello World&amp;quot;;<br /> out &lt;&lt; &quot;My text to encrypt&amp;quot;;<br /></code>
device.setBlockSize(256);
device.setKey(Q_UINT64_C(0x0c2ad4a4acb9f023));
device.setCompressionMode(SimpleCrypt::CompressionAlways);
device.setIntegrityProtectionMode(SimpleCrypt::ProtectionHash);
device.open(QIODevice::WriteOnly);
QTextStream stream(&amp;device);
out << "Hello World";
out << "My text to encrypt";
</code>


The &lt;code&amp;gt;compressionMode&amp;lt;/code&amp;gt; and &lt;code&amp;gt;Integrity Protection&amp;lt;/code&amp;gt; can be changed if needed.
The <code>compressionMode</code> and <code>Integrity Protection</code> can be changed if needed.


Also, if needed, a signal &lt;code&amp;gt;blockWritten&amp;lt;/code&amp;gt; can be connected.
Also, if needed, a signal <code>blockWritten</code> can be connected.


== Implementation ==
== Implementation ==
Line 25: Line 35:
The basic implementation is the same as in [[Custom_IO_Device|Custom I/O Device]]. The big difference is, that the data can't be stored directly when the client writes it to the device, as the encryption/decryption is done block wise.
The basic implementation is the same as in [[Custom_IO_Device|Custom I/O Device]]. The big difference is, that the data can't be stored directly when the client writes it to the device, as the encryption/decryption is done block wise.


This means &lt;code&amp;gt;readData&amp;lt;/code&amp;gt; and &lt;code&amp;gt;writeData&amp;lt;/code&amp;gt; must be changed.
This means <code>readData</code> and <code>writeData</code> must be changed.


&lt;code&amp;gt;SimpleCryptIoDevice&amp;lt;/code&amp;gt; has a property blockSize. Data that is written is stored in an internal buffer of size blockSize. When the buffer size is reached, the data is encrypted and stored. This is needed, as &lt;code&amp;gt;SimpleCrypt&amp;lt;/code&amp;gt; (in it's used version) does not allow to encrypt to a stream.
<code>SimpleCryptIoDevice</code> has a property blockSize. Data that is written is stored in an internal buffer of size blockSize. When the buffer size is reached, the data is encrypted and stored. This is needed, as <code>SimpleCrypt</code> (in it's used version) does not allow to encrypt to a stream.


=== Efficiency ===
=== Efficiency ===


Note that because &lt;code&amp;gt;SimpleCrypt&amp;lt;/code&amp;gt; uses a header and both the compression and the data protection hash or checksum are calculated and stored at the the block level, using &lt;code&amp;gt;SimpleCryptDevice&amp;lt;/code&amp;gt; in this form results in a larger output stream than when using the &lt;code&amp;gt;SimpleCrypt&amp;lt;/code&amp;gt; class directly. Perhaps a future version of &lt;code&amp;gt;SimpleCrypt&amp;lt;/code&amp;gt; will support a streaming interface to increase efficiency in use cases such as these.
Note that because <code>SimpleCrypt</code> uses a header and both the compression and the data protection hash or checksum are calculated and stored at the the block level, using <code>SimpleCryptDevice</code> in this form results in a larger output stream than when using the <code>SimpleCrypt</code> class directly. Perhaps a future version of <code>SimpleCrypt</code> will support a streaming interface to increase efficiency in use cases such as these.


=== readData ===
=== readData ===
Line 37: Line 47:
For reading, alway a complete block must be read from the device. Then the needed data is moved to the data buffer of the client. As there might be data left in the buffer, each read furst gets the data of the internal buffer. when it's empty, new data is read from the underlying device.
For reading, alway a complete block must be read from the device. Then the needed data is moved to the data buffer of the client. As there might be data left in the buffer, each read furst gets the data of the internal buffer. when it's empty, new data is read from the underlying device.


<code><br />qint64 SimpleCryptDevice::readData(char* data, qint64 maxSize)<br />{<br /> int bytesRead = 0;<br /> if(!m_byteBuffer.isEmpty())<br /> {<br /> for(int copyByte = 0; copyByte &lt; qMin(m_byteBuffer.size(), (int)maxSize); +''copyByte,''+bytesRead)<br /> data[bytesRead] = m_byteBuffer[copyByte];
<code>
qint64 SimpleCryptDevice::readData(char* data, qint64 maxSize)
{
int bytesRead = 0;
if(!m_byteBuffer.isEmpty())
{
for(int copyByte = 0; copyByte < qMin(m_byteBuffer.size(), (int)maxSize); +''copyByte,''+bytesRead)
data[bytesRead] = m_byteBuffer[copyByte];


m_byteBuffer.remove(0, bytesRead);<br /> }
m_byteBuffer.remove(0, bytesRead);
}


while(m_byteBuffer.isEmpty() &amp;&amp; (bytesRead &lt; maxSize) &amp;&amp; !m_underlyingDevice-&gt;atEnd())<br /> {<br /> int sizeOfCypher = 0;<br /> int bytesReallyRead = m_underlyingDevice-&gt;read((char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));
while(m_byteBuffer.isEmpty() &amp;&amp; (bytesRead < maxSize) &amp;&amp; !m_underlyingDevice->atEnd())
{
int sizeOfCypher = 0;
int bytesReallyRead = m_underlyingDevice->read((char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));


if(bytesReallyRead != sizeof(sizeOfCypher))<br /> return <s>1;
if(bytesReallyRead != sizeof(sizeOfCypher))
<br /> QByteArray myCypherText;<br /> myCypherText.resize(sizeOfCypher);<br /> bytesReallyRead = m_underlyingDevice</s>&gt;read(myCypherText.data(), sizeOfCypher);
return -1;


if(bytesReallyRead != bytesRead)<br /> {<br /> m_byteBuffer = m_crypto.decryptToByteArray(myCypherText);<br /> if (m_crypto.lastError() != SimpleCrypt::ErrorNoError)<br /> {<br /> return <s>1;<br /> }<br /> else<br /> {<br /> int copyByte = 0;<br /> for(copyByte = 0; (copyByte &lt; m_byteBuffer.size()) &amp;&amp; (bytesRead &lt; (int)maxSize); +''copyByte,bytesRead)<br /> {<br /> data[bytesRead] = m_byteBuffer[copyByte];<br /> }<br /> m_byteBuffer.remove(0, copyByte);<br /> }<br /> }<br /> }<br /> return bytesRead;<br />}<br /></code>
QByteArray myCypherText;
<br />The stored data always contains an int with the size of the encrypted buffer.
myCypherText.resize(sizeOfCypher);
<br />h3. writeData
bytesReallyRead = m_underlyingDevice->read(myCypherText.data(), sizeOfCypher);
<br />To write the data to the underlying device, first the current block needs to be filled. To achieve this, all data is attached to the buffer &lt;code&amp;gt;m_byteBuffer&amp;lt;/code&amp;gt;. unless the buffer is smaller than the block size, one block is removed of the buffer and stored in the underlying device.
 
<br /><code><br />qint64 SimpleCryptDevice::writeData(const char* data, qint64 maxSize)<br />{<br /> m_byteBuffer.append(data, (int)maxSize);<br /> quint64 realBytesWritten = 0;
if(bytesReallyRead != bytesRead)
<br /> // always write blocks of m_blockSize bytes [[Image:|Image:]]!<br /> while(m_byteBuffer.size() &gt; m_blockSize)<br /> {<br /> QByteArray bytesToWrite = m_byteBuffer.left(m_blockSize);<br /> m_byteBuffer.remove(0, m_blockSize);
{
<br /> realBytesWritten''= writeBlock(bytesToWrite);<br /> }
m_byteBuffer = m_crypto.decryptToByteArray(myCypherText);
<br /> emit encryptedBytesWritten(realBytesWritten);
if (m_crypto.lastError() != SimpleCrypt::ErrorNoError)
<br /> return maxSize;<br />}<br /></code>
{
<br />writing one block is fairly easy. The block is encrypted by a call to &lt;code&amp;gt;SimpleCrypt::encryptToByteArray&amp;lt;/code&amp;gt; and the size of the encrypted data and the data itself is written to the underlying device.
return -1;
<br /><code><br />int SimpleCryptDevice::writeBlock(const QByteArray&amp;amp; bytesToWrite)<br />{<br /> quint64 realBytesWritten = 0;<br /> QByteArray myCypherBytes = m_crypto.encryptToByteArray(bytesToWrite); // cypher the bytes<br /> if (m_crypto.lastError() == SimpleCrypt::ErrorNoError)<br /> {<br /> // store the byte block incl. the size<br /> int sizeOfCypher = myCypherBytes.size();
}
<br /> realBytesWritten ''= m_underlyingDevice-&gt;write((const char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));<br /> realBytesWritten''= m_underlyingDevice</s>&gt;write(myCypherBytes.data(), sizeOfCypher);<br /> emit blockWritten();<br /> return realBytesWritten;<br /> }<br /> return 0;<br />}<br /></code>
else
{
int copyByte = 0;
for(copyByte = 0; (copyByte < m_byteBuffer.size()) &amp;&amp; (bytesRead < (int)maxSize); +''copyByte,bytesRead)
{
data[bytesRead] = m_byteBuffer[copyByte];
}
m_byteBuffer.remove(0, copyByte);
}
}
}
return bytesRead;
}
</code>
 
The stored data always contains an int with the size of the encrypted buffer.
 
h3. writeData
 
To write the data to the underlying device, first the current block needs to be filled. To achieve this, all data is attached to the buffer <code>m_byteBuffer</code>. unless the buffer is smaller than the block size, one block is removed of the buffer and stored in the underlying device.
 
<code>
qint64 SimpleCryptDevice::writeData(const char* data, qint64 maxSize)
{
m_byteBuffer.append(data, (int)maxSize);
quint64 realBytesWritten = 0;
 
// always write blocks of m_blockSize bytes [[Image:|Image:]]!
while(m_byteBuffer.size() > m_blockSize)
{
QByteArray bytesToWrite = m_byteBuffer.left(m_blockSize);
m_byteBuffer.remove(0, m_blockSize);
 
realBytesWritten''= writeBlock(bytesToWrite);
}
 
emit encryptedBytesWritten(realBytesWritten);
 
return maxSize;
}
</code>
 
writing one block is fairly easy. The block is encrypted by a call to <code>SimpleCrypt::encryptToByteArray</code> and the size of the encrypted data and the data itself is written to the underlying device.
 
<code>
int SimpleCryptDevice::writeBlock(const QByteArray&amp;amp; bytesToWrite)
{
quint64 realBytesWritten = 0;
QByteArray myCypherBytes = m_crypto.encryptToByteArray(bytesToWrite); // cypher the bytes
if (m_crypto.lastError() == SimpleCrypt::ErrorNoError)
{
// store the byte block incl. the size
int sizeOfCypher = myCypherBytes.size();
 
realBytesWritten ''= m_underlyingDevice->write((const char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));
realBytesWritten''= m_underlyingDevice->write(myCypherBytes.data(), sizeOfCypher);
emit blockWritten();
return realBytesWritten;
}
return 0;
}
</code>


To ensure no data is left when the device is closed, during close or destructor, the last buffer is flushed to the device.
To ensure no data is left when the device is closed, during close or destructor, the last buffer is flushed to the device.


<code><br />void SimpleCryptDevice::flushEnd()<br />{<br /> if(openMode() &amp; WriteOnly)<br /> {<br /> quint64 realBytesWritten = writeBlock(m_byteBuffer);<br /> emit encryptedBytesWritten(realBytesWritten);<br /> }<br />}<br /></code>
<code>
void SimpleCryptDevice::flushEnd()
{
if(openMode() &amp; WriteOnly)
{
quint64 realBytesWritten = writeBlock(m_byteBuffer);
emit encryptedBytesWritten(realBytesWritten);
}
}
</code>


That's all.
That's all.

Revision as of 09:55, 25 February 2015


[toc align_right="yes" depth="3"]

Writing a Custom I/O Device with encryption via SimpleCrypt class

Creating a custom IO device was already described in Writing a Custom I/O Device. The encryption is used from Simple encryption with SimpleCrypt.

The example app can be found on gitorious: "qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide":https://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-simplecryptiodevide .

Usage

The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:

 QFile file("output.dat");
 SimpleCryptDevice device(&amp;file); // stream to store the encrypted data

device.setBlockSize(256);
 device.setKey(Q_UINT64_C(0x0c2ad4a4acb9f023));
 device.setCompressionMode(SimpleCrypt::CompressionAlways);
 device.setIntegrityProtectionMode(SimpleCrypt::ProtectionHash);
 device.open(QIODevice::WriteOnly);
 QTextStream stream(&amp;device);
 out << "Hello World";
 out << "My text to encrypt";

The

compressionMode

and

Integrity Protection

can be changed if needed. Also, if needed, a signal

blockWritten

can be connected.

Implementation

The basic implementation is the same as in Custom I/O Device. The big difference is, that the data can't be stored directly when the client writes it to the device, as the encryption/decryption is done block wise.

This means

readData

and

writeData

must be changed.

SimpleCryptIoDevice

has a property blockSize. Data that is written is stored in an internal buffer of size blockSize. When the buffer size is reached, the data is encrypted and stored. This is needed, as

SimpleCrypt

(in it's used version) does not allow to encrypt to a stream.

Efficiency

Note that because

SimpleCrypt

uses a header and both the compression and the data protection hash or checksum are calculated and stored at the the block level, using

SimpleCryptDevice

in this form results in a larger output stream than when using the

SimpleCrypt

class directly. Perhaps a future version of

SimpleCrypt

will support a streaming interface to increase efficiency in use cases such as these.

readData

For reading, alway a complete block must be read from the device. Then the needed data is moved to the data buffer of the client. As there might be data left in the buffer, each read furst gets the data of the internal buffer. when it's empty, new data is read from the underlying device.

qint64 SimpleCryptDevice::readData(char* data, qint64 maxSize)
{
 int bytesRead = 0;
 if(!m_byteBuffer.isEmpty())
 {
 for(int copyByte = 0; copyByte < qMin(m_byteBuffer.size(), (int)maxSize); +''copyByte,''+bytesRead)
 data[bytesRead] = m_byteBuffer[copyByte];

m_byteBuffer.remove(0, bytesRead);
 }

while(m_byteBuffer.isEmpty() &amp;&amp; (bytesRead < maxSize) &amp;&amp; !m_underlyingDevice->atEnd())
 {
 int sizeOfCypher = 0;
 int bytesReallyRead = m_underlyingDevice->read((char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));

if(bytesReallyRead != sizeof(sizeOfCypher))
 return -1;

 QByteArray myCypherText;
 myCypherText.resize(sizeOfCypher);
 bytesReallyRead = m_underlyingDevice->read(myCypherText.data(), sizeOfCypher);

if(bytesReallyRead != bytesRead)
 {
 m_byteBuffer = m_crypto.decryptToByteArray(myCypherText);
 if (m_crypto.lastError() != SimpleCrypt::ErrorNoError)
 {
 return -1;
 }
 else
 {
 int copyByte = 0;
 for(copyByte = 0; (copyByte < m_byteBuffer.size()) &amp;&amp; (bytesRead < (int)maxSize); +''copyByte,bytesRead)
 {
 data[bytesRead] = m_byteBuffer[copyByte];
 }
 m_byteBuffer.remove(0, copyByte);
 }
 }
 }
 return bytesRead;
}

The stored data always contains an int with the size of the encrypted buffer.

h3. writeData

To write the data to the underlying device, first the current block needs to be filled. To achieve this, all data is attached to the buffer

m_byteBuffer

. unless the buffer is smaller than the block size, one block is removed of the buffer and stored in the underlying device.

qint64 SimpleCryptDevice::writeData(const char* data, qint64 maxSize)
{
 m_byteBuffer.append(data, (int)maxSize);
 quint64 realBytesWritten = 0;

 // always write blocks of m_blockSize bytes [[Image:|Image:]]!
 while(m_byteBuffer.size() > m_blockSize)
 {
 QByteArray bytesToWrite = m_byteBuffer.left(m_blockSize);
 m_byteBuffer.remove(0, m_blockSize);

 realBytesWritten''= writeBlock(bytesToWrite);
 }

 emit encryptedBytesWritten(realBytesWritten);

 return maxSize;
}

writing one block is fairly easy. The block is encrypted by a call to

SimpleCrypt::encryptToByteArray

and the size of the encrypted data and the data itself is written to the underlying device.

int SimpleCryptDevice::writeBlock(const QByteArray&amp;amp; bytesToWrite)
{
 quint64 realBytesWritten = 0;
 QByteArray myCypherBytes = m_crypto.encryptToByteArray(bytesToWrite); // cypher the bytes
 if (m_crypto.lastError() == SimpleCrypt::ErrorNoError)
 {
 // store the byte block incl. the size
 int sizeOfCypher = myCypherBytes.size();

 realBytesWritten ''= m_underlyingDevice->write((const char*)&amp;sizeOfCypher, sizeof(sizeOfCypher));
 realBytesWritten''= m_underlyingDevice->write(myCypherBytes.data(), sizeOfCypher);
 emit blockWritten();
 return realBytesWritten;
 }
 return 0;
}

To ensure no data is left when the device is closed, during close or destructor, the last buffer is flushed to the device.

void SimpleCryptDevice::flushEnd()
{
 if(openMode() &amp; WriteOnly)
 {
 quint64 realBytesWritten = writeBlock(m_byteBuffer);
 emit encryptedBytesWritten(realBytesWritten);
 }
}

That's all.

Example application screenshot

Example App