Block Cipher Without Key Schedule? I'm designing a cryptography assignment for a college security course, and one of the problems is to perform a simple meet-in-the-middle attack on a 2-round, 2-key cipher.

Since we want this to be a reasonable attack for students to mount on their own laptops or school computers, the cipher needs to be very fast to compute. However, all of the ciphers I've looked at so far require significant key expansion via a key schedule, and since each encryption/decryption performed is with a different key, this expansion must be performed for every encryption/decryption operation, which we're finding in practice has a big performance penalty.

What I'm wondering is: are there any block ciphers that don't use key expansion?

These don't have to be secure by any stretch of the imagination: just secure enough that it's easier for students to do the assignment directly than break the block cipher via some other means.
You might consider using TEA or its successor, XTEA.  Here's the complete C source code for XTEA, taken from the Wikipedia article:



#include &lt;stdint.h&gt;

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i &lt; num_rounds; i++) {
        v0 += (((v1 &lt;&lt; 4) ^ (v1 &gt;&gt; 5)) + v1) ^ (sum + key[sum &amp; 3]);
        sum += delta;
        v1 += (((v0 &lt;&lt; 4) ^ (v0 &gt;&gt; 5)) + v0) ^ (sum + key[(sum&gt;&gt;11) &amp; 3]);
    }
    v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i &lt; num_rounds; i++) {
        v1 -= (((v0 &lt;&lt; 4) ^ (v0 &gt;&gt; 5)) + v0) ^ (sum + key[(sum&gt;&gt;11) &amp; 3]);
        sum -= delta;
        v0 -= (((v1 &lt;&lt; 4) ^ (v1 &gt;&gt; 5)) + v1) ^ (sum + key[sum &amp; 3]);
    }
    v[0]=v0; v[1]=v1;
}


This code is simple enough that your students should be able to easily integrate it into their programs, and even modify it if necessary.  You might even want to consider reusing it for other exercises, too.  Notably, it also involves no special pre-processing of the key &mdash; the "key schedule", such as it is, is very simple and done directly within the encryption loop.

One feature of (X)TEA that (while generally a good thing) may not be ideal for your purposes is that it has a 128-bit keyspace, which is too large for a practical brute force search.  For your exercise, you could artificially restrict the keyspace to just, say, 32 bits by setting the remaining 96 bits of the key to some fixed value.  Then compositing two such encryptions with different keys should give you a 64-bit keyspace, which is still rather large to just exhaustively search by brute force, whereas the meet-in-the-middle attack needs only up to 233 operations, which should be easily doable.

By the way, the general property you're asking for is called key agility.  You may find other suitable ciphers by Googling for "key-agile cipher".