/** * * Code derived and adapted from the Jitsi client side SRTP framework. * * Distributed under LGPL license. * See terms of license at gnu.org. */ package org.restcomm.media.rtp.crypto; import java.nio.ByteBuffer; import java.util.Arrays; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.params.KeyParameter; /** * SRTPCipherF8 implements SRTP F8 Mode AES Encryption (AES-f8). * F8 Mode AES Encryption algorithm is defined in RFC3711, section 4.1.2. * * Other than Null Cipher, RFC3711 defined two two encryption algorithms: * Counter Mode AES Encryption and F8 Mode AES encryption. Both encryption * algorithms are capable to encrypt / decrypt arbitrary length data, and the * size of packet data is not required to be a multiple of the AES block * size (128bit). So, no padding is needed. * * Please note: these two encryption algorithms are specially defined by SRTP. * They are not common AES encryption modes, so you will not be able to find a * replacement implementation in common cryptographic libraries. * * As defined by RFC3711: F8 mode encryption is optional. * * mandatory to impl optional default * ------------------------------------------------------------------------- * encryption AES-CM, NULL AES-f8 AES-CM * message integrity HMAC-SHA1 - HMAC-SHA1 * key derivation (PRF) AES-CM - AES-CM * * We use AESCipher to handle basic AES encryption / decryption. * * @author Bing SU (nova.su@gmail.com) * @author Werner Dittmann <werner.dittmann@t-online.de> */ public class SRTPCipherF8 { /** * AES block size, just a short name. */ private final static int BLKLEN = 16; /** * F8 mode encryption context, see RFC3711 section 4.1.2 for detailed * description. */ class F8Context { public byte[] S; public byte[] ivAccent; long J; } public static void deriveForIV(BlockCipher f8Cipher, byte[] key, byte[] salt) { /* * Get memory for the special key. This is the key to compute the * derived IV (IV'). */ byte[] saltMask = new byte[key.length]; byte[] maskedKey = new byte[key.length]; /* * First copy the salt into the mask field, then fill with 0x55 to get a * full key. */ System.arraycopy(salt, 0, saltMask, 0, salt.length); for (int i = salt.length; i < saltMask.length; ++i) saltMask[i] = 0x55; /* * XOR the original key with the above created mask to get the special * key. */ for (int i = 0; i < key.length; i++) maskedKey[i] = (byte) (key[i] ^ saltMask[i]); /* * Prepare the f8Cipher with the special key to compute IV' */ KeyParameter encryptionKey = new KeyParameter(maskedKey); f8Cipher.init(true, encryptionKey); saltMask = null; maskedKey = null; } public static void process(BlockCipher cipher, ByteBuffer data, int off, int len, byte[] iv, BlockCipher f8Cipher) { F8Context f8ctx = new SRTPCipherF8().new F8Context(); /* * Get memory for the derived IV (IV') */ f8ctx.ivAccent = new byte[BLKLEN]; /* * Use the derived IV encryption setup to encrypt the original IV to produce IV'. */ f8Cipher.processBlock(iv, 0, f8ctx.ivAccent, 0); f8ctx.J = 0; // initialize the counter f8ctx.S = new byte[BLKLEN]; // get the key stream buffer Arrays.fill(f8ctx.S, (byte) 0); int inLen = len; while (inLen >= BLKLEN) { processBlock(cipher, f8ctx, data, off, data, off, BLKLEN); inLen -= BLKLEN; off += BLKLEN; } if (inLen > 0) { processBlock(cipher, f8ctx, data, off, data, off, inLen); } } /** * Encrypt / Decrypt a block using F8 Mode AES algorithm, read len bytes * data from in at inOff and write the output into out at outOff * * @param f8ctx * F8 encryption context * @param in * byte array holding the data to be processed * @param inOff * start offset of the data to be processed inside in array * @param out * byte array that will hold the processed data * @param outOff * start offset of output data in out * @param len * length of the input data */ private static void processBlock(BlockCipher cipher, F8Context f8ctx, ByteBuffer in, int inOff, ByteBuffer out, int outOff, int len) { /* * XOR the previous key stream with IV' * ( S(-1) xor IV' ) */ for (int i = 0; i < BLKLEN; i++) f8ctx.S[i] ^= f8ctx.ivAccent[i]; /* * Now XOR (S(n-1) xor IV') with the current counter, then increment * the counter */ f8ctx.S[12] ^= f8ctx.J >> 24; f8ctx.S[13] ^= f8ctx.J >> 16; f8ctx.S[14] ^= f8ctx.J >> 8; f8ctx.S[15] ^= f8ctx.J >> 0; f8ctx.J++; /* * Now compute the new key stream using AES encrypt */ cipher.processBlock(f8ctx.S, 0, f8ctx.S, 0); /* * As the last step XOR the plain text with the key stream to produce * the cipher text. */ for (int i = 0; i < len; i++) out.put(outOff + i, (byte) (in.get(inOff + i) ^ f8ctx.S[i])); } }