|
|
Classification: |
C++ |
Category: |
Application Engines |
Created: |
01/26/2002 |
Modified: |
04/26/2005 |
Number: |
FAQ-0768 |
Platform: |
Symbian OS v6.0, Symbian OS v6.1, Symbian OS v7.0, Symbian OS v7.0s |
|
Question: How can I use CSecurityBase to encrypt and decrypt my data files?
Answer: CSecurityBase and associated classes provide a framework for two security related operations: authentication and encryption. In this framework
the two operations are linked, in that the encryption/decryption should only be enabled once authentication has succeeded
- however, this is up to the implementor of the derived classes. The Security API provides a factory for a concrete implementation
of this framework, which is used in the kernel to implement the Password API (this only uses the authentication), and in applications
for password protected documents (using both bits of functionality).
First we will look at how you would typically use the provided implementation, and then we'll describe how you can create
and use an alternative implementation. To do anything you need an object that implements the framework: there are two factory
functions in the Security class for creating CSecurityBase objects.
The one with no parameters is used when wanting to protect/encrypt a new object, e.g. when password protecting a document
for the first time or when booting the device without a device password. This functions returns a security object that is
valid, but for which the password is the empty string ("") - so the initial password is set by changing the password from
"" to the desired string. At this point the authentication token (a.k.a. security data) can be extracted and saved - this
is later used to create new security objects that can check the password. Streams of data can now be encrypted using encryption
objects created by the security object. Here is a simple example of how a new password protected document might be created:
CStreamStore* store; // this is the document storage const TDesC8& data; // data to be encrypted const TDesC* pwd; // user supplied password
TStreamId securityId; // stream which will contain the security data TStreamId dataId; // stream which will contain the encrypted stream
CSecurityBase* security = Security::NewL(); CleanupStack::PushL(security); security->SetL(KNullDesC, pwd); RStoreWriteStream stream; stream.CreateLC(*store, securityId); stream<SecurityData(); stream.CommitL(); CleanupStack::PopAndDestroy(&stream); stream.CreateL(*store, dataId); REncryptStream encrypt; encrypt.AttachLC(stream, *security, KNullDesC); // encryption filter for stream, taking ownership of stream encrypt< encrypt.CommitL(); CleanupStack::PopAndDestroy(&encrypt); CleanupStack::PopAndDestroy(security);
It is possible to use the CSecurityEncryptBase objects directly, though it is usually easier to use the stream filters to drive the cipher as in the example.
The second Security::NewL() function takes a previously generated authentication token and returns a security object that is currently invalid. In this
state it cannot be used for encryption or decryption (or password change) - first of all it has to be passed the correct password.
If the correct password is supplied, then the object becomes valid and can be used to encrypt or decrypt data. It is also
possible to change the password, this would then require the new security data to be saved. Given the above stored data, you
could recover the plain text like this:
RStoreReadStream stream; stream.OpenLC(*store, securityId); HBufC8 token = HBufC8::NewL(stream, 1000); // recover the security data CleanupStack::PopAndDestroy(&stream); CleanupStack::PushL(token); CSecurityBase* security = Security::NewL(*token); // create security object CleanupStack::PushL(security); security->PrepareL(pwd); // authenticate password stream.OpenL(*store, dataId); RDecryptStream decrypt; decrypt.AttachLC(stream, *security, KNullDesC); // decryption filter for stream, taking ownership of stream HBufC8* data = HBufC8::NewL(decrypt, KMaxTInt); CleanupStack::PopAndDestroy(&decrypt); CleanupStack::PopAndDestroy(security); CleanupStack::PopAndDestroy(token); // use data
Providing an alternative implementation:
It is not possible to replace the implementation of CSecurityBase that is returned by the Security API (which provides default weak encryption), but it is possible to implement new classes
that can be used for encrypted storage as the STORE APIs only depend on the framework classes and do not use the Security
API directly. In fact, STORE only depends on the NewEncryptL(), NewDecryptL() and MaxCipherLength() functions in CBoundedSecurityBase, the other functions are not used. However, for completeness this is what the authentication functions should do (as illustrated
by the above examples):
PrepareL() takes a password as a parameter and should validate it against the stored security data. If this fails, the object should
remain 'invalid' and this function should leave. If successful, the object is now valid and will create encryption and decryption
objects. Typically success on this function would generate or gain access to some encyption key for later use by the encryption
and decryption services.
SetL() takes the old and new passwords and should validate the old password before changing the password to the new one. If the
validation fails the object state is unchanged and the function leaves. Otherwise the new password becomes valid.
SecurityData() returns a reference to some binary data that forms the authentication token for the current password. Typically this token
will be a one-way hash of the password in some form.
IsEnabled() and SetEnabledL() provide an extra item of state in the security object that is both (1) stored with the security data and (2) protected by
the password. This is used for the device password on a Series 5 to allow there to be a password, but disabled - this means
that no password is requested to use the device, but you need to know the password in order to enable password protection
or change the password. For most purposes you can ignore it.
If you have another way to generate the encryption/decryption keys you could stub the implementation of these functions in
the derived class and just create the object with the keys available.
Encryption and Decryption:
NewEncryptL() creates an object that can encrypt a single stream of plain text, with some optional salt data. It should be possible to
create multiple encryption objects from the same seucrity object and interleave the encryption - i.e. they shouldn't share
state with each other.
NewDecryptL() creates an object that can decrypt a single stream of cipher text, with some optional salt data. Calling this with the same
salt data as passed to NewEncryptL() (on a security object created with the same password) should result in a decryption object that can decipher the cipher text
generated by the encryption object. Again, it should be possible to create multiple decryption objects from the same seucrity
object and interleave the decryption - i.e. they shouldn't share state with each other.
The encryption/decryption objects are free to implement the cipher as they like, and can do any buffering that is necessary
for feedback mechanisms. The basic API has the form:
TInt ProcessL(TDes8& aOutput, const TDesC8& aInput);
The algorithm should read data from aInput, the return value from this function should be the number of bytes consumed. Output
from the algorithm should be placed into aOutput (aOutput should be Zero()'d by the implementation first). This is called multiple times while processing a single stream of data. The caller ensures
that any data not consumed by a call to ProcessL() will be passed back in at the beginning of input to the next call. It is also assumed that the caller will provide reasonable
sized blocks of input and output buffers that are big enough to accomodate at least one 'block' of encryption.
Maximally, the algorithm could produce as much output as possible and consume as much input as possible, dealing with block
alignment internally. Minimally, an algorithm could consume whole blocks from the input, and generate whole blocks into the
output and do no buffering at all, trusting that the caller will pass more data in to the next call. The only exception to
the 'minial' rule is in CSecurityEncryptbase::CompleteL() where the algorithm can assume that the input is the final data in the plain-text stream and must use it even if it is not
a whole 'block' - in which case it should apply some padding algorithm.
The algorithm should expect that EncryptL() will be called until it both consumes no input and produces no output, after which any remaining input is passed to CompleteL(), potentially multiple times, until the same termination condition applies. At this point the data stream is considered to
be fully processed and the encryption object will be discarded.
Decryption is simpler as the cipher-text stream is assumed to already be a whole number of 'blocks' and thus there is no termination
issue with incomplete blocks of data.
Note: the REncryptStream/RDecryptStream API is deprecated from v8.0 of Symbian OS. |
|
|