Here is some code I wrote to use a device key to determine a processing key via the subset-difference process. This code essentially consolidates the libaacs functions _find_dk(), _calc_mk_dks(), and _calc_v_mask(), along with some macros. The input to the function is a candidate device key and an index into the record arrays for the encrypted media key and subset-differences. The function returns a processing key if the device key is valid, otherwise it returns an error code. I'm not including all the lower-level functions here.
There is some ambiguity in the AACS spec as far as what record array index to start from when testing a candidate device key in the subset-difference tree. My code follows the model used in libaacs in which we simply test every index for a given device key and look for the first one that delivers a valid processing key. Unfortunately, this can be slow since there may be 500+ records to test and each one requires iterating over a subset-difference tree.
Code:
// attempts to derive a processing key from a given device key and subset difference record index
// follows logic from AACS Common Spec section 3.2.4
// sdIdx = index of subset difference record to use
// deviceKey = 16 byte device key to use
// processingKey = 16 byte processing key if sucessful
// mediaKey = 16 byte media key if successful
// returns 0 if found processing key, -1 if no valid key, -2 if device key is revoked
int DeriveProcessingKeyForDeviceKeySubsetRecord(BDAACSMKBStatus *mkbStat, int sdIdx,
unsigned char *deviceKey, unsigned char *processingKey, unsigned char *mediaKey)
{
int result;
int32_t tmvIter;
uint32_t m, n, mu, mv, uv, mvIter;
unsigned char uvNumber[4], devKey[16], verifyData[16], encDataKey[16], procKey[16], medKey[16];
// get the verification data (MKB record type 0x81)
BDAACSMKBGetVerificationData(mkbStat, verifyData);
// get encrypted data key for this subset difference index (MKB record type 0x05)
BDAACSMKBGetMediaKeyData(mkbStat, sdIdx, encDataKey);
// get subset diffence data for this index (MKB record type 0x04)
BDAACSMKBGetSubsetDiffData(mkbStat, sdIdx, &mu, uvNumber);
// check u mask value from record
// AACS common spec section 3.2.3:
// if the u mask value is not of the bit form 00xxxxxx, this marks the end of the list
if ((mu & 0xC0) != 0) {
// end of the list, device at this record location is revoked
return -2;
}
// compute uv from uvNumber in endian independent way
uv = uvNumber[0];
uv = (uv << 8) | uvNumber[1];
uv = (uv << 8) | uvNumber[2];
uv = (uv << 8) | uvNumber[3];
if (uv == 0)
return -1;
// following AACS common spec section 3.2.3, derive v mask mv from the uv number
// the v mask is given by the first lower-order 1-bit in the uv number
// that bit, and all lower-order 0-bits, are zero bits in the v mask
mv = 0xFFFFFFFF;
while ((uv & ~mv) == 0)
mv <<= 1;
// following AACS common spec section 3.2.5.1.5, encoded u-mask value is actually
// the number of low-order 0-bits in the mask
mu = 0xFFFFFFFF << mu;
// section 3.2.3: the mu mask always has more trailing 0 bits than the mv mask
// the deeper the position of a node in the tree, the shorter the sequence of 0-bits
// in the mask associated to that node, and u is an ancestor of v
if (mv < mu)
return -1;
// initialize iterator and starting device key
mvIter = mv;
result = -1;
while (mvIter != mu) {
// start with initial device key
memcpy(devKey, deviceKey, 16);
// repeat subset-difference iteration as needed
tmvIter = mvIter;
while (tmvIter != mv) {
// determine the bit position of the most significant 0 bit in iterator
// use algorithm from Hacker's Delight, number of trailing zeros
m = ~tmvIter & (tmvIter - 1);
n = 1;
while (m != 0) {
n <<= 1;
m >>= 1;
}
n >>= 1;
// determine whether to take a right or a left subsidiary Device Key
if ((uv & n) == 0) {
// use left subsidiary Device key
AACSEncryptAESG3(devKey, devKey, 0, 0);
}
else {
// use right subsidiary Device key
AACSEncryptAESG3(devKey, 0, 0, devKey);
}
// arithmetic shift right using signed integer
tmvIter >>= 1;
}
// get processing key at this position
AACSEncryptAESG3(devKey, 0, procKey, 0);
// attempt to derive media key using this processing key to check if it is valid
result = AACSDeriveMediaKeyFromProcessKey(procKey, encDataKey, uvNumber, verifyData, medKey);
if (result == 0) {
// valid processing key
if (processingKey)
memcpy(processingKey, procKey, 16);
if (mediaKey)
memcpy(mediaKey, medKey, 16);
mvIter = mu;
}
else
mvIter <<= 1;
}
return result;
}