1

I'm attempting to write code to decrypt a JWE token in PHP, as the existing libraries don't support the algorithm I need (A128CBC+HS256, it's a deprecated algorithm).

My issue is I can't understand how to generate the content encryption key which uses a "Concatenation Key Derivation Function" (see section 5.8.1 here: http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf). The symbols and explanation of the function goes over my head.

I'm getting my values based on JOSE JSON web algorithms draft 06.

So far, the relevant portion of my code looks like this:

// Derive CBC encryption & integrity keys
$shaSize = 256;
$encryptionKeySize = $shaSize / 2;
$integrityKeySize = $shaSize;

// Calculate the key derivation using Concat KDF for the content 
// encryption key
$encryptionSegments = [
    $masterKey,         // Z
    $encryptionKeySize, // keydatalen
    $this->packInt32sBe($encryptionKeySize) . utf8_encode('A128CBC+HS256'), // AlgorithmID
    $this->packInt32sBe(0), // PartyUInfo
    $this->packInt32sBe(0), // PartyUInfo
    'Encryption',           // SuppPubInfo
    $this->packInt32sBe(1), // SuppPrivInfo
];

// Calculate the SHA256 digest
$cek = hex2bin(hash('sha256', implode('', $encryptionSegments)));

Possibly relevant, my function for getting a big endian integer:

public function packInt32sBe($n)
{
    if (pack('L', 1) === pack('N', 1)) {
        return pack('l', $n);
    }

    return strrev(pack('l', $n));
}

The only variable not shown here is $masterKey which is the decrypted content master key.

Brandon
  • 16,382
  • 12
  • 55
  • 88

2 Answers2

3

I did end up solving this. Not sure if it'll ever help anyone else, but just in case:

// Derive CBC encryption & integrity keys
$shaSize = 256;
$encryptionKeySize = $shaSize / 2;
$integrityKeySize = $shaSize;

// Calculate the key derivation using Concat KDF for the content 
// encryption key
$encryptionSegments = [
    $this->packInt32sBe(1), 
    $cmk,                              // Z
    $this->packInt32sBe($encryptionKeySize) . utf8_encode('A128CBC+HS256'), // AlgorithmID
    $this->packInt32sBe(0), // PartyUInfo
    $this->packInt32sBe(0), // PartyUInfo
    'Encryption',           // SuppPubInfo
];

// Calculate the SHA256 digest, and then get the first 16 bytes of it
$cek = substr(hex2bin(hash('sha256', implode('', $encryptionSegments))), 0, 16);

The only unknown variable here is $cmk which is my content master key, aka the "Z" value. In this specific case I got the master key by decrypting it from an XBOX One token request.

Brandon
  • 16,382
  • 12
  • 55
  • 88
  • You can optimize your method `packInt32sBe` uing the following single line `return hex2bin(str_pad(dechex($value), 8, "0", STR_PAD_LEFT));` – Spomky-Labs Jan 03 '15 at 20:35
1

Here is my own implementation according to the same specification, but draft #39:

<?php

class ConcatKDF
{
    public static function generate($Z, $encryption_algorithm, $encryption_key_size, $apu = "", $apv = "")
    {
        $encryption_segments = array(
            self::toInt32Bits(1),                                                   // Round number 1
            $Z,                                                                     // Z (shared secret)
            self::toInt32Bits(strlen($encryption_algorithm)).$encryption_algorithm, // Size of algorithm and algorithm
            self::toInt32Bits(strlen($apu)).$apu,                                   // PartyUInfo
            self::toInt32Bits(strlen($apv)).$apv,                                   // PartyVInfo
            self::toInt32Bits($encryption_key_size),                                // SuppPubInfo (the encryption key size)
            "",                                                                     // SuppPrivInfo
        );

        return substr(hex2bin(hash('sha256', implode('', $encryption_segments))), 0, $encryption_key_size/8);
    }

    private static function toInt32Bits($value)
    {
        return hex2bin(str_pad(dechex($value), 8, "0", STR_PAD_LEFT));
    }
}

The use is very simple:

ConcatKDF::generate("The shared key here", 'A128CBC+HS256', 128);

If you have apu and apv parameters:

ConcatKDF::generate("Another shared key here", 'A128GCM', 128, "Alice", "Bob");
Spomky-Labs
  • 15,473
  • 5
  • 40
  • 64