Android 使用CipherInputStream解密文件

Android 使用CipherInputStream解密文件,android,encryption,Android,Encryption,我有一个要求,我需要下载一个加密形式的视频文件,播放时我需要解密和播放。为此,我使用CipherOutputstream和CipherInputStream。我使用下面的代码对文件进行加密和解密。加密进行得很顺利,但我对11MB文件的解密需要很长时间。而且解密后的文件无法播放。下面是我的代码。我创建了一个自定义数据源类,但无法将其附加到exoplayer。如何将自定义数据源类附加到exoplayer public final class EncryptedFileDataSource imple

我有一个要求,我需要下载一个加密形式的视频文件,播放时我需要解密和播放。为此,我使用CipherOutputstream和CipherInputStream。我使用下面的代码对文件进行加密和解密。加密进行得很顺利,但我对11MB文件的解密需要很长时间。而且解密后的文件无法播放。下面是我的代码。我创建了一个自定义数据源类,但无法将其附加到exoplayer。如何将自定义数据源类附加到exoplayer

public final class EncryptedFileDataSource implements DataSource {

private final TransferListener<? super EncryptedFileDataSource> mTransferListener;
private StreamingCipherInputStream mInputStream;
private Uri mUri;
private long mBytesRemaining;
private boolean mOpened;
private Cipher mCipher;

public EncryptedFileDataSource(Cipher cipher, TransferListener<? super EncryptedFileDataSource> listener) {
    mCipher = cipher;
    mTransferListener = listener;
}

@Override
public long open(DataSpec dataSpec) throws EncryptedFileDataSourceException {
    // if we're open, we shouldn't need to open again, fast-fail
    if (mOpened) {
        return mBytesRemaining;
    }
    // #getUri is part of the contract...
    mUri = dataSpec.uri;
    // put all our throwable work in a single block, wrap the error in a custom Exception
    try {
        setupInputStream();
        skipToPosition(dataSpec);
        computeBytesRemaining(dataSpec);
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    }
    // if we made it this far, we're open
    mOpened = true;
    // notify
    if (mTransferListener != null) {
        mTransferListener.onTransferStart(this, dataSpec);
    }
    // report
    return mBytesRemaining;
}

private void setupInputStream() throws FileNotFoundException {
    File encryptedFile = new File(mUri.getPath());
    FileInputStream fileInputStream = new FileInputStream(encryptedFile);
    mInputStream = new StreamingCipherInputStream(fileInputStream, mCipher);
}

private void skipToPosition(DataSpec dataSpec) throws IOException {
    mInputStream.forceSkip(dataSpec.position);
}

private void computeBytesRemaining(DataSpec dataSpec) throws IOException {
    if (dataSpec.length != C.LENGTH_UNSET) {
        mBytesRemaining = dataSpec.length;
    } else {
        mBytesRemaining = mInputStream.available();
        if (mBytesRemaining == Integer.MAX_VALUE) {
            mBytesRemaining = C.LENGTH_UNSET;
        }
    }
}

@Override
public int read(byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException {
    // fast-fail if there's 0 quantity requested or we think we've already processed everything
    if (readLength == 0) {
        return 0;
    } else if (mBytesRemaining == 0) {
        return C.RESULT_END_OF_INPUT;
    }
    // constrain the read length and try to read from the cipher input stream
    int bytesToRead = getBytesToRead(readLength);
    int bytesRead;
    try {
        bytesRead = mInputStream.read(buffer, offset, bytesToRead);
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    }
    // if we get a -1 that means we failed to read - we're either going to EOF error or broadcast EOF
    if (bytesRead == -1) {
        if (mBytesRemaining != C.LENGTH_UNSET) {
            throw new EncryptedFileDataSourceException(new EOFException());
        }
        return C.RESULT_END_OF_INPUT;
    }
    // we can't decrement bytes remaining if it's just a flag representation (as opposed to a mutable numeric quantity)
    if (mBytesRemaining != C.LENGTH_UNSET) {
        mBytesRemaining -= bytesRead;
    }
    // notify
    if (mTransferListener != null) {
        mTransferListener.onBytesTransferred(this, bytesRead);
    }
    // report
    return bytesRead;
}

private int getBytesToRead(int bytesToRead) {
    if (mBytesRemaining == C.LENGTH_UNSET) {
        return bytesToRead;
    }
    return (int) Math.min(mBytesRemaining, bytesToRead);
}

@Override
public Uri getUri() {
    return mUri;
}

@Override
public void close() throws EncryptedFileDataSourceException {
    mUri = null;
    try {
        if (mInputStream != null) {
            mInputStream.close();
        }
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    } finally {
        mInputStream = null;
        if (mOpened) {
            mOpened = false;
            if (mTransferListener != null) {
                mTransferListener.onTransferEnd(this);
            }
        }
    }
}

public static final class EncryptedFileDataSourceException extends IOException {
    public EncryptedFileDataSourceException(IOException cause) {
        super(cause);
    }
}

public static class StreamingCipherInputStream extends CipherInputStream {

    private int mBytesAvailable;

    public StreamingCipherInputStream(InputStream is, Cipher c) {
        super(is, c);
        try {
            mBytesAvailable = is.available();
        } catch (IOException e) {
            // let it be 0
        }
    }

    // if the CipherInputStream has returns 0 from #skip, #read out enough bytes to get where we need to be
    public long forceSkip(long bytesToSkip) throws IOException {
        long processedBytes = 0;
        while (processedBytes < bytesToSkip) {
            long bytesSkipped = skip(bytesToSkip - processedBytes);
            if (bytesSkipped == 0) {
                if (read() == -1) {
                    throw new EOFException();
                }
                bytesSkipped = 1;
            }
            processedBytes += bytesSkipped;
        }
        return processedBytes;
    }

    // We need to return the available bytes from the upstream.
    // In this implementation we're front loading it, but it's possible the value might change during the lifetime
    // of this instance, and reference to the stream should be retained and queried for available bytes instead
    @Override
    public int available() throws IOException {
        return mBytesAvailable;
    }
}
公共最终类EncryptedFileDataSource实现DataSource{

private final TransferListener最后我找到了解决方案。它可能会有所帮助。首先,我们需要创建一个自定义数据源类来处理本地存储的加密视频文件的解密。下面是该类

EncryptedFileDataSource:

public final class EncryptedFileDataSource implements DataSource {

private final TransferListener<? super EncryptedFileDataSource> mTransferListener;
private StreamingCipherInputStream mInputStream;
private Uri mUri;
private long mBytesRemaining;
private boolean mOpened;
private Cipher mCipher;

public EncryptedFileDataSource(Cipher cipher, TransferListener<? super EncryptedFileDataSource> listener) {
    mCipher = cipher;
    mTransferListener = listener;
}

@Override
public long open(DataSpec dataSpec) throws EncryptedFileDataSourceException {
    // if we're open, we shouldn't need to open again, fast-fail
    if (mOpened) {
        return mBytesRemaining;
    }
    // #getUri is part of the contract...
    mUri = dataSpec.uri;
    // put all our throwable work in a single block, wrap the error in a custom Exception
    try {
        setupInputStream();
        skipToPosition(dataSpec);
        computeBytesRemaining(dataSpec);
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    }
    // if we made it this far, we're open
    mOpened = true;
    // notify
    if (mTransferListener != null) {
        mTransferListener.onTransferStart(this, dataSpec);
    }
    // report
    return mBytesRemaining;
}

private void setupInputStream() throws FileNotFoundException {
    File encryptedFile = new File(mUri.getPath());
    FileInputStream fileInputStream = new FileInputStream(encryptedFile);
    mInputStream = new StreamingCipherInputStream(fileInputStream, mCipher);
}

private void skipToPosition(DataSpec dataSpec) throws IOException {
    mInputStream.forceSkip(dataSpec.position);
}

private void computeBytesRemaining(DataSpec dataSpec) throws IOException {
    if (dataSpec.length != C.LENGTH_UNSET) {
        mBytesRemaining = dataSpec.length;
    } else {
        mBytesRemaining = mInputStream.available();
        if (mBytesRemaining == Integer.MAX_VALUE) {
            mBytesRemaining = C.LENGTH_UNSET;
        }
    }
}

@Override
public int read(byte[] buffer, int offset, int readLength) throws EncryptedFileDataSourceException {
    // fast-fail if there's 0 quantity requested or we think we've already processed everything
    if (readLength == 0) {
        return 0;
    } else if (mBytesRemaining == 0) {
        return C.RESULT_END_OF_INPUT;
    }
    // constrain the read length and try to read from the cipher input stream
    int bytesToRead = getBytesToRead(readLength);
    int bytesRead;
    try {
        bytesRead = mInputStream.read(buffer, offset, bytesToRead);
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    }
    // if we get a -1 that means we failed to read - we're either going to EOF error or broadcast EOF
    if (bytesRead == -1) {
        if (mBytesRemaining != C.LENGTH_UNSET) {
            throw new EncryptedFileDataSourceException(new EOFException());
        }
        return C.RESULT_END_OF_INPUT;
    }
    // we can't decrement bytes remaining if it's just a flag representation (as opposed to a mutable numeric quantity)
    if (mBytesRemaining != C.LENGTH_UNSET) {
        mBytesRemaining -= bytesRead;
    }
    // notify
    if (mTransferListener != null) {
        mTransferListener.onBytesTransferred(this, bytesRead);
    }
    // report
    return bytesRead;
}

private int getBytesToRead(int bytesToRead) {
    if (mBytesRemaining == C.LENGTH_UNSET) {
        return bytesToRead;
    }
    return (int) Math.min(mBytesRemaining, bytesToRead);
}

@Override
public Uri getUri() {
    return mUri;
}

@Override
public void close() throws EncryptedFileDataSourceException {
    mUri = null;
    try {
        if (mInputStream != null) {
            mInputStream.close();
        }
    } catch (IOException e) {
        throw new EncryptedFileDataSourceException(e);
    } finally {
        mInputStream = null;
        if (mOpened) {
            mOpened = false;
            if (mTransferListener != null) {
                mTransferListener.onTransferEnd(this);
            }
        }
    }
}

public static final class EncryptedFileDataSourceException extends IOException {
    public EncryptedFileDataSourceException(IOException cause) {
        super(cause);
    }
}

public static class StreamingCipherInputStream extends CipherInputStream {

    private int mBytesAvailable;

    public StreamingCipherInputStream(InputStream is, Cipher c) {
        super(is, c);
        try {
            mBytesAvailable = is.available();
        } catch (IOException e) {
            // let it be 0
        }
    }

    // if the CipherInputStream has returns 0 from #skip, #read out enough bytes to get where we need to be
    public long forceSkip(long bytesToSkip) throws IOException {
        long processedBytes = 0;
        while (processedBytes < bytesToSkip) {
            long bytesSkipped = skip(bytesToSkip - processedBytes);
            if (bytesSkipped == 0) {
                if (read() == -1) {
                    throw new EOFException();
                }
                bytesSkipped = 1;
            }
            processedBytes += bytesSkipped;
        }
        return processedBytes;
    }

    // We need to return the available bytes from the upstream.
    // In this implementation we're front loading it, but it's possible the value might change during the lifetime
    // of this instance, and reference to the stream should be retained and queried for available bytes instead
    @Override
    public int available() throws IOException {
        return mBytesAvailable;
    }
}

你想通过TearrayOutputStream将整个视频数据保存在
中吗?我不确定,你能不能建议我更好的方法来解密文件使用,它会在飞行中解密?是吗?就像我用密码加密文件后,如果我只是将该文件的路径传递给exoplayer,它会自动进行加密并播放?但在加密文件时,我生成了一个密钥。exoplayer从哪里获得解密文件的密钥?请添加一个关于修复程序的文本说明。它仍然缺少为什么现在可以顺利播放(这没有查看源文件)。因为现在我正在附加我们的自定义数据源以播放加密的本地文件。我在上面提供了所有必需的代码
private void prepareExoPlayerFromFileUri(Uri uri) {

    try {
        BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
        videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
        trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);

        // 2. Create a default LoadControl
        LoadControl loadControl = new DefaultLoadControl();

        // 3. Create the player
        player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);
        player.addListener(this);
        simpleExoPlayerView.setPlayer(player);
        DataSpec dataSpec = new DataSpec(uri);
        Cipher aes = Cipher.getInstance("ARC4");
        aes.init(Cipher.DECRYPT_MODE, new SecretKeySpec("secretkey".getBytes(), "ARC4"));
        final EncryptedFileDataSource fileDataSource = new EncryptedFileDataSource(aes, new TransferListener<EncryptedFileDataSource>() {
            @Override
            public void onTransferStart(EncryptedFileDataSource source, DataSpec dataSpec) {

            }

            @Override
            public void onBytesTransferred(EncryptedFileDataSource source, int bytesTransferred) {

            }

            @Override
            public void onTransferEnd(EncryptedFileDataSource source) {

            }
        });
        try {
            fileDataSource.open(dataSpec);
        } catch (Exception e) {
            e.printStackTrace();
        }

        DataSource.Factory factory = new DataSource.Factory() {
            @Override
            public DataSource createDataSource() {
                return fileDataSource;
            }
        };
        MediaSource videoSource = new ExtractorMediaSource(fileDataSource.getUri(),
                factory, new DefaultExtractorsFactory(), null, null);

        player.prepare(videoSource);
        simpleExoPlayerView.requestFocus();
        player.setPlayWhenReady(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  prepareExoPlayerFromFileUri(Uri.parse(url));