/* * Copyright © 2012, castLabs GmbH, www.castlabs.com */ package com.googlecode.mp4parser.boxes.cenc; import com.googlecode.mp4parser.authoring.Sample; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.AbstractList; import java.util.List; import static com.googlecode.mp4parser.util.CastUtils.l2i; public class CencEncryptingSampleList extends AbstractList<Sample> { List<CencSampleAuxiliaryDataFormat> auxiliaryDataFormats; SecretKey secretKey; List<Sample> parent; static Cipher cipher; static { try { cipher = Cipher.getInstance("AES/CTR/NoPadding"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (NoSuchPaddingException e) { throw new RuntimeException(e); } } public CencEncryptingSampleList( SecretKey secretKey, List<Sample> parent, List<CencSampleAuxiliaryDataFormat> auxiliaryDataFormats) { this.auxiliaryDataFormats = auxiliaryDataFormats; this.secretKey = secretKey; this.parent = parent; } @Override public Sample get(int index) { Sample clearSample = parent.get(index); CencSampleAuxiliaryDataFormat entry = auxiliaryDataFormats.get(index); return new EncryptedSampleImpl(clearSample, entry, cipher); } private class EncryptedSampleImpl implements Sample { private final Sample clearSample; private final CencSampleAuxiliaryDataFormat cencSampleAuxiliaryDataFormat; private final Cipher cipher; private EncryptedSampleImpl( Sample clearSample, CencSampleAuxiliaryDataFormat cencSampleAuxiliaryDataFormat, Cipher cipher) { this.clearSample = clearSample; this.cencSampleAuxiliaryDataFormat = cencSampleAuxiliaryDataFormat; this.cipher = cipher; } public void writeTo(WritableByteChannel channel) throws IOException { ByteBuffer sample = (ByteBuffer) clearSample.asByteBuffer().rewind(); initCipher(cencSampleAuxiliaryDataFormat.iv); try { if (cencSampleAuxiliaryDataFormat.pairs != null && cencSampleAuxiliaryDataFormat.pairs.length > 0) { for (CencSampleAuxiliaryDataFormat.Pair pair : cencSampleAuxiliaryDataFormat.pairs) { byte[] clears = new byte[pair.clear()]; sample.get(clears); channel.write(ByteBuffer.wrap(clears)); if (pair.encrypted() > 0) { byte[] toBeEncrypted = new byte[l2i(pair.encrypted())]; sample.get(toBeEncrypted); assert (toBeEncrypted.length % 16) == 0; byte[] encrypted = cipher.update(toBeEncrypted); assert encrypted.length == toBeEncrypted.length; channel.write(ByteBuffer.wrap(encrypted)); } } } else { byte[] fullyEncryptedSample = new byte[sample.limit()]; sample.get(fullyEncryptedSample); channel.write(ByteBuffer.wrap(cipher.doFinal(fullyEncryptedSample))); } sample.rewind(); } catch (IllegalBlockSizeException e) { throw new RuntimeException(e); } catch (BadPaddingException e) { throw new RuntimeException(e); } } public long getSize() { return clearSample.getSize(); } public ByteBuffer asByteBuffer() { ByteBuffer sample = (ByteBuffer) clearSample.asByteBuffer().rewind(); ByteBuffer encSample = ByteBuffer.allocate(sample.limit()); CencSampleAuxiliaryDataFormat entry = cencSampleAuxiliaryDataFormat; initCipher(cencSampleAuxiliaryDataFormat.iv); try { if (entry.pairs != null) { for (CencSampleAuxiliaryDataFormat.Pair pair : entry.pairs) { byte[] clears = new byte[pair.clear()]; sample.get(clears); encSample.put(clears); if (pair.encrypted() > 0) { byte[] toBeEncrypted = new byte[l2i(pair.encrypted())]; sample.get(toBeEncrypted); assert (toBeEncrypted.length % 16) == 0; byte[] encrypted = cipher.update(toBeEncrypted); assert encrypted.length == toBeEncrypted.length; encSample.put(encrypted); } } } else { byte[] fullyEncryptedSample = new byte[sample.limit()]; sample.get(fullyEncryptedSample); encSample.put(cipher.doFinal(fullyEncryptedSample)); } sample.rewind(); } catch (IllegalBlockSizeException e) { throw new RuntimeException(e); } catch (BadPaddingException e) { throw new RuntimeException(e); } encSample.rewind(); return encSample; } } protected void initCipher(byte[] iv) { try { byte[] fullIv = new byte[16]; System.arraycopy(iv, 0, fullIv, 0, iv.length); // The IV cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(fullIv)); } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } } @Override public int size() { return parent.size(); } }