<p>I understand that they are both supposed to be small, but what are the key differences between the two?</p>

Exokernel
is an
operating system from MIT
(and a class of it's variants) which handles relatively little hardware abstraction.  In exokernel, the low-level responsibilities of controlling hardware (particularly
memory allocation
) are often left in the hands of the developer.  Many developers would probably prefer to have the OS take more responsibility of such low-level tasks, because most developers are just writing applications.
An exokernel just
allocates physical hardware resources to programs
.  This allows the program to use library operating systems, which are linked in to provide some of the abstraction that the exokernel isn't providing.  The developer can then choose between abstraction models, or roll their own.  Given the application, this may have great performance benefits.  If you don't know what you're doing, you can also write programs that will explode when they crash.
Most kernels will do more to
abstract physical hardware
resources into some kind of
theoretical model
.  A developer interfaces with this model, which handles the finer points of dealing with hardware internally.
The term
nanokernel
is used to describe a specific
type of kernel
.  The prefix "pico-", or "nano-", "micro-" is usually denoting the "size" of the kernel..
Bigger kernels
are more built with more features, and
handle more hardware abstraction
.  Nanokernels are relatively small kernels which
provide hardware abstraction
, but
lack system services
.  Modern microkernels also lack system services, so the terms have become analogous.
The
names of kernels
usually stem from a specific batch of research which yielded a new kind of kernel, for example the kernel developed at at Carnegie Mellon called "Mach", which was one of the first examples of a modern "microkernel".
Sidenote:
The real benefit of exokernel is choice.  Most of the time, a lot of abstraction means fewer catastrophic bugs.  In some applications, you might want to use a different abstraction model, or you might want to handle everything yourself.  If we wanted to scrap the OS abstraction for a particular project, we'd have to cut out the operating system and commit a piece of hardware to the job.  In the case of exokernel, this isn't necessary.  We can program directly "to the metal", but also choose to link in an abstraction model whenever we like.  It's a very powerful concept.
Sidenote
: Dealing with memory on such a low level is unnecessary for most application developers.  There are usually several layers of operating system built on top of a kernel, and your application will run on the highest level of the OS.  When writing in javascript, you're higher up still, interfacing with a model implemented in an application which runs on an operating system, etc. etc.  Addressing memory, while it shouldn't be ignored, might mean something entirely different to a developer who is writing on such a high level of abstraction.