view dcrypt/crypto/modes/CBC.d @ 23:4589f8c5eb3c

Replaced dcrypt.crypto.Util with dcrypt.misc.Bitwise and dcrypt.misc.ByteConverter. Altered all dependent files to reflect changes.
author Thomas Dixon <reikon@reikon.us>
date Sat, 14 Feb 2009 19:58:20 -0500
parents 8c7f8fecdd75
children 176c933827a8
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;

version (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;
    private ubyte[] iv,
                    previousCiphertext,
                    cbcOutput;
    private bool encrypt,
                 initialized;
    
    /**
     * Params:
     *     cipher = Block cipher to wrap.
     */
    this (BlockCipher cipher) {
        wrappedCipher = cipher;
    }
    
    /** Returns: The underlying cipher we are wrapping. */
    BlockCipher cipher() {
        return wrappedCipher;
    }
    
    char[] 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");
                    
        this.encrypt = encrypt;
        wrappedCipher.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 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++)
                previousCiphertext[i] ^= input[i];
                
            // E_k(P_i XOR C_i-1)
            wrappedCipher.update(previousCiphertext, cbcOutput);
            
            // Store C_i for next block
            previousCiphertext[] = cbcOutput;
            
            // C_i = E_k(P_i XOR C_i-1)
            output[0..blockSize] = cbcOutput;            
        } else {
            // Temporarily store C_i
            ubyte[] t = input[0..blockSize];

            // D_k(C_i)
            wrappedCipher.update(t, cbcOutput);
            
            // P_i = D_k(C_i) XOR C_i-1
            for (int i = 0; i < blockSize; i++)
                output[i] = (cbcOutput[i] ^ previousCiphertext[i]);
             
            // Store C_i for next block
            previousCiphertext[] = t;
       }
        
        return blockSize;
    }
    
    uint blockSize() {
        return wrappedCipher.blockSize;
    }
    
    void reset() {
        previousCiphertext[] = iv;
        wrappedCipher.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(ByteConverter.hexDecode(test_keys[i]));
                ParametersWithIV params = new ParametersWithIV(key, iv);
                
                // Encryption
                c.init(true, params);
                for (int j = 0; j < 32; j+=x.blockSize)
                    c.update(ByteConverter.hexDecode(test_plaintexts[i])[j..j+x.blockSize], buffer[j..j+x.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+=x.blockSize)
                    c.update(ByteConverter.hexDecode(test_ciphertexts[i])[j..j+x.blockSize], buffer[j..j+x.blockSize]);
                result = ByteConverter.hexEncode(buffer);
                assert(result == test_plaintexts[i],
                        c.name()~": ("~result~") != ("~test_plaintexts[i]~")");
            }   
        }
    }
}