I am trying to implement DTLS-SRTP in Node.js. The client is a Chrome WebRTC agent and my Node.js implementation acts as the DTLS server.
The client sends a RTP stream consisting of VP8 packets to my implementation and I am trying to decrypt it.
SRTP profile: SRTP_AES128_CM_HMAC_SHA1_80 (0x0001)
Key derivation rate (kdr) : 0
I am not able to decrypt the SRTP packets from the client.
Since my context is DTLS-SRTP, the Key derivation rate is 0. RFC 5764 4.1.2
Since Key Derivation Rate (kdr
) is zero, the packet sequence number does not matter since index DIV kdr
is always zero. (Where DIV is defined in RFC 3711)
Here is my implementation of SRTP master keys and session keys derivation according to RFC 5764
const srtpMasterKeyLength = 16; // bytes
const srtpMasterSaltLength = 14; // bytes
const profileConstants = {
0x0001: 'SRTP_AES128_CM_HMAC_SHA1_80'
}
const profileParams = {
SRTP_AES128_CM_HMAC_SHA1_80: {
cipher: "AES_128_CM",
cipherKeyLengthBits: 128,
cipherSaltLengthBits: 112,
maximumLifetime: 2 ** 31,
authFunction: "HMAC_SHA1",
authKeyLengthBits: 160,
authTagLengthBits: 80,
}
}
const tlsExtractorLabel = Buffer.from('EXTRACTOR-dtls_srtp', 'utf8');
function deriveSRTPKeys(packetIndex){
packetIndex = packetIndex || 0;
const seed = Buffer.concat([tlsData.clientRandom, tlsData.serverRandom]);
const srtpKeyBlock = PRF(tlsData.masterSecret, tlsExtractorLabel, seed, 2 * (srtpMasterKeyLength + srtpMasterSaltLength));
const srtpClientWriteMasterKey = srtpKeyBlock.slice(0, srtpMasterKeyLength);
const srtpServerWriteMasterKey = srtpKeyBlock.slice(srtpMasterKeyLength, 2 * srtpMasterKeyLength);
const srtpClientWriteMasterSalt = srtpKeyBlock.slice(2 * srtpMasterKeyLength, 2 * srtpMasterKeyLength + srtpMasterSaltLength);
const srtpServerWriteMasterSalt = srtpKeyBlock.slice(2 * srtpMasterKeyLength + srtpMasterSaltLength, 2 * (srtpMasterKeyLength + srtpMasterSaltLength));
srtpParams.clientKeys = srtpKDF(srtpClientWriteMasterKey, srtpClientWriteMasterSalt, packetIndex);
srtpParams.serverKeys = srtpKDF(srtpServerWriteMasterKey, srtpServerWriteMasterSalt, packetIndex);
}
function srtpKDF(masterKey, masterSalt, index){
const currentProfile = profileConstants[tlsData.srtpProfile];
const currentProfileParams = profileParams[currentProfile];
const srtpEncryptionKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x00]), index, currentProfileParams.cipherKeyLengthBits / 8);
const srtpAuthenticationKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x01]), index, currentProfileParams.authKeyLengthBits / 8);
const srtpSaltKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x02]), index, currentProfileParams.cipherSaltLengthBits / 8);
const srtcpEncryptionKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x03]), index, currentProfileParams.cipherKeyLengthBits / 8);
const srtcpAuthenticationKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x04]), index, currentProfileParams.authKeyLengthBits / 8);
const srtcpSaltKey = srtpKDFForLabel(masterKey, masterSalt, Buffer.from([0x05]), index, currentProfileParams.cipherSaltLengthBits / 8);
return {
srtpEncryptionKey,
srtpAuthenticationKey,
srtpSaltKey,
srtcpEncryptionKey,
srtcpAuthenticationKey,
srtcpSaltKey
}
}
const DIV = (a, t) => t ? Math.floor(a / t) : 0;
function srtpKDFForLabel(masterKey, masterSalt, labelBuffer, index, length) {
const r = DIV(index, keyDerivationRate);
const rBuffer = Buffer.alloc(6);
rBuffer.writeUIntBE(r, 0, 6);
const keyId = Buffer.concat([labelBuffer, rBuffer]);
// we need to XOR keyId with master salt.
// master salt is 14 bytes long, keyId is 7 bytes long.
// we need to pad keyId with 7 zero on the left.
const paddedKeyId = Buffer.alloc(14);
keyId.copy(paddedKeyId, 7);
const x = Buffer.alloc(14);
for (let i = 0; i < 14; i++) {
x[i] = masterSalt[i] ^ paddedKeyId[i];
}
const fillSize = Math.max(length - 14, 2);
const input = Buffer.concat([x, Buffer.alloc(fillSize)]);
const cipher = crypto.createCipheriv('aes-128-ecb', masterKey, null);
const encrypted = Buffer.concat([cipher.update(input), cipher.final()]);
if(!length){
return Buffer.from([]);
}
// Return the requested length
return encrypted.slice(0, length);
}
For the below values of tlsData and packetIndex,
masterSecret: <Buffer 8c 7b 04 58 1f 64 b5 a3 b8 2f 9a 19 4c a7 f8 0b ec ab 55 4f f5 4e 46 c2 c0 47 db 76 3e 74 2b 63 3f f2 41 e4 0e a5 b2 ae 02 01 42 05 96 55 1b 15>
clientRandom: <Buffer b4 ac 79 c9 28 0e dc 25 c7 92 b9 a2 7f ed 59 a4 ec e5 c5 60 01 ce fc 45 96 e4 d2 a8 2a 3e 4d 3c>
serverRandom: <Buffer 61 cc 55 54 a2 bc 49 a8 5b b1 91 48 10 ef fe f8 aa 2c 7b 6f 6f 7f 43 66 47 69 2d f5 56 08 58 cb>
srtpProfile: 0x0001
these are the srtpMasterKeys I got,
srtpClientWriteMasterKey : d4c0af3f5c00765d639adc8dd9eb73f5
srtpServerWriteMasterKey : e5ded7a3ae6e472ae3aff1abbed8ec08
srtpClientWriteMasterSalt: c2d34663023e8ad0fcc1318b931f
srtpServerWriteMasterSalt: 9d9eb0b02be74451996c2fa482aa
With the above master keys, I have derived the following session keys,
client - srtpEncryptionKey : a91932af5e332477f61bd4114b3ccf44
client - srtpAuthKey : 4403e333322ef447ec366adbf1c604a73dc35948
client - srtpSaltKey : c481ad61727fe97a8108f59183a9
client - srtcpEncryptionKey : 5281eebcc430f1c4ae639e7e69a17657
client - srtcpAuthKey : 7f262e298aea66dcb3dd50daa53faf433dc35948
client - srtcpSaltKey : 5c5f4c6bec5d8f08ccbfc80beb49
server - srtpEncryptionKey : d001aa61b70de77e64d28adb25eed225
server - srtpAuthKey : e608b0730dcebb6fe227615e61a118d4a953426f
server - srtpSaltKey : 39084612afe1746447ba94070cec
server - srtcpEncryptionKey : df38b45c19377474ded019b469b6112c
server - srtcpAuthKey : f4c1bee2aad8478c496301a45ca48fd4a953426f
server - srtcpSaltKey : e1f0979ac8041f76d1af3ee10da5
Here is my implementation to decrypt SRTP packets.
function decryptSRTP(packet){
const sequenceNumber = packet.readUInt16BE(2);
// 12 bytes RTP header and 10 bytes Auth Tag
const payloadLength = packet.length - 12 - 10;
const ssrc = packet.readUInt32BE(8);
const ssrcBuffer = Buffer.alloc(4);
ssrcBuffer.writeUIntBE(ssrc, 0, 4);
const index = ((2 ** 16) * ROC) + sequenceNumber;
const indexBuffer = Buffer.alloc(6);
indexBuffer.writeUIntBE(index, 0, 6);
const keyStreamBuffer = getKeyStream(
srtpParams.clientKeys.srtpEncryptionKey,
srtpParams.clientKeys.srtpSaltKey,
ssrcBuffer, indexBuffer,
payloadLength
);
const encryptedPayload = packet.slice(12, packet.length - 10);
const authTag = packet.slice(packet.length - 10);
// verify auth tag
const hmac = crypto.createHmac('sha1', srtpParams.clientKeys.srtpAuthenticationKey);
hmac.update(encryptedPayload);
const calculatedAuthTag = hmac.digest();
if(!calculatedAuthTag.equals(authTag)){
console.error('Auth tag mismatch');
}
const decryptedPayload = Buffer.alloc(encryptedPayload.length);
for(let i = 0; i < encryptedPayload.length; i++){
decryptedPayload[i] = encryptedPayload[i] ^ keyStreamBuffer[i];
}
console.log('authTag :', authTag.toString('hex'));
console.log('encryptedPayload:', encryptedPayload);
console.log('decryptedPayload:', decryptedPayload);
}
function getKeyStream(encryptionKeyBuffer, saltingKeyBuffer, ssrcBuffer, indexBuffer, length){
const keyStreamBuffer = Buffer.alloc(length);
const blockCount = Math.ceil(length / 16);
for(let i = 0; i < blockCount; i++){
const counter = i;
const keyStreamBlock = getKeyStreamBlock(encryptionKeyBuffer, saltingKeyBuffer, ssrcBuffer, indexBuffer, counter);
const start = i * 16;
const end = Math.min(start + 16, length);
keyStreamBlock.copy(keyStreamBuffer, start, 0, end); // optimize this copy operation
}
return keyStreamBuffer;
}
function getKeyStreamBlock(encryptionKeyBuffer, saltingKeyBuffer, ssrcBuffer, packetIndexBuffer, counter){
//IV = (k_s * 2^16) XOR (SSRC * 2^64) XOR (i * 2^16)
const ivSegment = Buffer.alloc(16);
for(let i = 0; i < 16; i++){
const saltByte = saltingKeyBuffer[i] || 0;
const ssrcByte = ssrcBuffer[i] || 0;
const packetIndexByte = packetIndexBuffer[i] || 0;
ivSegment[i] = saltByte ^ ssrcByte ^ packetIndexByte
}
addNumberToBuffer(ivSegment, counter);
const cipher = crypto.createCipheriv('aes-128-ctr', encryptionKeyBuffer, ivSegment);
cipher.setAutoPadding(false);
const keyStreamBlock = Buffer.concat([
cipher.update(Buffer.alloc(16)),
cipher.final()
])
return keyStreamBlock;
}
Here is a sample RTP packet that I tried to decrypt with above values including RTP headers at the start and SRTP auth tag at the end. (Packet #15, seqNo = 12998 from the dump attached below)
0000 80 e0 32 c6 9c 78 c3 4e f1 b5 d5 44 6e 03 8a c3
0010 b9 f6 b3 28 18 1f 60 8d df d6 ea f2 89 78 8a 88
0020 58 be 43 19 56 c4 50 92 2b 9e cd 94 0e 4a df c8
0030 2b 64 c5 e6 0e 8f 2a ea 0d 00 a4 72 23 b9 4e b4
0040 fd 36 61 72 5c fd f7 f0 7e 4c 54 51 06 e9 ac 38
0050 af a7 78 db cc b9 21 2f 9e f0 fe d0 94 8b 1c 9d
0060 55 57 a9 ff a0 31 4c ea 32 3e 14 73 c9 ac 66 0d
0070 32 54 01 b6 b0 cb 03 e8 46 eb 03 38 1a 84 2b 90
0080 8d ac 34 89 73 79 03 73 b7 4a 69 e9 6b 4e c0 f7
0090 6f 0b da cf 0e 92 ca 2a de 19 51 5f d6 67 ae 21
00a0 38 37 dc 61 3f 9e 56 e2 ae 0f 3c 5d a4 60 c8 49
00b0 45 24 38 c3 86 c3 ff 0f 63 0f 57 af 73 01 93 4f
00c0 92 c0 a1 6c 2e 5f e4 be ce 0b 6b f3 85 34 ac cd
00d0 fb 40 9f a5 66 6f 65 8c 0f df fb 69 9b 46 7a 55
While decrypting, the auth tag verification is failing and the decrypted packet’s contents are not what I expected. (This is a VP8 stream, so I expect the first byte to be 90).
I verified the functions srtpKDFForLabel()
and getKeyStreamBlock()
with the test vectors from in RFC 3711, appendix B.2 and B.3
and it works fine. I also exported key material from client end using openssl s_client -keymatexport Extractor-dtls_srtp
and and verified it with my keyBlock. It also works fine.
Here is a PCAP dump of this session in case if it is required.
What am I doing wrong? Any help would be much appreciated.
Thanks in advance.