If you're a user of cryptography: all of these are obsolete. There is no reason in this day and age to combine a MAC or hash with encryption. Use a standard AEAD algorithm, which combines confidentiality and authenticity protection in a way that's been vetted by cryptographers. It might use MAC-then-encrypt, encrypt-then-MAC or encrypt-and-MAC under the hood, or something that doesn't fit into any of those three frameworks, but you don't need to care.
If you're a designer of cryptographic primitives: all of these are potential ways to provide authenticated encryption. Authenticated encryption of a message guarantees two properties: confidentiality (only entities who have the secret key can recover the message from the ciphertext) and authenticity (only entities who have the secret key can craft a valid ciphertext).
A MAC guarantees authenticity. Encryption guarantees confidentiality under certain assumptions (encryption without authenticity can be vulnerable to oracle attacks, for example padding oracle attacks against the popular CBC mode). You can combine a MAC primitive
M with an encryption primitive
E on a message
m in various ways (
|| is concatenation):
E(M) || M(E(m)): encrypt-then-MAC. Encrypt the message, and append the MAC of the encryption.
E(m || M(m)): MAC-then-encrypt. Append the MAC of the message to the message, and encrypt the result.
E(M) || M(m): encrypt-and-MAC. Encrypt the message, and append the MAC of the original message.
It's possible to get each of them right. It's also possible to get each of them wrong. For a review of the upsides and downsides of each approach, read Should we MAC-then-encrypt or encrypt-then-MAC?
Hash-then-encrypt is the same thing as MAC-then-encrypt, except that it uses a hash function instead of a MAC:
E(M || H(h)). This has a good chance of guaranteeing confidentiality since everything is encrypted. Authenticity is more fragile: it relies on the adversary not being able to craft the encryption of the hash. I'm not aware of a working construction that uses this framework, but it's possible that one exists.
Bonus question: why not encrypt-then-hash or encrypt-and-hash?
Those cannot possibly work. Encrypt-then-hash (
E(m) || H(E(m))) allows anyone to forge arbitrary messages by just making up a ciphertext and appending its hash. (And the adversary may even be able to know the message content; for example, truncating a ciphertext often corresponds to truncating a message, so an adversary can freely truncate an existing message.) Encrypt-and-hash (
E(m) || H(m)) reveals the hash of the message, so anyone can at least guess what the message might be and verify their guess.