Is this symmetric key MAC-then-encrypt auth token methodology secure? NOTE: This question originally conflated a "digital signature" and a "MAC", which I've since learned are not the same thing. Any references (in the answer or any comments) to a "signature" should be read as a "MAC".


I'm experimenting with auth tokens and I'm trying to wrap my mind around the best method.

Goals


Upon successful login, a token is created containing the users accountID, a nonce, and an expiry. The token is then passed back to the client to be used for the next request.
The client passes the token back to the server with each request to identify the origin (accountID) of any given request.
The token contains a nonce that's changed with each request.
A MAC is created from the plaintext value to ensure it's not been tampered with.
The plaintext value of the token (including the MAC) is then encrypted to ensure it's securely obscured.
The client is never privy to the macKey or the encryptionKey. The client is not intended to ever have access to the plaintext contents of the token.


Current Method

///process.env.TOKEN_ENCRYPTION_PASSWORD = '13sd4089f-268c-483d-9e82-jk3c1b47c77a';
///process.env.TOKEN_MAC_KEY = '1fde05f4-268c-483d-9e82-85fc1b42321';

/// Successful login by user ...

var token = {
    nonce : 'SOME-UUID-GOES-HERE',
    accountID : 'account-12345',
    expires : 1234567890
};

token.mac = crypto.createHmac('sha512', TOKEN_MAC_KEY)
                  .update(JSON.stringify(token))
                  .digest('base64');

var encryptedToken = cryptoJS.AES.encrypt(JSON.stringify(token), TOKEN_ENCRYPTION_PASSWORD).toString();

response.send({
    message : 'Created, MAC'd, and encrypted an auth token.',
    authToken : encryptedToken
});


Questions


Is it secure, in this case, to employ a MAC-then-encrypt method? I've read several opinions on this and walked away more confused than when I started.
I've read that cbc-mode should be used when employing a MAC-then-encrypt method. Is this the default mode for CryptoJS.AES.encrypt()?
CryptoJS.AES seems pretty generic. Is this defaulting to AES256? Or is that something that I need to explicitly declare in my code?
When passing a string to CryptoJS.AES.encrypt() I've read that it automagically generates a key and and iv 'behind the scenes'. Would it be more secure to generate my own key and iv, or should I just let CryptoJS handle that?
Is there more security to be had by employing a MAC-encrypt-MAC method, or is that just adding complexity to my app logic?
Is it secure, in this case, to employ a sign-then-encrypt method? I've read several opinions on this and walked away more confused than when I started.
  


First of all, given that your sample code is using HMAC, not digital signatures, I assume you mean MAC-then-encrypt.

The generally safe way to combine a MAC and encryption is encrypt-then-MAC: first encrypt the message, then compute a MAC on the ciphertext (including the IV and any associated data you may have!).  When decrypting, first verify the MAC, and only then try to decrypt the message.

Encrypt-then-MAC is safe because it guarantees that the MAC and the cipher each only need to do their own job: when encrypting, the MAC only receives input that has already been encrypted, so it cannot accidentally leak information about the plaintext; when decrypting, the cipher only receives input that has already been authenticated by the MAC, so it doesn't need to worry about securely handling forged or modified messages.

However, MAC-then-encrypt (or even MAC-and-encrypt) can be safe if the MAC and the cipher are chosen appropriately.  In particular, a sufficient condition for the security of MAC-then-encrypt is that the cipher mode must be length-preserving &mdash; that is, the ciphertext (not counting the IV) must be exactly as long as the input plaintext.

The CFB, OFB and CTR modes are all length-preserving, but CBC is not (unless combined with a modification known as "ciphertext stealing").  In particular, because normal CBC mode requires messages to be padded to an integer number of cipher blocks, it is often vulnerable to padding oracle attacks unless protected with a MAC applied after encryption (and thus verified before decryption).  Thus, you should not use CBC mode with MAC-then-encrypt.


  
  I've read that cbc-mode should be used when employing a sign-then-encrypt method. Is this the default mode for CryptoJS.AES.encrypt()?
  


That's hard to say, and may depend on which version of CryptoJS you're using.  This page says the default is OFB mode.  This page (for CryptoJS v3) says it's CFB.  Both of those appear to be unofficial third-party sources, but I haven't been able to find any comprehensive official documentation for this library.  Apparently it's "still a bit sparse." :(

In any case, as noted above, you should not use CBC mode with MAC-then-encrypt.  If you want to use CBC, apply the MAC after encryption.


  
  CryptoJS.AES seems pretty generic. Is this defaulting to AES256? Or is that something that I need to explicitly declare in my code?
  


See previous answer.  According to the linked Google Groups post (which may or may not be reliable, of course), the library can handle AES-128, AES-192 or AES-256, presumably depending on the size of the key you pass in.  If you pass in just a password, I have no idea which AES variant it'll choose.

(I'd also like to take this opportunity to reiterate my dislike of poorly documented crypto libraries.  It doesn't matter how well your library is coded, if nobody can tell what it's supposed to be doing and how to use it correctly and safely.)


  
  When passing a string to CryptoJS.AES.encrypt() I've read that it automagically generates a key and and iv 'behind the scenes'. Would it be more secure to generate my own key and iv, or should I just let CryptoJS handle that?
  


It looks like if you pass the encrypt() method a plain string as the key, it assumes that this string is a user-supplied password, and applies a password-based key derivation function like PBKDF2 on it.  Such functions are deliberately designed to be slow, in order to resist brute-force password cracking.  Thus, whenever possible, it's more efficient to call the key derivation function only once yourself, and to store the raw AES and/or HMAC key that it outputs.

You almost certainly don't have any reason to supply your own IV; any decent crypto library should be able to generate perfectly good random IVs for you.


  
  Is there more security to be had by employing a sign-encrypt-sign method, or is that just adding complexity to my app logic?
  


In general, there's no point in doing MAC-encrypt-MAC instead of plain encrypt-then-MAC.  That said, it shouldn't hurt either (beyond wasting a few CPU cycles, and increasing your ciphertext length a little), and it might protect you against some implementation mistakes (like forgetting to include the IV in the outer MAC input).

However, what you really should do, if at all possible, is use an integrated authenticated encryption mode like SIV (my current personal favorite) or GCM (or even the new GCM-SIV mode that combines them).  Those encryption modes come with message authentication built in, and take care of combining the encryption and the authentication safely and correctly.

(For actual digital signatures, sign-encrypt-sign might sometimes be useful, since the double signing protects against some attacks where e.g. the attacker takes a valid encrypt-then-signed message, strips off the signature and substitutes their own, or where a malicious recipient decrypts a signed-then-encrypted message and resends it to someone else, keeping the original signature.  In most cases, however, there are more effective ways to avoid such attacks.  See e.g. this earlier question for more details.)