Symbian
Symbian OS Library

FAQ-0768 How can I use CSecurityBase to encrypt and decrypt my data files?

[Index][spacer] [Previous] [Next]



 

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.