![JAR search and dependency download from the Maven repository](/logo.png)
com.googlecode.mp4parser.authoring.tracks.CencEncryptingTrackImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of isoparser Show documentation
Show all versions of isoparser Show documentation
A generic parser and writer for all ISO 14496 based files (MP4, Quicktime, DCF, PDCF, ...)
package com.googlecode.mp4parser.authoring.tracks;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeReaderVariable;
import com.coremedia.iso.boxes.*;
import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
import com.googlecode.mp4parser.MemoryDataSourceImpl;
import com.googlecode.mp4parser.authoring.Edit;
import com.googlecode.mp4parser.authoring.Sample;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.TrackMetaData;
import com.googlecode.mp4parser.authoring.tracks.h264.H264NalUnitHeader;
import com.googlecode.mp4parser.authoring.tracks.h264.H264NalUnitTypes;
import com.googlecode.mp4parser.authoring.tracks.h264.H264TrackImpl;
import com.googlecode.mp4parser.authoring.tracks.h265.H265NalUnitHeader;
import com.googlecode.mp4parser.authoring.tracks.h265.H265NalUnitTypes;
import com.googlecode.mp4parser.authoring.tracks.h265.H265TrackImpl;
import com.googlecode.mp4parser.boxes.cenc.CencEncryptingSampleList;
import com.googlecode.mp4parser.boxes.mp4.samplegrouping.CencSampleEncryptionInformationGroupEntry;
import com.googlecode.mp4parser.boxes.mp4.samplegrouping.GroupEntry;
import com.googlecode.mp4parser.util.RangeStartMap;
import com.mp4parser.iso14496.part15.AvcConfigurationBox;
import com.mp4parser.iso14496.part15.HevcConfigurationBox;
import com.mp4parser.iso23001.part7.CencSampleAuxiliaryDataFormat;
import com.mp4parser.iso23001.part7.TrackEncryptionBox;
import javax.crypto.SecretKey;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.security.SecureRandom;
import java.util.*;
import static com.googlecode.mp4parser.util.CastUtils.l2i;
/**
* Encrypts a given track with common encryption.
*/
public class CencEncryptingTrackImpl implements CencEncryptedTrack {
private final String encryptionAlgo;
Track source;
Map keys = new HashMap();
UUID defaultKeyId;
List samples;
List cencSampleAuxiliaryData;
boolean dummyIvs = false;
boolean subSampleEncryption = false;
SampleDescriptionBox stsd = null;
RangeStartMap indexToKey;
Map sampleGroups;
Object configurationBox;
public CencEncryptingTrackImpl(Track source, UUID defaultKeyId, SecretKey key, boolean dummyIvs) {
this(source, defaultKeyId, Collections.singletonMap(defaultKeyId, key),
null,
"cenc", dummyIvs);
}
public CencEncryptingTrackImpl(Track source, UUID defaultKeyId, Map keys,
Map keyRotation,
String encryptionAlgo, boolean dummyIvs) {
this(source, defaultKeyId, keys, keyRotation, encryptionAlgo, dummyIvs, false);
}
/**
* Encrypts a given source track.
*
* @param source unencrypted source file
* @param defaultKeyId the default key ID - might be null if sample are not encrypted by default
* @param keys key ID to key map
* @param keyRotation assigns an encryption group to a number of samples
* @param encryptionAlgo cenc or cbc1 (don't use cbc1)
* @param dummyIvs disables RNG for IVs and use IVs starting with 0x00...000
* @param encryptButAllClear will cause sub sample encryption format to keep full sample in clear (clear/encrypted pair will be len(sample)/0
*/
public CencEncryptingTrackImpl(Track source, UUID defaultKeyId, Map keys,
Map keyRotation,
String encryptionAlgo, boolean dummyIvs, boolean encryptButAllClear) {
this.source = source;
this.keys = keys;
this.defaultKeyId = defaultKeyId;
this.dummyIvs = dummyIvs;
this.encryptionAlgo = encryptionAlgo;
this.sampleGroups = new HashMap();
for (Map.Entry entry : source.getSampleGroups().entrySet()) {
if (!(entry.getKey() instanceof CencSampleEncryptionInformationGroupEntry)) {
sampleGroups.put(entry.getKey(), entry.getValue());
}
}
if (keyRotation != null) {
for (Map.Entry entry : keyRotation.entrySet()) {
sampleGroups.put(entry.getKey(), entry.getValue());
}
}
this.sampleGroups = new HashMap(sampleGroups) {
@Override
public long[] put(GroupEntry key, long[] value) {
if (key instanceof CencSampleEncryptionInformationGroupEntry) {
throw new RuntimeException("Please supply CencSampleEncryptionInformationGroupEntries in the constructor");
}
return super.put(key, value);
}
};
this.samples = source.getSamples();
this.cencSampleAuxiliaryData = new ArrayList();
BigInteger one = new BigInteger("1");
byte[] init = new byte[]{0, 0, 0, 0, 0, 0, 0, 0};
if (!dummyIvs) {
Random random = new SecureRandom();
random.nextBytes(init);
}
BigInteger ivInt = new BigInteger(1, init);
List groupEntries =
new ArrayList();
if (keyRotation != null) {
groupEntries.addAll(keyRotation.keySet());
}
indexToKey = new RangeStartMap();
int lastSampleGroupDescriptionIndex = -1;
for (int i = 0; i < source.getSamples().size(); i++) {
int index = 0;
for (int j = 0; j < groupEntries.size(); j++) {
GroupEntry groupEntry = groupEntries.get(j);
long[] sampleNums = getSampleGroups().get(groupEntry);
if (Arrays.binarySearch(sampleNums, i) >= 0) {
index = j + 1;
}
}
if (lastSampleGroupDescriptionIndex != index) {
if (index == 0) {
indexToKey.put(i, keys.get(defaultKeyId));
} else {
if (groupEntries.get(index - 1).getKid() != null) {
SecretKey sk = keys.get(groupEntries.get(index - 1).getKid());
if (sk == null) {
throw new RuntimeException("Key " + groupEntries.get(index - 1).getKid() + " was not supplied for decryption");
}
indexToKey.put(i, sk);
} else {
indexToKey.put(i, null);
}
}
lastSampleGroupDescriptionIndex = index;
}
}
List boxes = source.getSampleDescriptionBox().getSampleEntry().getBoxes();
int nalLengthSize = -1;
for (Box box : boxes) {
if (box instanceof AvcConfigurationBox) {
AvcConfigurationBox avcC = (AvcConfigurationBox) (configurationBox = box);
subSampleEncryption = true;
nalLengthSize = avcC.getLengthSizeMinusOne() + 1;
}
if (box instanceof HevcConfigurationBox) {
HevcConfigurationBox hvcC = (HevcConfigurationBox) (configurationBox = box);
subSampleEncryption = true;
nalLengthSize = hvcC.getLengthSizeMinusOne() + 1;
}
}
for (int i = 0; i < samples.size(); i++) {
Sample origSample = samples.get(i);
CencSampleAuxiliaryDataFormat e = new CencSampleAuxiliaryDataFormat();
this.cencSampleAuxiliaryData.add(e);
if (indexToKey.get(i) != null) {
byte[] iv = ivInt.toByteArray();
byte[] eightByteIv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0};
System.arraycopy(
iv,
iv.length - 8 > 0 ? iv.length - 8 : 0,
eightByteIv,
(8 - iv.length) < 0 ? 0 : (8 - iv.length),
iv.length > 8 ? 8 : iv.length);
e.iv = eightByteIv;
ByteBuffer sample = (ByteBuffer) origSample.asByteBuffer().rewind();
if (subSampleEncryption) {
if (encryptButAllClear) {
e.pairs = new CencSampleAuxiliaryDataFormat.Pair[]{e.createPair(sample.remaining(), 0)};
} else {
List pairs = new ArrayList(5);
while (sample.remaining() > 0) {
int nalLength = l2i(IsoTypeReaderVariable.read(sample, nalLengthSize));
int clearBytes;
int nalGrossSize = nalLength + nalLengthSize;
if (nalGrossSize < 112 || isClearNal(sample.duplicate())) {
clearBytes = nalGrossSize;
} else {
clearBytes = 96 + nalGrossSize % 16;
}
pairs.add(e.createPair(clearBytes, nalGrossSize - clearBytes));
sample.position(sample.position() + nalLength);
}
e.pairs = pairs.toArray(new CencSampleAuxiliaryDataFormat.Pair[pairs.size()]);
}
}
ivInt = ivInt.add(one);
}
}
}
public UUID getDefaultKeyId() {
return defaultKeyId;
}
public boolean hasSubSampleEncryption() {
return subSampleEncryption;
}
public List getSampleEncryptionEntries() {
return cencSampleAuxiliaryData;
}
public synchronized SampleDescriptionBox getSampleDescriptionBox() {
if (stsd == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
source.getSampleDescriptionBox().getBox(Channels.newChannel(baos));
stsd = (SampleDescriptionBox) new IsoFile(new MemoryDataSourceImpl(baos.toByteArray())).getBoxes().get(0);
} catch (IOException e) {
throw new RuntimeException("Dumping stsd to memory failed");
}
// stsd is now a copy of the original stsd. Not very efficient but we don't have to do that a hundred times ...
OriginalFormatBox originalFormatBox = new OriginalFormatBox();
originalFormatBox.setDataFormat(stsd.getSampleEntry().getType());
if (stsd.getSampleEntry() instanceof AudioSampleEntry) {
((AudioSampleEntry) stsd.getSampleEntry()).setType("enca");
} else if (stsd.getSampleEntry() instanceof VisualSampleEntry) {
((VisualSampleEntry) stsd.getSampleEntry()).setType("encv");
} else {
throw new RuntimeException("I don't know how to cenc " + stsd.getSampleEntry().getType());
}
ProtectionSchemeInformationBox sinf = new ProtectionSchemeInformationBox();
sinf.addBox(originalFormatBox);
SchemeTypeBox schm = new SchemeTypeBox();
schm.setSchemeType(encryptionAlgo);
schm.setSchemeVersion(0x00010000);
sinf.addBox(schm);
SchemeInformationBox schi = new SchemeInformationBox();
TrackEncryptionBox trackEncryptionBox = new TrackEncryptionBox();
trackEncryptionBox.setDefaultIvSize(defaultKeyId == null ? 0 : 8);
trackEncryptionBox.setDefaultAlgorithmId(defaultKeyId == null ? 0x0 : 0x01);
trackEncryptionBox.setDefault_KID(defaultKeyId == null ? new UUID(0, 0) : defaultKeyId);
schi.addBox(trackEncryptionBox);
sinf.addBox(schi);
stsd.getSampleEntry().addBox(sinf);
}
return stsd;
}
public long[] getSampleDurations() {
return source.getSampleDurations();
}
public long getDuration() {
return source.getDuration();
}
public List getCompositionTimeEntries() {
return source.getCompositionTimeEntries();
}
public long[] getSyncSamples() {
return source.getSyncSamples();
}
public List getSampleDependencies() {
return source.getSampleDependencies();
}
public TrackMetaData getTrackMetaData() {
return source.getTrackMetaData();
}
public String getHandler() {
return source.getHandler();
}
public List getSamples() {
return new CencEncryptingSampleList(indexToKey, source.getSamples(), cencSampleAuxiliaryData, encryptionAlgo);
}
public SubSampleInformationBox getSubsampleInformationBox() {
return source.getSubsampleInformationBox();
}
public void close() throws IOException {
source.close();
}
public String getName() {
return "enc(" + source.getName() + ")";
}
public List getEdits() {
return source.getEdits();
}
public Map getSampleGroups() {
return sampleGroups;
}
public boolean isClearNal(ByteBuffer s) {
if (configurationBox instanceof HevcConfigurationBox) {
H265NalUnitHeader nuh = H265TrackImpl.getNalUnitHeader(s.slice());
return !( // These ranges are all slices --> NOT CLEAR
(nuh.nalUnitType >= H265NalUnitTypes.NAL_TYPE_TRAIL_N && (nuh.nalUnitType <= H265NalUnitTypes.NAL_TYPE_RASL_R)) ||
(nuh.nalUnitType >= H265NalUnitTypes.NAL_TYPE_BLA_W_LP && (nuh.nalUnitType <= H265NalUnitTypes.NAL_TYPE_CRA_NUT)) ||
(nuh.nalUnitType >= H265NalUnitTypes.NAL_TYPE_BLA_W_LP && (nuh.nalUnitType <= H265NalUnitTypes.NAL_TYPE_CRA_NUT))
);
} else if (configurationBox instanceof AvcConfigurationBox) {
// only encrypt
H264NalUnitHeader nuh = H264TrackImpl.getNalUnitHeader(s.slice());
return !(nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_AUX_PIC ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_DATA_PART_A ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_DATA_PART_B ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_DATA_PART_C ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_EXT ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_IDR ||
nuh.nal_unit_type == H264NalUnitTypes.CODED_SLICE_NON_IDR
);
} else {
throw new RuntimeException("Subsample encryption is activated but the CencEncryptingTrackImpl can't say if this sample is to be encrypted or not!");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy