view dcrypt/crypto/modes/CBC.d @ 0:0e08791a1418

Initial import.
author Thomas Dixon <reikon@reikon.us>
date Sun, 10 Aug 2008 14:20:17 -0400
parents
children 23c62e28b3a4
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;
import dcrypt.crypto.params.ParametersWithIV;

version (UnitTest) {
    import dcrypt.crypto.ciphers.XTEA;
    import dcrypt.misc.Util;
}

/** This class implements the cipher block chaining (CBC) block mode. */
class CBC : BlockCipher {
    private BlockCipher m_cipher;
    private ubyte[] iv,
                    previousCiphertext,
                    cbcOutput;
    private bool encrypt,
                 initialized;
    
    /**
     * Params:
     *     cipher = Block cipher to wrap.
     */
    this (BlockCipher cipher) {
        m_cipher = cipher;
    }
    
    /** Returns: The underlying cipher we are wrapping. */
    BlockCipher cipher() {
        return m_cipher;
    }
    
    char[] name() {
        return m_cipher.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");
                    
        this.encrypt = encrypt;
        m_cipher.init(encrypt, ivParams.parameters);
        
        iv = ivParams.iv[0..blockSize];
        previousCiphertext = new ubyte[blockSize];
        previousCiphertext[] = iv; // C_0 = IV
        cbcOutput = new ubyte[blockSize]; // Output buffer for E_k/D_k(...)
        
        initialized = true;
    }
    
    uint processBlock(void[] input_, uint inOff, void[] output_, uint outOff) {
        if (!initialized)
            throw new NotInitializedError(
                    name()~": Block mode not initialized");
            
        ubyte[] input = cast(ubyte[]) input_,
                output = cast(ubyte[]) output_;
        
        if ((inOff + blockSize) > input.length)
            throw new ShortBufferError(name()~": Input buffer too short");
            
        if ((outOff + blockSize) > output.length)
            throw new ShortBufferError(name()~": Output buffer too short");
        
        if (encrypt) {
            // P_i XOR C_i-1
            for (int i = 0; i < blockSize; i++)
                previousCiphertext[i] ^= input[inOff++];
                
            // E_k(P_i XOR C_i-1)
            m_cipher.processBlock(previousCiphertext, 0, cbcOutput, 0);
            
            // Store C_i for next block
            previousCiphertext[] = cbcOutput;            
            
            // C_i = E_k(P_i XOR C_i-1)
            output[outOff..(outOff+blockSize)] = cbcOutput;
        } else {
            // Temporarily store C_i
            ubyte[] t = input[inOff..(inOff+blockSize)];

            // D_k(C_i)
            m_cipher.processBlock(t, 0, cbcOutput, 0);
            
            // P_i = D_k(C_i) XOR C_i-1
            for (int i = 0; i < blockSize; i++)
                output[outOff++] = (cbcOutput[i] ^ previousCiphertext[i]);
             
            // Store C_i for next block
            previousCiphertext[] = t;
       }
        return blockSize;
    }
    
    uint blockSize() {
        return m_cipher.blockSize;
    }
    
    void reset() {
        previousCiphertext[] = iv;
        m_cipher.reset();
    }
    
    /** Test vectors for CBC mode. Assumes XTEA passes test vectors. */
    version (UnitTest) {
        unittest {
            static const char[][] test_keys = [
                "00000000000000000000000000000000",            
                "00000000000000000000000000000000",
                "0123456789abcdef0123456789abcdef"
            ];
                 
            static const char[][] test_plaintexts = [
                "00000000000000000000000000000000"~
                "00000000000000000000000000000000",
                 
                "41414141414141414141414141414141"~
                "41414141414141414141414141414141",
                 
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"
            ];
                 
            static const char[][] test_ciphertexts = [
                "dee9d4d8f7131ed9b0e40a036a85d2c4"~
                "4602d6e67f0c603738197998166ef281",
                 
                "ed23375a821a8c2d0e1f03d719874eaa"~
                "4b71be74f261e22f4cd2285883a61a23",
                 
                "c09d3c606614d84b8d184fa29c5cb5f6"~
                "f26fa5a0b6b63ba0f7ebf2f8735f85e3"
            ];
            
            XTEA x = new XTEA();
            CBC c = new CBC(x);
            ubyte[] iv = new ubyte[x.blockSize], // Initialized to 0
                    buffer = new ubyte[32];
            char[] result;
            for (int i = 0; i < test_keys.length; i++) {
                SymmetricKey key = new SymmetricKey(Util.hexToUbytes(test_keys[i]));
                ParametersWithIV params = new ParametersWithIV(key, iv);
                
                // Encryption
                c.init(true, params);
                for (int j = 0; j < 32; j+=x.blockSize)
                    c.processBlock(Util.hexToUbytes(test_plaintexts[i]), j, buffer, j);
                result = Util.ubytesToHex(buffer);
                assert(result == test_ciphertexts[i],
                        c.name()~": ("~result~") != ("~test_ciphertexts[i]~")");           
                
                // Decryption
                c.init(false, params);
                for (int j = 0; j < 32; j+=x.blockSize)
                    c.processBlock(Util.hexToUbytes(test_ciphertexts[i]), j, buffer, j);
                result = Util.ubytesToHex(buffer);
                assert(result == test_plaintexts[i],
                        c.name()~": ("~result~") != ("~test_ciphertexts[i]~")");
            }   
        }
    }
}