123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
/**
 * Copyright: Copyright (C) Thomas Dixon 2009. All rights reserved.
 * License:   BSD style: $(LICENSE)
 * Authors:   Thomas Dixon
 */

module tango.util.cipher.RC4;

private import tango.util.cipher.Cipher;

/** Implementation of RC4 designed by Ron Rivest of RSA Security. */
class RC4 : StreamCipher
{
    private
    {
        ubyte[] state;
        const(ubyte)[] workingKey;
        ubyte x, y;
    }
    
    this()
    {
        state = new ubyte[256];
    }

    this(bool encrypt, ubyte[] key) {
        this();
        init(encrypt, key);
    }
    
    final void init(bool encrypt, ubyte[] key)
    {
        if (key.length < 0 || key.length > 256)
            invalid(name()~": Invalid key length (requires 1-256 bytes)");
                
        workingKey = key;
        setup(workingKey);
        
        _encrypt = _initialized = true;
    }
    
    @property final override const(char)[] name()
    {
        return "RC4";
    }
    
    override ubyte returnByte(ubyte input)
    {
        if (!_initialized)
            invalid(name()~": Cipher not initialized");
            
        y += state[++x];
        ubyte t = state[x];
        state[x] = state[y];
        state[y] = t;
        
        return (input^state[cast(ubyte)(state[x]+state[y])]);
    }
    
    final override uint update(const(void[]) input_, void[] output_)
    {
        if (!_initialized)
            invalid(name()~": Cipher not initialized");
            
        const(ubyte[]) input = cast(const(ubyte[])) input_;
        ubyte[] output = cast(ubyte[]) output_;
            
        if (input.length > output.length)
            invalid(name()~": Output buffer too short");
            
        for (int i = 0; i < input.length; i++)
        {
            y += state[++x];
            ubyte t = state[x];
            state[x] = state[y];
            state[y] = t;
            output[i] = input[i] ^ state[cast(ubyte)(state[x]+state[y])];
        }
        
        return cast(uint)input.length;
    }
    
    final override void reset()
    { 
        setup(workingKey);
    }
    
    // Do RC4's key setup in a separate method to ease resetting
    private void setup(const(ubyte)[] key)
    {
        for (int i = 0; i < 256; i++)
            state[i] = cast(ubyte)i;
            
        x = 0;
        for (int i = 0; i < 256; i++)
        {
            x += key[i % key.length] + state[i];
            ubyte t = state[i];
            state[i] = state[x];
            state[x] = t;
        }
        
        x = y = 0;
    }
    
    /** Some RC4 test vectors. */
    debug (UnitTest)
    {
        unittest
        {
            enum immutable(char)[][] test_keys = [
                "0123456789abcdef",
                "0123456789abcdef",
                "0000000000000000",
                "ef012345",
                "0123456789abcdef"
            ];
                 
            enum immutable(char)[][] test_plaintexts = [
                "0123456789abcdef",
                "0000000000000000",
                "0000000000000000",
                "00000000000000000000",
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"~
                "01010101010101010101010101010101"
            ];
                 
            enum immutable(char)[][] test_ciphertexts = [
                "75b7878099e0c596",
                "7494c2e7104b0879",
                "de188941a3375d3a",
                "d6a141a7ec3c38dfbd61",
                "7595c3e6114a09780c4ad452338e1ffd"~
                "9a1be9498f813d76533449b6778dcad8"~
                "c78a8d2ba9ac66085d0e53d59c26c2d1"~
                "c490c1ebbe0ce66d1b6b1b13b6b919b8"~
                "47c25a91447a95e75e4ef16779cde8bf"~
                "0a95850e32af9689444fd377108f98fd"~
                "cbd4e726567500990bcc7e0ca3c4aaa3"~
                "04a387d20f3b8fbbcd42a1bd311d7a43"~
                "03dda5ab078896ae80c18b0af66dff31"~
                "9616eb784e495ad2ce90d7f772a81747"~
                "b65f62093b1e0db9e5ba532fafec4750"~
                "8323e671327df9444432cb7367cec82f"~
                "5d44c0d00b67d650a075cd4b70dedd77"~
                "eb9b10231b6b5b741347396d62897421"~
                "d43df9b42e446e358e9c11a9b2184ecb"~
                "ef0cd8e7a877ef968f1390ec9b3d35a5"~
                "585cb009290e2fcde7b5ec66d9084be4"~
                "4055a619d9dd7fc3166f9487f7cb2729"~ 
                "12426445998514c15d53a18c864ce3a2"~ 
                "b7555793988126520eacf2e3066e230c"~  
                "91bee4dd5304f5fd0405b35bd99c7313"~
                "5d3d9bc335ee049ef69b3867bf2d7bd1"~
                "eaa595d8bfc0066ff8d31509eb0c6caa"~
                "006c807a623ef84c3d33c195d23ee320"~
                "c40de0558157c822d4b8c569d849aed5"~
                "9d4e0fd7f379586b4b7ff684ed6a189f"~
                "7486d49b9c4bad9ba24b96abf924372c"~
                "8a8fffb10d55354900a77a3db5f205e1"~
                "b99fcd8660863a159ad4abe40fa48934"~
                "163ddde542a6585540fd683cbfd8c00f"~
                "12129a284deacc4cdefe58be7137541c"~
                "047126c8d49e2755ab181ab7e940b0c0"
            ];
    
            RC4 r = new RC4();
            foreach (uint i, immutable(char)[] test_key; test_keys)
            {
                ubyte[] buffer = new ubyte[test_plaintexts[i].length>>1];
                char[] result;
                
                r.init(true, ByteConverter.hexDecode(test_key));
                
                // Encryption
                r.update(ByteConverter.hexDecode(test_plaintexts[i]), buffer);
                result = ByteConverter.hexEncode(buffer);
                assert(result == test_ciphertexts[i],
                        r.name~": ("~result~") != ("~test_ciphertexts[i]~")");
                        
                r.reset();
                
                // Decryption
                r.update(ByteConverter.hexDecode(test_ciphertexts[i]), buffer);
                result = ByteConverter.hexEncode(buffer);
                assert(result == test_plaintexts[i],
                        r.name~": ("~result~") != ("~test_plaintexts[i]~")");
            }
        }
    }
}