Generally speaking, both are used like memory aliases. But, a variable more directly aliases a memory offset at compile time, and a key informs a memory mapping algorithm at runtime.
Conceptually, the primary difference is one of scope: Whether the alias operates at compile time versus runtime.
Functionally, the primary difference is one of runtime mutability (of all such aliases): Whether the collection of aliases can be changed at runtime. You can't compile your code and add
Int32 xyz at runtime. But, you can add key
xyz to a collection. (With some interpreted languages being an exception.)
Practically, the primary difference is lookup strategy and efficiency: Variables hold memory offsets. Using them is fast. Keys are piped through a lookup algorithm that, comparatively speaking, eventually determines a memory offset and returns its contents.
Lots of work is done at runtime to make keys work. Variables are sort of just there.
This all assumes you're referring to keys in the context of a collection. You can certainly have key-value pairs that have nothing to do with collections or finding things in memory. But, the keys in that context are far enough removed functionally that ... well ... I'm just assuming the question isn't referring to that.