view dcrypt/crypto/modes/CBC.d @ 28:ad687db713a4

Further reworked the code for hash padding. Replaced all instances of 'char[]' with 'string' and removed a few 'const' modifiers as per Glenn Haecker's patch for D2 compatibility. Updated CONTRIBUTORS file.
author Thomas Dixon <reikon@reikon.us>
date Sun, 10 May 2009 22:38:48 -0400
parents 8b5eaf3c2979
children
line wrap: on
line source

/**
 * This file is part of the dcrypt project.
 *
 * Copyright: Copyright (C) dcrypt contributors 2008. All rights reserved.
 * License:   MIT
 * Authors:   Thomas Dixon
 */

module dcrypt.crypto.modes.CBC;

import dcrypt.crypto.BlockCipher;
public import dcrypt.crypto.params.ParametersWithIV;

debug (UnitTest)
{
    import dcrypt.crypto.ciphers.XTEA;
    import dcrypt.misc.ByteConverter;
}

/** This class implements the cipher block chaining (CBC) block mode. */
class CBC : BlockCipher
{
    private
    {
         BlockCipher wrappedCipher;
         
         ubyte[] iv,
                 previousBlock,
                 currentBlock;
    }
    
    /**
     * Params:
     *     cipher = Block cipher to wrap.
     */
    this (BlockCipher cipher)
    {
        wrappedCipher = cipher;
    }
    
    /** Returns: The underlying cipher we are wrapping. */
    BlockCipher cipher()
    {
        return wrappedCipher;
    }
    
    string name()
    {
        return wrappedCipher.name~"/CBC";
    }
    
    /** 
     * Throws: dcrypt.crypto.errors.InvalidParameterError if params aren't 
     *         an instance of dcrypt.crypto.params.ParametersWithIV.
     */
    void init(bool encrypt, CipherParameters params)
    {
        ParametersWithIV ivParams = cast(ParametersWithIV)params;
        
        if (!ivParams)
            throw new InvalidParameterError(
                    name()~": Block mode requires IV (use ParametersWithIV)");
        if (ivParams.iv.length != blockSize)
            throw new InvalidParameterError(
                    name()~": IV must be same length as cipher block size");
                    
        _encrypt = encrypt;
        
        wrappedCipher.init(_encrypt, ivParams.parameters);
        
        iv = ivParams.iv[0..blockSize];
        
        currentBlock = new ubyte[blockSize];
        previousBlock = new ubyte[blockSize];
        previousBlock[] = iv; // C_0 = IV
        
        _initialized = true;
    }
    
    uint update(void[] input_, void[] output_)
    {
        if (!_initialized)
            throw new NotInitializedError(name()~": Block mode not initialized");
            
        ubyte[] input = cast(ubyte[]) input_,
                output = cast(ubyte[]) output_;
                    
        if (input.length < blockSize)
            throw new ShortBufferError(name()~": Input buffer too short");
            
        if (output.length < blockSize)
            throw new ShortBufferError(name()~": Output buffer too short");
        
        if (_encrypt)
        {
            // P_i XOR C_i-1
            for (int i = 0; i < blockSize; i++)
                previousBlock[i] ^= input[i];
                
            // E_k(P_i XOR C_i-1) and store C_i for the next block
            wrappedCipher.update(previousBlock, previousBlock);
            
            // C_i = E_k(P_i XOR C_i-1)
            output[0..blockSize] = previousBlock;            
        }
        else
        {
            // Local reference to C_i
            ubyte[] temp = input[0..blockSize];

            // D_k(C_i)
            wrappedCipher.update(temp, currentBlock);
            
            // P_i = D_k(C_i) XOR C_i-1
            for (int i = 0; i < blockSize; i++)
                output[i] = (currentBlock[i] ^ previousBlock[i]);
             
            // Store C_i for next block
            previousBlock[] = temp;
       }
        
        return blockSize;
    }
    
    uint blockSize()
    {
        return wrappedCipher.blockSize;
    }
    
    void reset()
    {
        previousBlock[] = iv;
        wrappedCipher.reset();
    }
    
    /** Test vectors for CBC mode. Assumes XTEA passes test vectors. */
    debug (UnitTest)
    {
        unittest
        {
            static const string[] test_keys = [
                "00000000000000000000000000000000",            
                "00000000000000000000000000000000",
                "0123456789abcdef0123456789abcdef"
            ];
                 
            static const string[] test_plaintexts = [
                "00000000000000000000000000000000"~
                "00000000000000000000000000000000",
                 
                "41414141414141414141414141414141"~
                "41414141414141414141414141414141",
                 
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"
            ];

            static const string[] test_ciphertexts = [
                "dee9d4d8f7131ed9b0e40a036a85d2c4"~
                "4602d6e67f0c603738197998166ef281",
                 
                "ed23375a821a8c2d0e1f03d719874eaa"~
                "4b71be74f261e22f4cd2285883a61a23",
                 
                "c09d3c606614d84b8d184fa29c5cb5f6"~
                "f26fa5a0b6b63ba0f7ebf2f8735f85e3"
            ];

            CBC c = new CBC(new XTEA);
            ubyte[] iv = new ubyte[c.blockSize], // Initialized to 0
                    buffer = new ubyte[32];
            string result;
            for (int i = 0; i < test_keys.length; i++)
            {
                SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(test_keys[i]));
                ParametersWithIV params = new ParametersWithIV(key, iv);
                
                // Encryption
                c.init(true, params);
                for (int j = 0; j < 32; j+=c.blockSize)
                    c.update(ByteConverter.hexDecode(test_plaintexts[i])[j..j+c.blockSize], buffer[j..j+c.blockSize]);
                result = ByteConverter.hexEncode(buffer);
                assert(result == test_ciphertexts[i],
                        c.name()~": ("~result~") != ("~test_ciphertexts[i]~")");           
                
                // Decryption
                c.init(false, params);
                for (int j = 0; j < 32; j+=c.blockSize)
                    c.update(ByteConverter.hexDecode(test_ciphertexts[i])[j..j+c.blockSize], buffer[j..j+c.blockSize]);
                result = ByteConverter.hexEncode(buffer);
                assert(result == test_plaintexts[i],
                        c.name()~": ("~result~") != ("~test_plaintexts[i]~")");
            }   
        }
    }
}