Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCM key unwrapping does not validate tag and returns 16 bytes of garbage header #36

Open
marcan opened this issue Jan 3, 2019 · 3 comments
Assignees

Comments

@marcan
Copy link

marcan commented Jan 3, 2019

The entire world apparently calls what BitLocker uses to wrap keys "AES-CCM". Unfortunately, that's not what is actually implemented. The algorithm implemented by libcaes_crypt_ccm isn't CCM: it doesn't have a MAC, it doesn't have associated data, and it doesn't use the first keystream block for that. That makes it just AES-CTR.

The only thing "CCM" about it is that the nonce is prepended with a byte of value 15 - (uint8_t) nonce_size - 1 (which CCM does, and is not inherent to CTR mode). There's none of the other bits that would make it compliant with the CCM specification (RFC3610). Crucially, BitLocker key unwrapping can be implemented using a standard AES-CTR implementation (just prepend 0x02 to the nonce), but cannot be implemented using a standard AES-CCM implementation (because there is no way to disable the whole MAC machinery).

I would recommend renaming all mentions of CCM to CTR, to avoid confusion. I just spent a few hours wondering why using PyCryptodome yielded incorrect decryptions of BitLocker wrapped keys. Instead, CTR mode is what you want. In PyCryptodome:

python
def decrypt(p, k):
nonce = p[:12]
nonce = bytes([15 - len(nonce) - 1]) + nonce
aes = AES.new(k, AES.MODE_CTR, nonce=nonce)
a = aes.decrypt(p[12:])
return a

~~
is the code you'd want to unwrap a BitLocker wrapped key, with MODE_CTR, not MODE_CCM.

@marcan
Copy link
Author

marcan commented Jan 3, 2019

I take that back; it is CCM, it's just that the tag is prepended. I was really confused because there was no notion of the two chunks in the libbde codebase.

So the problem is that libcaes_crypt_ccm doesn't actually implement CCM. It implements the CTR part of CCM, and then returns 16 bytes of garbage in front of the result (what would be the tag section).

This is a correct implementation of BitLocker wrapped key decryption:

def decrypt(p, k):
    nonce = p[:12]
    tag = p[12:28]
    aes = AES.new(k, AES.MODE_CCM, nonce=nonce)
    return aes.decrypt_and_verify(p[28:], received_mac_tag=tag)

So the format of AES-CCM encrypted keys is:

  • 12 bytes nonce
  • 16 bytes tag
  • remaining bytes are ciphertext

And a correct implementation of AES-CCM would validate the tag correctly according to RFC3610, and return an error if it mismatches (this also lets you recognize if the wrong key was supplied).

@marcan marcan changed the title Key wrapping algorithm name is incorrect (CCM should be CTR) CCM key unwrapping does not validate tag and returns 16 bytes of garbage header Jan 3, 2019
@joachimmetz
Copy link
Member

@marcan interesting, thx for the update.

This seems to be related to libyal/libcaes#2. When time permits I'll have a look to likely move this function into libbde and remove it from libcaes.

@marcan
Copy link
Author

marcan commented Jan 4, 2019

I think it should be fine in libcaes as it is a standard mode, if the interface is changed to be standard-compliant (and then libbde is changed to use it in a standard way).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants