@DoctorB I think you’ve got the broad strokes of this scheme down, but your description is inaccurate in some of its details.
First, the genesis of Key1 (account encryption key) and Key2 (MAC key): These are not derived from the Stretched Master Key, but they are produced by a random number generator that produced a 512-bit random number (the Generated Symmetric Key). The the Generated Symmetric Key is then split into Key1 and Key2, which have 256 bits of entropy each.
Second, the AES encryption scheme and the computation of the MAC digest both require a piece of data to be encrypted or hashed, as well as a secret key (which influences the computed result). Thus, as explained in Dmitry’s article, the two operations are performed as follows:
encryptedData = AES-CBC(key1, data)
MAC = HMAC(key2, encryptedData)
cipherString = encryptedData + MAC
[Edit: Either I misread your post, or you may have edited it while I was composing this response — it seems now that your description of ciphertext/MAC generation from Key1 & Key2 matches what I’ve written above, except that we use slightly different terminology.]
The role of the Stretched Master Key is to be an encryption key for AES-encryption of the Generated Symmetric Key (= Key1 + Key2). This produces the so-called Protected Symmetric Key.
Bitwarden’s cloud database contains both your vault (including all of the cipher strings that each consist of encrypted data and a corresponding MAC) and your account’s Protected Symmetric Key.
Therefore, the attack would consist of computing a candidate stretched master key based on a master password guess, and use this key as the AES decryption key to attempt to decipher the Protected Symmetric Key. This will results in 512 bits (64 bytes) of data, which can be divided down the middle to produce two keys (MaybeKey1 and MaybeKey2); these two keys may be just garbage (if the password guess was wrong), or they may be the actual Key1 and Key2 (if the password guess was correct). The attacker doesn’t know yet whether the guess was right or wrong, but now they simply take a cipher string from the vault, and compute a MAC using
HMAC(MaybeKey2, encryptedData) — if the result matches the actual MAC that was stored in the cipher string, that implies that MaybeKey2 was equal to the original Key2. Therefore, it must also be true that MaybeKey1 equals the original Key1 (the account encryption key that can be used to decrypt all the encrypted data stored in the vault).