使用CipherInputStream解密文件

时间:2018-06-03 07:47:47

标签: android encryption

我有一个要求,我需要以加密形式下载视频文件,而在播放时我需要解密和播放。为此我使用CipherOutputSStream和CipherInputStream。我使用以下代码加密和解密文件。加密正在顺利进行,但我对11 MB文件的解密需要很长时间。并且解密的文件也无法播放。下面是我的代码。我创建了一个自定义数据源类,但我无法将其附加到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;
    }
}

}

1 个答案:

答案 0 :(得分:-1)

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

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;
    }
}

}

然后初始化ExoPlayer和PlayerView类并绑定我们的自定义数据源,我们可以使用以下方法

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));