An introduction to the symmetric cipher framework
The symmetric cipher framework collates the behaviour of all symmetric ciphers under one interface: CSymmetricCipher. This interface is intended to represent one direction of one instance of any symmetric cipher. One direction means either encryption or decryption, but not both.
CSymmetricCipher interface basics
Block ciphers -- Here one must create an underlying transformation (CBlockTransformation) and create a CBufferedTransformation (which is an CSymmetricCipher) from that.
Stream ciphers -- These have no concept of buffering and are treated as specializations of CSymmetricCipher. They require no intermediate container class.
The following code illustrates the creation of a buffered AES ECB encryptor and an ARC4 stream cipher.
CBlockTransformation* block = 0; block = CAESEncryptor::NewLC(aKey); CPadding* padding = CPaddingSSLv3::NewLC(KAESBlockSize); //The blocksize of AES (16 bytes) CSymmetricCipher* cipher = CBufferedEncryptor::NewL(block, padding); CleanupStack::Pop(2, block); //padding, block -> both owned by cipher
CSymmetricCipher* cipher = new(ELeave)CARC4(aKey); CleanupStack::PushL(cipher):
After creation, both examples are usable through the CSymmetricCipher interface. So, to encrypt with either of the above ciphers one could do:
HBufC8* output = HBufC8::NewLC(cipher->MaxOutputLength(input.Size())); cipher->Process(input, output); HBufC8* output2 = HBufC8::NewLC(cipher->MaxFinalOutputLength(input2.Size())); cipher->ProcessFinalL(input2, output2);
In this example, input and input2 are two arbitrary, finite length descriptors. The derived implementations of CSymmetricCipher (CBufferedEncryptor / CBufferedDecryptor and CStreamCipher) are responsible for handling what to do in each specific case. For example, in the case of an encrypting block cipher, ProcessFinalL() will call the underlying padding system, which will in turn ensure that the overall length of input plaintext is of a suitable length for encryption. For more information on how the values returned from MaxOutputLength() and MaxFinalOutputLength() are calculated see How does buffering work within the symmetric cipher framework?.
Example code for a symmetric factory class
To simplify the process of creating symmetric encryptors and decryptors, it is strongly recommended that applications create a factory that automates the process for them. The following code gives a sample factory that applications might like to use as a reference. It is not supplied as part of the framework as every application has different ways of identifying symmetric cipher suites.
CSymmetricCipher* CCipherFactory::BuildEncryptorL( TSymmetricCipherType aType,const TDesC8& aKey,const TDesC8& aIV) { CSymmetricCipher* cipher = NULL; if (aType==ERc4) { cipher = new(ELeave) CARC4(aKey); } else { CBlockTransformation* bT = NULL; switch (aType) { case EDes_cbc: bT = CDESEncryptor::NewL(aKey); break; case EDes_ede3_cbc: bT = C3DESEncryptor::NewL(aKey); break; case ERc2_cbc: bT = CRC2Encryptor::NewL(aKey); break; default: User::Leave(KErrNotSupported); }; CleanupStack::PushL(bT); CBlockTransformation* mode = CModeCBCEncryptor::NewL(bT, aIV); CleanupStack::Pop(bT); // owned by mode CleanupStack::PushL(mode); CPadding* padding = CPaddingSSLv3::NewLC(KBlockSize); //All of these ciphers use 8 byte blocks cipher = CBufferedEncryptor::NewL(mode, padding); CleanupStack::Pop(2, mode); //padding, mode now owned by cipher } return cipher; }
Applications creating these factories need to supply an equivalent to the TSymmetricCipherType enum, which contains the list of identifiers representing the cipher, padding, and mode requirements of the application.
Note that a similar BuildDecryptorL() will also have to be created.
Good naming conventions dictate that applications should not pollute the global namespace and either use their own namespace or prefix their factory classes with identifiers that associated it with that specific application.
When the amount of plaintext to be encrypted is larger than a single block, some method must be employed to specify how subsequent blocks are dependent on previous blocks. The simplest method, known as ECB (Electronic CodeBook), specifies that subsequent blocks are completely independent. Therefore, two identical blocks of plaintext will encrypt to two identical blocks of ciphertext. ECB has significant security drawbacks, thus most applications use more advanced modes in which subsequent blocks are dependent on the ciphertext of previous blocks. The symmetric framework handles these modes through the CBlockChainingMode class, which is a specialization of CBlockTransformation. The idea is that one gives an implementation of a CBlockChainingMode another CBlockTransformation (CAESEncryptor, for instance) and then performs all operations on the CBlockChainingMode instance. When CBlockTransformation::Transform() is called on the mode, it is responsible for calling Transform() on the underlying transformation that it owns and then applying its own chaining mode transformation.
The following example shows how to create a buffered AES CBC encryptor.
CBlockTransformation* basicAesBlock = 0; CBlockTransformation* cbcBlock = 0; basicAesBlock = CAESEncryptor::NewLC(aKey); cbcBlock = CModeCBCEncryptor::NewL(basicAesBlock, iv); CleanupStack::Pop(basicAesBlock); //owned by cbcBlock CleanupStack::PushL(cbcBlock); CPadding* padding = CPaddingSSLv3::NewLC(KAESBlockSize); //The blocksize of AES (16 bytes) CSymmetricCipher* cipher = CBufferedEncryptor::NewL(cbcBlock, padding); CleanupStack::Pop(2, cbcBlock); //padding, cbcBlock -> both owned by cipher
Generally, when implementing secure comms protocols, the cipher you use will be dictated by the protocol specification. However, if you are writing your own application, you should consider the use of AES (CAESEncryptor); this is the cipher recommended by NIST.
Stream ciphers consume all content they are given. That is, the value returned from CSymmetricCipher::MaxOutputLength() is always the same as the aInputLength parameter passed in.
Block ciphers controlled through a CBufferedTransformation operate under the following rules:
Process()
ProcessFinalL()
Encryption
Any previously buffered data is (logically) prepended to aInput.
All whole block are transformed and appended to aOutput.
Any remaining partial blocks are padded with underlying padding system to be block aligned to the padding block size. (In the vast majority of cases, the padding block size is equal to the block cipher block size).
The resulting block(s) are transformed and appended to aOutput.
Decryption
In all cases CSymmetricCipher::MaxOutputLength() returns as tight an upper bound as possible on the number of bytes that will be returned by a call to CSymmetricCipher::Process() with a specified number of input bytes. Correspondingly, CSymmetricCipher::MaxFinalOutputLength() returns a similar bound but for a pending call to CSymmetricCipher::ProcessFinalL().
Copyright ©2010 Nokia Corporation and/or its subsidiary(-ies).
All rights
reserved. Unless otherwise stated, these materials are provided under the terms of the Eclipse Public License
v1.0.